-
Notifications
You must be signed in to change notification settings - Fork 7.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add crowd.dev node and trigger node (#6082)
- Loading branch information
1 parent
7a8b85a
commit 238a78f
Showing
18 changed files
with
1,703 additions
and
0 deletions.
There are no files selected for viewing
72 changes: 72 additions & 0 deletions
72
packages/nodes-base/credentials/CrowdDevApi.credentials.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import type { | ||
IAuthenticateGeneric, | ||
ICredentialTestRequest, | ||
ICredentialType, | ||
INodeProperties, | ||
} from 'n8n-workflow'; | ||
|
||
export class CrowdDevApi implements ICredentialType { | ||
name = 'crowdDevApi'; | ||
|
||
displayName = 'crowd.dev API'; | ||
|
||
documentationUrl = 'crowdDev'; | ||
|
||
properties: INodeProperties[] = [ | ||
{ | ||
displayName: 'URL', | ||
name: 'url', | ||
type: 'string', | ||
default: 'https://app.crowd.dev', | ||
}, | ||
{ | ||
displayName: 'Tenant ID', | ||
name: 'tenantId', | ||
type: 'string', | ||
default: '', | ||
}, | ||
{ | ||
displayName: 'Token', | ||
name: 'token', | ||
type: 'string', | ||
typeOptions: { | ||
password: true, | ||
}, | ||
default: '', | ||
}, | ||
{ | ||
displayName: 'Ignore SSL Issues', | ||
name: 'allowUnauthorizedCerts', | ||
type: 'boolean', | ||
description: 'Whether to connect even if SSL certificate validation is not possible', | ||
default: false, | ||
}, | ||
]; | ||
|
||
// This allows the credential to be used by other parts of n8n | ||
// stating how this credential is injected as part of the request | ||
// An example is the Http Request node that can make generic calls | ||
// reusing this credential | ||
authenticate: IAuthenticateGeneric = { | ||
type: 'generic', | ||
properties: { | ||
headers: { | ||
Authorization: '={{"Bearer " + $credentials.token}}', | ||
}, | ||
}, | ||
}; | ||
|
||
// The block below tells how this credential can be tested | ||
test: ICredentialTestRequest = { | ||
request: { | ||
method: 'POST', | ||
baseURL: '={{$credentials.url.replace(/\\/$/, "") + "/api/tenant/" + $credentials.tenantId}}', | ||
url: '/member/query', | ||
skipSslCertificateValidation: '={{ $credentials.allowUnauthorizedCerts }}', | ||
body: { | ||
limit: 1, | ||
offset: 0, | ||
}, | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"node": "n8n-nodes-base.crowdDev", | ||
"nodeVersion": "1.0", | ||
"codexVersion": "1.0", | ||
"categories": ["Productivity"], | ||
"resources": { | ||
"credentialDocumentation": [ | ||
{ | ||
"url": "https://docs.n8n.io/credentials/crowdDev" | ||
} | ||
], | ||
"primaryDocumentation": [ | ||
{ | ||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.crowdDev/" | ||
} | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import type { INodeType, INodeTypeDescription } from 'n8n-workflow'; | ||
import { allProperties } from './descriptions'; | ||
|
||
export class CrowdDev implements INodeType { | ||
description: INodeTypeDescription = { | ||
displayName: 'crowd.dev', | ||
name: 'crowdDev', | ||
icon: 'file:crowdDev.svg', | ||
group: ['transform'], | ||
version: 1, | ||
subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}', | ||
description: | ||
'crowd.dev is an open-source suite of community and data tools built to unlock community-led growth for your organization.', | ||
defaults: { | ||
name: 'crowd.dev', | ||
}, | ||
inputs: ['main'], | ||
outputs: ['main'], | ||
credentials: [ | ||
{ | ||
name: 'crowdDevApi', | ||
required: true, | ||
}, | ||
], | ||
requestDefaults: { | ||
baseURL: '={{$credentials.url}}/api/tenant/{{$credentials.tenantId}}', | ||
json: true, | ||
skipSslCertificateValidation: '={{ $credentials.allowUnauthorizedCerts }}', | ||
}, | ||
properties: allProperties, | ||
}; | ||
} |
18 changes: 18 additions & 0 deletions
18
packages/nodes-base/nodes/CrowdDev/CrowdDevTrigger.node.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"node": "n8n-nodes-base.crowdDevTrigger", | ||
"nodeVersion": "1.0", | ||
"codexVersion": "1.0", | ||
"categories": ["Productivity"], | ||
"resources": { | ||
"credentialDocumentation": [ | ||
{ | ||
"url": "https://docs.n8n.io/credentials/crowdDev" | ||
} | ||
], | ||
"primaryDocumentation": [ | ||
{ | ||
"url": "https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.crowddevtrigger/" | ||
} | ||
] | ||
} | ||
} |
185 changes: 185 additions & 0 deletions
185
packages/nodes-base/nodes/CrowdDev/CrowdDevTrigger.node.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
import type { | ||
IHookFunctions, | ||
IWebhookFunctions, | ||
INodeType, | ||
INodeTypeDescription, | ||
IWebhookResponseData, | ||
IHttpRequestOptions, | ||
} from 'n8n-workflow'; | ||
|
||
interface ICrowdDevCreds { | ||
url: string; | ||
tenantId: string; | ||
token: string; | ||
allowUnauthorizedCerts: boolean; | ||
} | ||
|
||
const credsName = 'crowdDevApi'; | ||
|
||
const getCreds = async (hookFns: IHookFunctions) => | ||
hookFns.getCredentials(credsName) as unknown as ICrowdDevCreds; | ||
|
||
const createRequest = ( | ||
creds: ICrowdDevCreds, | ||
opts: Partial<IHttpRequestOptions>, | ||
): IHttpRequestOptions => { | ||
const defaults: IHttpRequestOptions = { | ||
baseURL: `${creds.url}/api/tenant/${creds.tenantId}`, | ||
url: '', | ||
json: true, | ||
skipSslCertificateValidation: creds.allowUnauthorizedCerts, | ||
}; | ||
return Object.assign(defaults, opts); | ||
}; | ||
|
||
export class CrowdDevTrigger implements INodeType { | ||
description: INodeTypeDescription = { | ||
displayName: 'crowd.dev Trigger', | ||
name: 'crowdDevTrigger', | ||
icon: 'file:crowdDev.svg', | ||
group: ['trigger'], | ||
version: 1, | ||
description: 'Starts the workflow when crowd.dev events occur.', | ||
defaults: { | ||
name: 'crowd.dev Trigger', | ||
}, | ||
inputs: [], | ||
outputs: ['main'], | ||
credentials: [ | ||
{ | ||
name: 'crowdDevApi', | ||
required: true, | ||
}, | ||
], | ||
webhooks: [ | ||
{ | ||
name: 'default', | ||
httpMethod: 'POST', | ||
responseMode: 'onReceived', | ||
path: 'webhook', | ||
}, | ||
], | ||
properties: [ | ||
{ | ||
displayName: 'Trigger', | ||
name: 'trigger', | ||
description: 'What will trigger an automation', | ||
type: 'options', | ||
required: true, | ||
default: 'new_activity', | ||
options: [ | ||
{ | ||
name: 'New Activity', | ||
value: 'new_activity', | ||
}, | ||
{ | ||
name: 'New Member', | ||
value: 'new_member', | ||
}, | ||
], | ||
}, | ||
], | ||
}; | ||
|
||
webhookMethods = { | ||
default: { | ||
async checkExists(this: IHookFunctions): Promise<boolean> { | ||
const creds = await getCreds(this); | ||
const webhookData = this.getWorkflowStaticData('node'); | ||
const webhookUrl = this.getNodeWebhookUrl('default') as string; | ||
|
||
if (webhookData.webhookId !== undefined) { | ||
try { | ||
const options = createRequest(creds, { | ||
url: `/automation/${webhookData.webhookId}`, | ||
method: 'GET', | ||
}); | ||
const data = await this.helpers.httpRequestWithAuthentication.call( | ||
this, | ||
credsName, | ||
options, | ||
); | ||
if (data.settings.url === webhookUrl) { | ||
return true; | ||
} | ||
} catch (error) { | ||
return false; | ||
} | ||
} | ||
|
||
// If it did not error then the webhook exists | ||
return false; | ||
}, | ||
|
||
async create(this: IHookFunctions): Promise<boolean> { | ||
const creds = await getCreds(this); | ||
const webhookData = this.getWorkflowStaticData('node'); | ||
const webhookUrl = this.getNodeWebhookUrl('default') as string; | ||
const params = { | ||
trigger: this.getNodeParameter('trigger') as string, | ||
}; | ||
|
||
const options = createRequest(creds, { | ||
url: '/automation', | ||
method: 'POST', | ||
body: { | ||
data: { | ||
settings: { | ||
url: webhookUrl, | ||
}, | ||
type: 'webhook', | ||
trigger: params.trigger, | ||
}, | ||
}, | ||
}); | ||
|
||
const responseData = await this.helpers.httpRequestWithAuthentication.call( | ||
this, | ||
'crowdDevApi', | ||
options, | ||
); | ||
if (responseData === undefined || responseData.id === undefined) { | ||
// Required data is missing so was not successful | ||
return false; | ||
} | ||
|
||
webhookData.webhookId = responseData.id as string; | ||
|
||
return true; | ||
}, | ||
|
||
async delete(this: IHookFunctions): Promise<boolean> { | ||
const creds = await getCreds(this); | ||
const webhookData = this.getWorkflowStaticData('node'); | ||
|
||
if (webhookData.webhookId !== undefined) { | ||
try { | ||
const options = createRequest(creds, { | ||
url: `/automation/${webhookData.webhookId}`, | ||
method: 'DELETE', | ||
}); | ||
await this.helpers.httpRequestWithAuthentication.call(this, credsName, options); | ||
} catch (error) { | ||
return false; | ||
} | ||
|
||
// Remove from the static workflow data so that it is clear | ||
// that no webhooks are registered anymore | ||
delete webhookData.webhookId; | ||
delete webhookData.webhookEvents; | ||
delete webhookData.hookSecret; | ||
} | ||
|
||
return true; | ||
}, | ||
}, | ||
}; | ||
|
||
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> { | ||
const bodyData = this.getBodyData(); | ||
|
||
return { | ||
workflowData: [this.helpers.returnJsonArray(bodyData)], | ||
}; | ||
} | ||
} |
Oops, something went wrong.