From 15bb156e819c7960638adcecf4652f2db449d784 Mon Sep 17 00:00:00 2001 From: Luca Faggianelli Date: Thu, 17 Oct 2019 01:49:09 +0200 Subject: [PATCH 1/5] gitlab credentials and triggers --- .../credentials/GitlabApi.credentials.ts | 24 ++ .../nodes/Gitlab/GenericFunctions.ts | 52 ++++ .../nodes/Gitlab/GitlabTrigger.node.ts | 252 ++++++++++++++++++ packages/nodes-base/nodes/Gitlab/gitlab.png | Bin 0 -> 2234 bytes 4 files changed, 328 insertions(+) create mode 100644 packages/nodes-base/credentials/GitlabApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Gitlab/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts create mode 100644 packages/nodes-base/nodes/Gitlab/gitlab.png diff --git a/packages/nodes-base/credentials/GitlabApi.credentials.ts b/packages/nodes-base/credentials/GitlabApi.credentials.ts new file mode 100644 index 0000000000..b3aceb1211 --- /dev/null +++ b/packages/nodes-base/credentials/GitlabApi.credentials.ts @@ -0,0 +1,24 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class GitlabApi implements ICredentialType { + name = 'gitlabApi'; + displayName = 'Gitlab API'; + properties = [ + { + displayName: 'Gitlab Server', + name: 'server', + type: 'string' as NodePropertyTypes, + default: 'https://gitlab.com' + }, + { + displayName: 'Access Token', + name: 'accessToken', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts b/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts new file mode 100644 index 0000000000..1c665dbf44 --- /dev/null +++ b/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts @@ -0,0 +1,52 @@ +import { + IExecuteFunctions, + IHookFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +/** + * Make an API request to Gitlab + * + * @param {IHookFunctions} this + * @param {string} method + * @param {string} url + * @param {object} body + * @returns {Promise} + */ +export async function gitlabApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: object, query?: object): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('gitlabApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const options = { + method, + headers: { + 'Private-Token': `token ${credentials.accessToken}`, + }, + body, + qs: query, + uri: `${(credentials.server as string).replace(/\/$/, '')}/api/v4/${endpoint}`, + json: true + }; + + try { + return await this.helpers.request(options); + } catch (error) { + if (error.statusCode === 401) { + // Return a clear error + throw new Error('The Gitlab credentials are not valid!'); + } + + if (error.response && error.response.body && error.response.body.message) { + // Try to return the error prettier + throw new Error(`Gitlab error response [${error.statusCode}]: ${error.response.body.message}`); + } + + // If that data does not exist for some reason return the actual error + throw error; + } +} diff --git a/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts b/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts new file mode 100644 index 0000000000..307e7441d5 --- /dev/null +++ b/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts @@ -0,0 +1,252 @@ +import { + IHookFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeTypeDescription, + INodeType, + IWebhookResponseData, +} from 'n8n-workflow'; + +import { + gitlabApiRequest, +} from './GenericFunctions'; + + +export class GitlabTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Gitlab Trigger', + name: 'gitlabTrigger', + icon: 'file:gitlab.png', + group: ['trigger'], + version: 1, + subtitle: '={{$parameter["owner"] + "/" + $parameter["repository"] + ": " + $parameter["events"].join(", ")}}', + description: 'Starts the workflow when a Gitlab events occurs.', + defaults: { + name: 'Gitlab Trigger', + color: '#FC6D27', + }, + inputs: [], + outputs: ['main'], + credentials: [ + { + name: 'gitlabApi', + required: true, + } + ], + webhooks: [ + { + name: 'default', + httpMethod: 'POST', + responseMode: 'onReceived', + path: 'webhook', + }, + ], + properties: [ + { + displayName: 'Repository Owner', + name: 'owner', + type: 'string', + default: '', + required: true, + placeholder: 'n8n-io', + description: 'Owner of the repsitory.', + }, + { + displayName: 'Repository Name', + name: 'repository', + type: 'string', + default: '', + required: true, + placeholder: 'n8n', + description: 'The name of the repsitory.', + }, + { + displayName: 'Events', + name: 'events', + type: 'multiOptions', + options: [ + { + name: '*', + value: '*', + description: 'Any time any event is triggered (Wildcard Event).', + }, + { + name: 'Comment', + value: 'note', + description: 'Triggered when a new comment is made on commits, merge requests, issues, and code snippets.' + }, + { + name: 'Issue', + value: 'issues', + description: 'Triggered when a new issue is created or an existing issue was updated/closed/reopened.' + }, + { + name: 'Job', + value: 'job', + description: 'Triggered on status change of a job.' + }, + { + name: 'Merge Request', + value: 'merge_requests', + description: 'Triggered when a new merge request is created, an existing merge request was updated/merged/closed or a commit is added in the source branch.' + }, + { + name: 'Pipeline', + value: 'pipeline', + description: 'Triggered on status change of Pipeline.' + }, + { + name: 'Push', + value: 'push', + description: 'Triggered when you push to the repository except when pushing tags.' + }, + { + name: 'Tag', + value: 'tag_push', + description: 'Triggered when you create (or delete) tags to the repository.' + }, + { + name: 'Wiki Page', + value: 'wiki_page', + description: 'Triggered when a wiki page is created, updated or deleted.' + } + ], + required: true, + default: [], + description: 'The events to listen to.', + }, + ], + }; + + // @ts-ignore (because of request) + webhookMethods = { + default: { + async checkExists(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + + if (webhookData.webhookId === undefined) { + // No webhook id is set so no webhook can exist + return false; + } + + // Webhook got created before so check if it still exists + const owner = this.getNodeParameter('owner') as string; + const repository = this.getNodeParameter('repository') as string; + const endpoint = `/projects/${owner}%2F${repository}/hooks/${webhookData.webhookId}`; + + try { + await gitlabApiRequest.call(this, 'GET', endpoint, {}); + } catch (e) { + if (e.message.includes('[404]:')) { + // Webhook does not exist + delete webhookData.webhookId; + delete webhookData.webhookEvents; + + return false; + } + + // Some error occured + throw e; + } + + // If it did not error then the webhook exists + return true; + }, + /** + * Gitlab API - Add project hook: + * https://docs.gitlab.com/ee/api/projects.html#add-project-hook + */ + async create(this: IHookFunctions): Promise { + const webhookUrl = this.getNodeWebhookUrl('default'); + + const owner = this.getNodeParameter('owner') as string; + const repository = this.getNodeParameter('repository') as string; + + let eventsArray = this.getNodeParameter('events', []) as string[]; + if (eventsArray.includes('*')) { + eventsArray = ['note', 'issues', 'job', 'merge_requests', 'pipeline', 'push', 'tag_push', 'wiki_page']; + } + + const events: { [key: string]: boolean } = { }; + for (const e of eventsArray) { + events[`${e}_events`] = true + } + + const endpoint = `/projects/${owner}%2F${repository}/hooks`; + + const body = { + url: webhookUrl, + events, + enable_ssl_verification: false, + }; + + + let responseData; + try { + responseData = await gitlabApiRequest.call(this, 'POST', endpoint, body); + } catch (e) { + throw e; + } + + if (responseData.id === undefined) { + // Required data is missing so was not successful + throw new Error('Gitlab webhook creation response did not contain the expected data.'); + } + + const webhookData = this.getWorkflowStaticData('node'); + webhookData.webhookId = responseData.id as string; + webhookData.webhookEvents = eventsArray as string[]; + + return true; + }, + async delete(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + + if (webhookData.webhookId !== undefined) { + const owner = this.getNodeParameter('owner') as string; + const repository = this.getNodeParameter('repository') as string; + const endpoint = `/projects/${owner}%2F${repository}/hooks/${webhookData.webhookId}`; + const body = {}; + + try { + await gitlabApiRequest.call(this, 'DELETE', endpoint, body); + } catch (e) { + return false; + } + + // Remove from the static workflow data so that it is clear + // that no webhooks are registred anymore + delete webhookData.webhookId; + delete webhookData.webhookEvents; + } + + return true; + }, + }, + }; + + + + async webhook(this: IWebhookFunctions): Promise { + const bodyData = this.getBodyData(); + + const returnData: IDataObject[] = []; + + returnData.push( + { + body: bodyData, + headers: this.getHeaderData(), + query: this.getQueryData(), + } + ); + + return { + workflowData: [ + this.helpers.returnJsonArray(returnData) + ], + }; + } +} diff --git a/packages/nodes-base/nodes/Gitlab/gitlab.png b/packages/nodes-base/nodes/Gitlab/gitlab.png new file mode 100644 index 0000000000000000000000000000000000000000..38ddea94d8506486893d5d20ce3ad820d23a2a46 GIT binary patch literal 2234 zcmV;r2u1gaP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 zVoOIv0RI600RN!9r;`8x00(qQO+^Re3K0M`4!f8HW&i*OzDYzuRA}Dqnph(cfTP_+#yJ_6UX)s;vA?G<5`?%q4%9r3lijo;USEN0Te}7o&2C_ zU#6K!`erZF8ILgQ-52G|E7>VopSjVLtD3ktJcK~X<>Wb+lgF=(`CR(S(48ebdc2p( z3jrvHRCx-OeJ+CWgXwuEtWHpsjjDWGm2Y+}&Gopbd4Ek2c9GD#HoH`nZ2$_pZJ^0) zrs)bPi_81cxCl^o0A0Y{s&bFI=HJttq#dZT85oQo@Oc+W|6BqohcGADKvN34@{c-D zc7|DDbZ>LAZkX_;J!z(Hc%bY8Qu)U#P`0`V#tzJ%tknt5$q>rtW}YdJ_(``Y$~EV+ zVZtsH+PcCXjb(N#IN0qcGzh2XC(D=jrEwt;mrZZc&<0i6*sPiwCY)~~n}FLjrQ8>V zeA>;Etn+eiQ`lWHvCW4L<+E3Vd(_Einvr!w zgbRBj%7*d=JP?I)*IZ@oLcs=_C?n?r7NhKN5e;|ix(Q#}lP1auC_9Pw#Bx}T@&$D; z`B;DRgq`3bS&y=%l8wSHFVOZzyLH`!T`1Uu>r~iT&7Z=spHwe%%{E1L{NNxO%lTp8 zQxy%BP3k1wsI2QIoR{+kl+RW-c{52=H(1A`3{c20;wvkv9{TDJKkZCkE^y}vHqsS1gtYY#)&RflU2;mdwP zPeh?{m-aN+RIG;Klx#&*fz@RlIBv@uv>$Ye@-U|;7V81mocfz@!4yJe*Yz3Ci46MknZj~DI0`S|Ensrz_$FDI*i7%vU0KaINgkf%K zK%#d3s#-{wKt_{D$q~;ZmUc&F(qbS{D_bRGf;PqaRcw5q4`?fSuW&zstgEL5S6>n< zV5J`^IpQgh%)dNQnLr|?n0jR``2{kS$5{z1D!a%l5x$6p@TKaqHZ@0u2Z=>Iix8c_ zs>)ux0D@$Fvi59T_Z4k>i5pd=NQE7G)fu{~ORAoeRu%{=j#dezZdq$wyPjWutk%Nj zF%`G=>ZtA_1R_qLyZ{xB90jR$Gdnv9K?;mlBZ|D7#mt^xvveRtIf3H^>GoW7*)>nt zK5-h*>tyvA98aRWU)Ink?wqOFq-~)1HDqmUT<^1NYB$F#IDSmT69^+Zp(K9zVXd{T zjque0WSK#)>kx+L5DrqjF%&#q189+VBvDP)<>XxbfHRHq7LG@; zoWxN#S!m*8TKL8Qn!`w&Y)5z=;ep!>9B>YpD2m^~nN`gUng#PEU7#3ASidX&Ua^PDOa}Xl61hOCDCkPt| ziMOi%#bVlwd#V6`WI3>&AI5WtT5Ok3zOA5O1OaV*MpGKgkF?GnSaW66RY*4P3w!AlW`qY z{O>J1**i)YAUZw(SxoMIS#CO+6vMRt{pL Date: Thu, 17 Oct 2019 02:53:15 +0200 Subject: [PATCH 2/5] gitlab node --- .../nodes-base/nodes/Gitlab/Gitlab.node.ts | 958 ++++++++++++++++++ 1 file changed, 958 insertions(+) create mode 100644 packages/nodes-base/nodes/Gitlab/Gitlab.node.ts diff --git a/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts new file mode 100644 index 0000000000..96f31d512f --- /dev/null +++ b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts @@ -0,0 +1,958 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeTypeDescription, + INodeType, +} from 'n8n-workflow'; + +import { + gitlabApiRequest, +} from './GenericFunctions'; + + +export class Gitlab implements INodeType { + description: INodeTypeDescription = { + displayName: 'Gitlab', + name: 'gitlab', + icon: 'file:gitlab.png', + group: ['input'], + version: 1, + description: 'Retrieve data from Gitlab API.', + defaults: { + name: 'Gitlab', + color: '#FC6D27', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'gitlabApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Issue', + value: 'issue', + }, + { + name: 'Repository', + value: 'repository', + }, + { + name: 'Release', + value: 'release', + }, + { + name: 'User', + value: 'user', + }, + ], + default: 'issue', + description: 'The resource to operate on.', + }, + + + + // ---------------------------------- + // operations + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'issue', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new issue', + }, + { + name: 'Create Comment', + value: 'createComment', + description: 'Create a new comment on an issue', + }, + { + name: 'Edit', + value: 'edit', + description: 'Edit an issue', + }, + { + name: 'Get', + value: 'get', + description: 'Get the data of a single issues', + }, + { + name: 'Lock', + value: 'lock', + description: 'Lock an issue', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, + + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'repository', + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + description: 'Get the data of a single repository', + }, + { + name: 'Get Issues', + value: 'getIssues', + description: 'Returns issues of a repository', + }, + ], + default: 'getIssues', + description: 'The operation to perform.', + }, + + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'user', + ], + }, + }, + options: [ + { + name: 'Get Repositories', + value: 'getRepositories', + description: 'Returns the repositories of a user', + }, + ], + default: 'getRepositories', + description: 'The operation to perform.', + }, + + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'release', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Creates a new release', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, + + + + // ---------------------------------- + // shared + // ---------------------------------- + { + displayName: 'Project Owner', + name: 'owner', + type: 'string', + default: '', + required: true, + placeholder: 'n8n-io', + description: 'User, group or namespace of the project.', + }, + { + displayName: 'Project Name', + name: 'repository', + type: 'string', + default: '', + required: true, + displayOptions: { + hide: { + resource: [ + 'user', + ], + operation: [ + 'getRepositories', + ], + }, + }, + placeholder: 'n8n', + description: 'The name of the project.', + }, + + // ---------------------------------- + // issue + // ---------------------------------- + + // ---------------------------------- + // issue:create + // ---------------------------------- + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'issue', + ], + }, + }, + description: 'The title of the issue.', + }, + { + displayName: 'Body', + name: 'body', + type: 'string', + typeOptions: { + rows: 5, + }, + default: '', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'issue', + ], + }, + }, + description: 'The body of the issue.', + }, + { + displayName: 'Labels', + name: 'labels', + type: 'collection', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Label', + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'issue', + ], + }, + }, + default: { 'label': '' }, + options: [ + { + displayName: 'Label', + name: 'label', + type: 'string', + default: '', + description: 'Label to add to issue.', + }, + ], + }, + { + displayName: 'Assignees', + name: 'assignee_ids', + type: 'collection', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Assignee', + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'issue', + ], + }, + }, + default: { 'assignee': '' }, + options: [ + { + displayName: 'Assignee', + name: 'assignee', + type: 'number', + default: 0, + description: 'User ID to assign issue to.', + }, + ], + }, + + // ---------------------------------- + // issue:createComment + // ---------------------------------- + { + displayName: 'Issue Number', + name: 'issueNumber', + type: 'number', + default: 0, + required: true, + displayOptions: { + show: { + operation: [ + 'createComment', + ], + resource: [ + 'issue', + ], + }, + }, + description: 'The number of the issue on which to create the comment on.', + }, + { + displayName: 'Body', + name: 'body', + type: 'string', + typeOptions: { + rows: 5, + }, + displayOptions: { + show: { + operation: [ + 'createComment', + ], + resource: [ + 'issue', + ], + }, + }, + default: '', + description: 'The body of the comment.', + }, + + // ---------------------------------- + // issue:edit + // ---------------------------------- + { + displayName: 'Issue Number', + name: 'issueNumber', + type: 'number', + default: 0, + required: true, + displayOptions: { + show: { + operation: [ + 'edit', + ], + resource: [ + 'issue', + ], + }, + }, + description: 'The number of the issue edit.', + }, + { + displayName: 'Edit Fields', + name: 'editFields', + type: 'collection', + typeOptions: { + multipleValueButtonText: 'Add Field', + }, + displayOptions: { + show: { + operation: [ + 'edit', + ], + resource: [ + 'issue', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'The title of the issue.', + }, + { + displayName: 'Body', + name: 'body', + type: 'string', + typeOptions: { + rows: 5, + }, + default: '', + description: 'The body of the issue.', + }, + { + displayName: 'State', + name: 'state', + type: 'options', + options: [ + { + name: 'Closed', + value: 'closed', + description: 'Set the state to "closed"', + }, + { + name: 'Open', + value: 'open', + description: 'Set the state to "open"', + }, + ], + default: 'open', + description: 'The state to set.', + }, + { + displayName: 'Labels', + name: 'labels', + type: 'collection', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Label', + }, + default: { 'label': '' }, + options: [ + { + displayName: 'Label', + name: 'label', + type: 'string', + default: '', + description: 'Label to add to issue.', + }, + ], + }, + { + displayName: 'Assignees', + name: 'assignee_ids', + type: 'collection', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Assignee', + }, + default: { 'assignee': '' }, + options: [ + { + displayName: 'Assignees', + name: 'assignee', + type: 'string', + default: '', + description: 'User to assign issue too.', + }, + ], + }, + ], + }, + + // ---------------------------------- + // issue:get + // ---------------------------------- + { + displayName: 'Issue Number', + name: 'issueNumber', + type: 'number', + default: 0, + required: true, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'issue', + ], + }, + }, + description: 'The number of the issue get data of.', + }, + + // ---------------------------------- + // issue:lock + // ---------------------------------- + { + displayName: 'Issue Number', + name: 'issueNumber', + type: 'number', + default: 0, + required: true, + displayOptions: { + show: { + operation: [ + 'lock', + ], + resource: [ + 'issue', + ], + }, + }, + description: 'The number of the issue to lock.', + }, + { + displayName: 'Lock Reason', + name: 'lockReason', + type: 'options', + displayOptions: { + show: { + operation: [ + 'lock', + ], + resource: [ + 'issue', + ], + }, + }, + options: [ + { + name: 'Off-Topic', + value: 'off-topic', + description: 'The issue is Off-Topic', + }, + { + name: 'Too Heated', + value: 'too heated', + description: 'The discussion is too heated', + }, + { + name: 'Resolved', + value: 'resolved', + description: 'The issue got resolved', + }, + { + name: 'Spam', + value: 'spam', + description: 'The issue is spam', + }, + ], + default: 'resolved', + description: 'The reason to lock the issue.', + }, + + + + // ---------------------------------- + // release + // ---------------------------------- + + // ---------------------------------- + // release:create + // ---------------------------------- + { + displayName: 'Tag', + name: 'releaseTag', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'release', + ], + }, + }, + description: 'The tag of the release.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + typeOptions: { + multipleValueButtonText: 'Add Field', + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'release', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'The name of the release.', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + typeOptions: { + rows: 5, + }, + default: '', + description: 'The description of the release.', + }, + { + displayName: 'Ref', + name: 'ref', + type: 'string', + default: '', + description: 'If Tag doesn’t exist, the release will be created from Ref. It can be a commit SHA, another tag name, or a branch name.', + }, + ], + }, + + + + // ---------------------------------- + // repository + // ---------------------------------- + + // ---------------------------------- + // repository:getIssues + // ---------------------------------- + { + displayName: 'Filters', + name: 'getRepositoryIssuesFilters', + type: 'collection', + typeOptions: { + multipleValueButtonText: 'Add Filter', + }, + displayOptions: { + show: { + operation: [ + 'getIssues' + ], + resource: [ + 'repository', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Assignee', + name: 'assignee_username', + type: 'string', + default: '', + description: 'Return only issues which are assigned to a specific user.', + }, + { + displayName: 'Creator', + name: 'author_username', + type: 'string', + default: '', + description: 'Return only issues which were created by a specific user.', + }, + { + displayName: 'Labels', + name: 'labels', + type: 'string', + default: '', + description: 'Return only issues with the given labels. Multiple lables can be separated by comma.', + }, + { + displayName: 'Updated After', + name: 'updated_after', + type: 'dateTime', + default: '', + description: 'Return only issues updated at or after this time.', + }, + { + displayName: 'State', + name: 'state', + type: 'options', + options: [ + { + name: 'All', + value: '', + description: 'Returns issues with any state', + }, + { + name: 'Closed', + value: 'closed', + description: 'Return issues with "closed" state', + }, + { + name: 'Open', + value: 'opened', + description: 'Return issues with "open" state', + }, + ], + default: 'opened', + description: 'The state to filter by.', + }, + { + displayName: 'Sort', + name: 'order_by', + type: 'options', + options: [ + { + name: 'Created At', + value: 'created_at', + description: 'Sort by created date.', + }, + { + name: 'Updated At', + value: 'updated_at', + description: 'Sort by updated date.', + }, + { + name: 'Priority', + value: 'priority', + description: 'Sort by priority.' + }, + ], + default: 'desc', + description: 'The order the issues should be returned in.', + }, + { + displayName: 'Direction', + name: 'sort', + type: 'options', + options: [ + { + name: 'Ascending', + value: 'asc', + description: 'Sort in ascending order', + }, + { + name: 'Descending', + value: 'desc', + description: 'Sort in descending order', + }, + ], + default: 'desc', + description: 'The sort order.', + }, + + ], + }, + + ], + }; + + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + + const credentials = this.getCredentials('gitlabApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + // Operations which overwrite the returned data + const overwriteDataOperations = [ + 'issue:create', + 'issue:createComment', + 'issue:edit', + 'issue:get', + 'release:create', + 'repository:get', + ]; + // Operations which overwrite the returned data and return arrays + // and has so to be merged with the data of other items + const overwriteDataOperationsArray = [ + 'repository:getIssues', + 'user:getRepositories', + ]; + + + // For Post + let body: IDataObject; + // For Query string + let qs: IDataObject; + + let requestMethod: string; + let endpoint: string; + + const operation = this.getNodeParameter('operation', 0) as string; + const resource = this.getNodeParameter('resource', 0) as string; + const fullOperation = `${resource}:${operation}`; + + for (let i = 0; i < items.length; i++) { + // Reset all values + requestMethod = 'GET'; + endpoint = ''; + body = {}; + qs = {}; + + // Request the parameters which almost all operations need + const owner = this.getNodeParameter('owner', i) as string; + let repository = ''; + if (fullOperation !== 'user:getRepositories') { + repository = this.getNodeParameter('repository', i) as string; + } + + const baseEndpoint = `/repos/${owner}/${repository}` + + if (resource === 'issue') { + if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + + requestMethod = 'POST'; + + body.title = this.getNodeParameter('title', i) as string; + body.description = this.getNodeParameter('body', i) as string; + const labels = this.getNodeParameter('labels', i) as IDataObject[]; + + const assigneeIds = this.getNodeParameter('assignee_ids', i) as IDataObject[]; + + body.labels = labels.map((data) => data['label']).join(','); + body.assignee_ids = assigneeIds.map((data) => data['assignee']); + + endpoint = `${baseEndpoint}/issues`; + } else if (operation === 'createComment') { + // ---------------------------------- + // createComment + // ---------------------------------- + requestMethod = 'POST'; + + const issueNumber = this.getNodeParameter('issueNumber', i) as string; + + body.body = this.getNodeParameter('body', i) as string; + + endpoint = `${baseEndpoint}/issues/${issueNumber}/notes`; + } else if (operation === 'edit') { + // ---------------------------------- + // edit + // ---------------------------------- + + requestMethod = 'PATCH'; + + const issueNumber = this.getNodeParameter('issueNumber', i) as string; + + body = this.getNodeParameter('editFields', i, {}) as IDataObject; + + if (body.labels !== undefined) { + body.labels = (body.labels as IDataObject[]).map((data) => data['label']).join(','); + } + if (body.assignee_ids !== undefined) { + body.assignee_ids = (body.assignee_ids as IDataObject[]).map((data) => data['assignee']); + } + + endpoint = `${baseEndpoint}/issues/${issueNumber}`; + } else if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + requestMethod = 'GET'; + + const issueNumber = this.getNodeParameter('issueNumber', i) as string; + + endpoint = `${baseEndpoint}/issues/${issueNumber}`; + } else if (operation === 'lock') { + // ---------------------------------- + // lock + // ---------------------------------- + + requestMethod = 'PUT'; + + const issueNumber = this.getNodeParameter('issueNumber', i) as string; + + body.discussion_locked = true; + + endpoint = `${baseEndpoint}/issues/${issueNumber}`; + } + } else if (resource === 'release') { + if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + + requestMethod = 'POST'; + + body = this.getNodeParameter('additionalFields', i, {}) as IDataObject; + + body.tag_name = this.getNodeParameter('releaseTag', i) as string; + + endpoint = `${baseEndpoint}/releases`; + } + } else if (resource === 'repository') { + if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + requestMethod = 'GET'; + + endpoint = `${baseEndpoint}`; + } else if (operation === 'getIssues') { + // ---------------------------------- + // getIssues + // ---------------------------------- + + requestMethod = 'GET'; + + qs = this.getNodeParameter('getRepositoryIssuesFilters', i) as IDataObject; + + endpoint = `${baseEndpoint}/issues`; + } + } else if (resource === 'user') { + if (operation === 'getRepositories') { + // ---------------------------------- + // getRepositories + // ---------------------------------- + + requestMethod = 'GET'; + + endpoint = `/users/${owner}/projects`; + } + } else { + throw new Error(`The resource "${resource}" is not known!`); + } + + const responseData = await gitlabApiRequest.call(this, requestMethod, endpoint, body, qs); + + if (overwriteDataOperations.includes(fullOperation)) { + returnData.push(responseData); + } else if (overwriteDataOperationsArray.includes(fullOperation)) { + returnData.push.apply(returnData, responseData); + } + } + + if (overwriteDataOperations.includes(fullOperation) || overwriteDataOperationsArray.includes(fullOperation)) { + // Return data gets replaced + return [this.helpers.returnJsonArray(returnData)]; + } else { + // For all other ones simply return the unchanged items + return this.prepareOutputData(items); + } + + } +} From 34092286bc38e1bac55623c8aeebf8ffeaa925f9 Mon Sep 17 00:00:00 2001 From: Luca Faggianelli Date: Thu, 17 Oct 2019 02:54:17 +0200 Subject: [PATCH 3/5] add gitlab to package.json --- packages/nodes-base/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index c7d3809070..3824505ee8 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -33,6 +33,7 @@ "dist/credentials/ChargebeeApi.credentials.js", "dist/credentials/DropboxApi.credentials.js", "dist/credentials/GithubApi.credentials.js", + "dist/credentials/GitlabApi.credentials.js", "dist/credentials/GoogleApi.credentials.js", "dist/credentials/HttpBasicAuth.credentials.js", "dist/credentials/HttpDigestAuth.credentials.js", @@ -73,6 +74,8 @@ "dist/nodes/FunctionItem.node.js", "dist/nodes/Github/Github.node.js", "dist/nodes/Github/GithubTrigger.node.js", + "dist/nodes/Gitlab/Gitlab.node.js", + "dist/nodes/Gitlab/GitlabTrigger.node.js", "dist/nodes/GoogleSheets/GoogleSheets.node.js", "dist/nodes/HttpRequest.node.js", "dist/nodes/If.node.js", From 2b554db8f3fc9c124f174cf4d88e746bd4ae4db8 Mon Sep 17 00:00:00 2001 From: Luca Faggianelli Date: Thu, 17 Oct 2019 03:33:52 +0200 Subject: [PATCH 4/5] fix gitlab auth and endpoint url --- packages/nodes-base/nodes/Gitlab/GenericFunctions.ts | 2 +- packages/nodes-base/nodes/Gitlab/Gitlab.node.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts b/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts index 1c665dbf44..85e92bbedc 100644 --- a/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts @@ -25,7 +25,7 @@ export async function gitlabApiRequest(this: IHookFunctions | IExecuteFunctions, const options = { method, headers: { - 'Private-Token': `token ${credentials.accessToken}`, + 'Private-Token': `${credentials.accessToken}`, }, body, qs: query, diff --git a/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts index 96f31d512f..caacfc7cf8 100644 --- a/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts +++ b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts @@ -816,7 +816,7 @@ export class Gitlab implements INodeType { repository = this.getNodeParameter('repository', i) as string; } - const baseEndpoint = `/repos/${owner}/${repository}` + const baseEndpoint = `/projects/${owner}%2F${repository}` if (resource === 'issue') { if (operation === 'create') { From 4ebdffd0dd2a51eb736b9972d7c72b7f6cc7fdf7 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 25 Oct 2019 08:15:36 +0200 Subject: [PATCH 5/5] :zap: Small fixes to Gitlab node before merge --- packages/nodes-base/nodes/Gitlab/GenericFunctions.ts | 2 +- packages/nodes-base/nodes/Gitlab/Gitlab.node.ts | 5 +++-- packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts b/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts index 85e92bbedc..8f1811b8c7 100644 --- a/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts @@ -29,7 +29,7 @@ export async function gitlabApiRequest(this: IHookFunctions | IExecuteFunctions, }, body, qs: query, - uri: `${(credentials.server as string).replace(/\/$/, '')}/api/v4/${endpoint}`, + uri: `${(credentials.server as string).replace(/\/$/, '')}/api/v4${endpoint}`, json: true }; diff --git a/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts index caacfc7cf8..bcf5712a84 100644 --- a/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts +++ b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts @@ -21,6 +21,7 @@ export class Gitlab implements INodeType { icon: 'file:gitlab.png', group: ['input'], version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Retrieve data from Gitlab API.', defaults: { name: 'Gitlab', @@ -733,7 +734,7 @@ export class Gitlab implements INodeType { description: 'Sort by priority.' }, ], - default: 'desc', + default: 'created_at', description: 'The order the issues should be returned in.', }, { @@ -816,7 +817,7 @@ export class Gitlab implements INodeType { repository = this.getNodeParameter('repository', i) as string; } - const baseEndpoint = `/projects/${owner}%2F${repository}` + const baseEndpoint = `/projects/${owner}%2F${repository}`; if (resource === 'issue') { if (operation === 'create') { diff --git a/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts b/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts index 307e7441d5..28987c2458 100644 --- a/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts +++ b/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts @@ -172,7 +172,7 @@ export class GitlabTrigger implements INodeType { const events: { [key: string]: boolean } = { }; for (const e of eventsArray) { - events[`${e}_events`] = true + events[`${e}_events`] = true; } const endpoint = `/projects/${owner}%2F${repository}/hooks`;