first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.graphql",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Data & Storage", "Development"],
|
||||
"resources": {
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.graphql/"
|
||||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "What are APIs and how to use them with no code",
|
||||
"icon": " 🪢",
|
||||
"url": "https://n8n.io/blog/what-are-apis-how-to-use-them-with-no-code/"
|
||||
},
|
||||
{
|
||||
"label": "How to automatically give kudos to contributors with GitHub, Slack, and n8n",
|
||||
"icon": "👏",
|
||||
"url": "https://n8n.io/blog/how-to-automatically-give-kudos-to-contributors-with-github-slack-and-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "How Goomer automated their operations with over 200 n8n workflows",
|
||||
"icon": "🛵",
|
||||
"url": "https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,576 @@
|
||||
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
JsonObject,
|
||||
IRequestOptionsSimplified,
|
||||
IRequestOptions,
|
||||
IHttpRequestMethods,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError, NodeConnectionTypes, NodeOperationError, jsonParse } from 'n8n-workflow';
|
||||
|
||||
export class GraphQL implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'GraphQL',
|
||||
name: 'graphql',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-class-description-icon-not-svg
|
||||
icon: 'file:graphql.png',
|
||||
group: ['input'],
|
||||
version: [1, 1.1],
|
||||
description: 'Makes a GraphQL request and returns the received data',
|
||||
defaults: {
|
||||
name: 'GraphQL',
|
||||
},
|
||||
usableAsTool: true,
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
credentials: [
|
||||
{
|
||||
name: 'httpBasicAuth',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['basicAuth'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'httpCustomAuth',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['customAuth'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'httpDigestAuth',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['digestAuth'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'httpHeaderAuth',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['headerAuth'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'httpQueryAuth',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['queryAuth'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'oAuth1Api',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['oAuth1'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'oAuth2Api',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['oAuth2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Basic Auth',
|
||||
value: 'basicAuth',
|
||||
},
|
||||
{
|
||||
name: 'Custom Auth',
|
||||
value: 'customAuth',
|
||||
},
|
||||
{
|
||||
name: 'Digest Auth',
|
||||
value: 'digestAuth',
|
||||
},
|
||||
{
|
||||
name: 'Header Auth',
|
||||
value: 'headerAuth',
|
||||
},
|
||||
{
|
||||
name: 'None',
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
name: 'OAuth1',
|
||||
value: 'oAuth1',
|
||||
},
|
||||
{
|
||||
name: 'OAuth2',
|
||||
value: 'oAuth2',
|
||||
},
|
||||
{
|
||||
name: 'Query Auth',
|
||||
value: 'queryAuth',
|
||||
},
|
||||
],
|
||||
default: 'none',
|
||||
description: 'The way to authenticate',
|
||||
},
|
||||
{
|
||||
displayName: 'HTTP Request Method',
|
||||
name: 'requestMethod',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'GET',
|
||||
value: 'GET',
|
||||
},
|
||||
{
|
||||
name: 'POST',
|
||||
value: 'POST',
|
||||
},
|
||||
],
|
||||
default: 'POST',
|
||||
description: 'The underlying HTTP request method to use',
|
||||
},
|
||||
{
|
||||
displayName: 'Endpoint',
|
||||
name: 'endpoint',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'http://example.com/graphql',
|
||||
description: 'The GraphQL endpoint',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Ignore SSL Issues (Insecure)',
|
||||
name: 'allowUnauthorizedCerts',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-ignore-ssl-issues
|
||||
description:
|
||||
'Whether to download the response even if SSL certificate validation is not possible',
|
||||
},
|
||||
{
|
||||
displayName: 'Request Format',
|
||||
name: 'requestFormat',
|
||||
type: 'options',
|
||||
required: true,
|
||||
options: [
|
||||
{
|
||||
name: 'GraphQL (Raw)',
|
||||
value: 'graphql',
|
||||
},
|
||||
{
|
||||
name: 'JSON',
|
||||
value: 'json',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
requestMethod: ['POST'],
|
||||
'@version': [1],
|
||||
},
|
||||
},
|
||||
default: 'graphql',
|
||||
description: 'The format for the query payload',
|
||||
},
|
||||
{
|
||||
displayName: 'Request Format',
|
||||
name: 'requestFormat',
|
||||
type: 'options',
|
||||
required: true,
|
||||
options: [
|
||||
{
|
||||
name: 'JSON (Recommended)',
|
||||
value: 'json',
|
||||
description:
|
||||
'JSON object with query, variables, and operationName properties. The standard and most widely supported format for GraphQL requests.',
|
||||
},
|
||||
{
|
||||
name: 'GraphQL (Raw)',
|
||||
value: 'graphql',
|
||||
description:
|
||||
'Raw GraphQL query string. Not all servers support this format. Use JSON for better compatibility.',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
requestMethod: ['POST'],
|
||||
'@version': [{ _cnd: { gte: 1.1 } }],
|
||||
},
|
||||
},
|
||||
default: 'json',
|
||||
description: 'The request format for the query payload',
|
||||
},
|
||||
{
|
||||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'GraphQL query',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
rows: 6,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Variables',
|
||||
name: 'variables',
|
||||
type: 'json',
|
||||
default: '',
|
||||
description: 'Query variables as JSON object',
|
||||
displayOptions: {
|
||||
show: {
|
||||
requestFormat: ['json'],
|
||||
requestMethod: ['POST'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Operation Name',
|
||||
name: 'operationName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of operation to execute',
|
||||
displayOptions: {
|
||||
show: {
|
||||
requestFormat: ['json'],
|
||||
requestMethod: ['POST'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Response Format',
|
||||
name: 'responseFormat',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'JSON',
|
||||
value: 'json',
|
||||
},
|
||||
{
|
||||
name: 'String',
|
||||
value: 'string',
|
||||
},
|
||||
],
|
||||
default: 'json',
|
||||
description: 'The format in which the data gets returned from the URL',
|
||||
},
|
||||
{
|
||||
displayName: 'Response Data Property Name',
|
||||
name: 'dataPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
responseFormat: ['string'],
|
||||
},
|
||||
},
|
||||
description: 'Name of the property to which to write the response data',
|
||||
},
|
||||
|
||||
// Header Parameters
|
||||
{
|
||||
displayName: 'Headers',
|
||||
name: 'headerParametersUi',
|
||||
placeholder: 'Add Header',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
description: 'The headers to send',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'parameter',
|
||||
displayName: 'Header',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of the header',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Value to set for the header',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
let httpBasicAuth;
|
||||
let httpDigestAuth;
|
||||
let httpCustomAuth;
|
||||
let httpHeaderAuth;
|
||||
let httpQueryAuth;
|
||||
let oAuth1Api;
|
||||
let oAuth2Api;
|
||||
|
||||
try {
|
||||
httpBasicAuth = await this.getCredentials('httpBasicAuth');
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
try {
|
||||
httpCustomAuth = await this.getCredentials('httpCustomAuth');
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
try {
|
||||
httpDigestAuth = await this.getCredentials('httpDigestAuth');
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
try {
|
||||
httpHeaderAuth = await this.getCredentials('httpHeaderAuth');
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
try {
|
||||
httpQueryAuth = await this.getCredentials('httpQueryAuth');
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
try {
|
||||
oAuth1Api = await this.getCredentials('oAuth1Api');
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
try {
|
||||
oAuth2Api = await this.getCredentials('oAuth2Api');
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
let requestOptions: IRequestOptions;
|
||||
|
||||
const returnItems: INodeExecutionData[] = [];
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
try {
|
||||
const requestMethod = this.getNodeParameter(
|
||||
'requestMethod',
|
||||
itemIndex,
|
||||
'POST',
|
||||
) as IHttpRequestMethods;
|
||||
const endpoint = this.getNodeParameter('endpoint', itemIndex, '') as string;
|
||||
const requestFormat = this.getNodeParameter('requestFormat', itemIndex, 'json') as string;
|
||||
const responseFormat = this.getNodeParameter('responseFormat', 0) as string;
|
||||
const { parameter }: { parameter?: Array<{ name: string; value: string }> } =
|
||||
this.getNodeParameter('headerParametersUi', itemIndex, {}) as IDataObject;
|
||||
const headerParameters = (parameter || []).reduce(
|
||||
(result, item) => ({
|
||||
...result,
|
||||
[item.name]: item.value,
|
||||
}),
|
||||
{},
|
||||
);
|
||||
|
||||
requestOptions = {
|
||||
headers: {
|
||||
'content-type': `application/${requestFormat}`,
|
||||
...headerParameters,
|
||||
},
|
||||
method: requestMethod,
|
||||
uri: endpoint,
|
||||
simple: false,
|
||||
rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false),
|
||||
};
|
||||
|
||||
// Add credentials if any are set
|
||||
if (httpBasicAuth !== undefined) {
|
||||
requestOptions.auth = {
|
||||
user: httpBasicAuth.user as string,
|
||||
pass: httpBasicAuth.password as string,
|
||||
};
|
||||
}
|
||||
if (httpCustomAuth !== undefined) {
|
||||
const customAuth = jsonParse<IRequestOptionsSimplified>(
|
||||
(httpCustomAuth.json as string) || '{}',
|
||||
{ errorMessage: 'Invalid Custom Auth JSON' },
|
||||
);
|
||||
if (customAuth.headers) {
|
||||
requestOptions.headers = { ...requestOptions.headers, ...customAuth.headers };
|
||||
}
|
||||
if (customAuth.body) {
|
||||
requestOptions.body = { ...requestOptions.body, ...customAuth.body };
|
||||
}
|
||||
if (customAuth.qs) {
|
||||
requestOptions.qs = { ...requestOptions.qs, ...customAuth.qs };
|
||||
}
|
||||
}
|
||||
if (httpHeaderAuth !== undefined) {
|
||||
requestOptions.headers![httpHeaderAuth.name as string] = httpHeaderAuth.value;
|
||||
}
|
||||
if (httpQueryAuth !== undefined) {
|
||||
if (!requestOptions.qs) {
|
||||
requestOptions.qs = {};
|
||||
}
|
||||
requestOptions.qs[httpQueryAuth.name as string] = httpQueryAuth.value;
|
||||
}
|
||||
if (httpDigestAuth !== undefined) {
|
||||
requestOptions.auth = {
|
||||
user: httpDigestAuth.user as string,
|
||||
pass: httpDigestAuth.password as string,
|
||||
sendImmediately: false,
|
||||
};
|
||||
}
|
||||
|
||||
const gqlQuery = this.getNodeParameter('query', itemIndex, '') as string;
|
||||
if (requestMethod === 'GET') {
|
||||
requestOptions.qs = requestOptions.qs ?? {};
|
||||
requestOptions.qs.query = gqlQuery;
|
||||
}
|
||||
|
||||
if (requestFormat === 'json') {
|
||||
const variables = this.getNodeParameter('variables', itemIndex, {});
|
||||
|
||||
let parsedVariables;
|
||||
if (typeof variables === 'string') {
|
||||
try {
|
||||
parsedVariables = JSON.parse(variables || '{}');
|
||||
} catch (error) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`Using variables failed:\n${variables}\n\nWith error message:\n${error}`,
|
||||
{ itemIndex },
|
||||
);
|
||||
}
|
||||
} else if (typeof variables === 'object' && variables !== null) {
|
||||
parsedVariables = variables;
|
||||
} else {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`Using variables failed:\n${variables}\n\nGraphQL variables should be either an object or a string.`,
|
||||
{ itemIndex },
|
||||
);
|
||||
}
|
||||
|
||||
const jsonBody = {
|
||||
...requestOptions.body,
|
||||
query: gqlQuery,
|
||||
variables: parsedVariables,
|
||||
operationName: this.getNodeParameter('operationName', itemIndex, '') as string,
|
||||
};
|
||||
|
||||
if (jsonBody.operationName === '') {
|
||||
jsonBody.operationName = null;
|
||||
}
|
||||
|
||||
requestOptions.json = true;
|
||||
requestOptions.body = jsonBody;
|
||||
} else {
|
||||
requestOptions.body = gqlQuery;
|
||||
}
|
||||
|
||||
let response;
|
||||
// Now that the options are all set make the actual http request
|
||||
if (oAuth1Api !== undefined) {
|
||||
response = await this.helpers.requestOAuth1.call(this, 'oAuth1Api', requestOptions);
|
||||
} else if (oAuth2Api !== undefined) {
|
||||
response = await this.helpers.requestOAuth2.call(
|
||||
this,
|
||||
'oAuth2Api',
|
||||
{
|
||||
...requestOptions,
|
||||
// needed for the refresh mechanism to work properly
|
||||
resolveWithFullResponse: true,
|
||||
},
|
||||
{
|
||||
tokenType: 'Bearer',
|
||||
},
|
||||
);
|
||||
// since we are using `resolveWithFullResponse: true`, we need to grab the body
|
||||
response = response.body;
|
||||
} else {
|
||||
response = await this.helpers.request(requestOptions);
|
||||
}
|
||||
if (responseFormat === 'string') {
|
||||
const dataPropertyName = this.getNodeParameter('dataPropertyName', 0);
|
||||
returnItems.push({
|
||||
json: {
|
||||
[dataPropertyName]: response,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
if (typeof response === 'string') {
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
} catch (error) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'Response body is not valid JSON. Change "Response Format" to "String"',
|
||||
{ itemIndex },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(response as IDataObject),
|
||||
{ itemData: { item: itemIndex } },
|
||||
);
|
||||
returnItems.push(...executionData);
|
||||
}
|
||||
|
||||
// parse error string messages
|
||||
if (typeof response === 'string' && response.startsWith('{"errors":')) {
|
||||
try {
|
||||
const errorResponse = JSON.parse(response) as IDataObject;
|
||||
if (Array.isArray(errorResponse.errors)) {
|
||||
response = errorResponse;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
// throw from response object.errors[]
|
||||
if (typeof response === 'object' && response.errors) {
|
||||
const message =
|
||||
response.errors?.map((error: IDataObject) => error.message).join(', ') ||
|
||||
'Unexpected error';
|
||||
throw new NodeApiError(this.getNode(), response.errors as JsonObject, { message });
|
||||
}
|
||||
} catch (error) {
|
||||
if (!this.continueOnFail()) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const errorData = this.helpers.returnJsonArray({
|
||||
error: error.message,
|
||||
});
|
||||
const exectionErrorWithMetaData = this.helpers.constructExecutionMetaData(errorData, {
|
||||
itemData: { item: itemIndex },
|
||||
});
|
||||
returnItems.push(...exectionErrorWithMetaData);
|
||||
}
|
||||
}
|
||||
return [returnItems];
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 979 B |
@@ -0,0 +1,113 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('GraphQL Node', () => {
|
||||
describe('valid request', () => {
|
||||
const baseUrl = 'https://api.n8n.io/';
|
||||
nock(baseUrl)
|
||||
.matchHeader('accept', 'application/json')
|
||||
.matchHeader('content-type', 'application/json')
|
||||
.matchHeader('content-length', '263')
|
||||
.matchHeader('accept-encoding', 'gzip, compress, deflate, br')
|
||||
.post(
|
||||
'/graphql',
|
||||
'{"query":"query {\\n nodes(pagination: { limit: 1 }) {\\n data {\\n id\\n attributes {\\n name\\n displayName\\n description\\n group\\n codex\\n createdAt\\n }\\n }\\n }\\n}","variables":{},"operationName":null}',
|
||||
)
|
||||
.reply(200, {
|
||||
data: {
|
||||
nodes: {
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
attributes: {
|
||||
name: 'n8n-nodes-base.activeCampaign',
|
||||
displayName: 'ActiveCampaign',
|
||||
description: 'Create and edit data in ActiveCampaign',
|
||||
group: '["transform"]',
|
||||
|
||||
codex: {
|
||||
data: {
|
||||
details:
|
||||
'ActiveCampaign is a cloud software platform that allows customer experience automation, which combines email marketing, marketing automation, sales automation, and CRM categories. Use this node when you want to interact with your ActiveCampaign account.',
|
||||
resources: {
|
||||
primaryDocumentation: [
|
||||
{
|
||||
url: 'https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.activecampaign/',
|
||||
},
|
||||
],
|
||||
credentialDocumentation: [
|
||||
{
|
||||
url: 'https://docs.n8n.io/integrations/builtin/credentials/activecampaign/',
|
||||
},
|
||||
],
|
||||
},
|
||||
categories: ['Marketing'],
|
||||
nodeVersion: '1.0',
|
||||
codexVersion: '1.0',
|
||||
},
|
||||
},
|
||||
createdAt: '2019-08-30T22:54:39.934Z',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['workflow.json'],
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid expression', () => {
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['workflow.error_invalid_expression.json'],
|
||||
});
|
||||
});
|
||||
|
||||
describe('oauth2 refresh token', () => {
|
||||
const credentials = {
|
||||
oAuth2Api: {
|
||||
scope: '',
|
||||
accessTokenUrl: 'http://test/token',
|
||||
clientId: 'dummy_client_id',
|
||||
clientSecret: 'dummy_client_secret',
|
||||
oauthTokenData: {
|
||||
access_token: 'dummy_access_token',
|
||||
refresh_token: 'dummy_refresh_token',
|
||||
},
|
||||
},
|
||||
};
|
||||
const baseUrl = 'http://test';
|
||||
nock(baseUrl)
|
||||
.post('/graphql', '{"query":"query { foo }","variables":{},"operationName":null}')
|
||||
.reply(401, {
|
||||
errors: [
|
||||
{
|
||||
message: 'Unauthorized',
|
||||
},
|
||||
],
|
||||
});
|
||||
nock(baseUrl)
|
||||
.post('/token', {
|
||||
refresh_token: 'dummy_refresh_token',
|
||||
grant_type: 'refresh_token',
|
||||
})
|
||||
.reply(200, {
|
||||
access_token: 'dummy_access_token',
|
||||
refresh_token: 'dummy_refresh_token',
|
||||
expires_in: 3600,
|
||||
});
|
||||
nock(baseUrl)
|
||||
.post('/graphql', '{"query":"query { foo }","variables":{},"operationName":null}')
|
||||
.reply(200, {
|
||||
data: {
|
||||
foo: 'bar',
|
||||
},
|
||||
});
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['workflow.refresh_token.json'],
|
||||
credentials,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"meta": {
|
||||
"templateId": "216",
|
||||
"instanceId": "ee90fdf8d57662f949e6c691dc07fa0fd2f66e1eee28ed82ef06658223e67255"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"endpoint": "https://graphql-teas-endpoint.netlify.app/",
|
||||
"requestFormat": "json",
|
||||
"query": "query getAllTeas($name: String) {\n teas(name: $name) {\n name,\n id\n }\n}",
|
||||
"variables": "={{ 1 }}"
|
||||
},
|
||||
"id": "7aece03f-e0d9-4f49-832c-fc6465613ca7",
|
||||
"name": "Test: Errors on unsuccessful Expression validation",
|
||||
"type": "n8n-nodes-base.graphql",
|
||||
"typeVersion": 1,
|
||||
"position": [660, 200],
|
||||
"onError": "continueRegularOutput"
|
||||
}
|
||||
],
|
||||
"connections": {},
|
||||
"pinData": {
|
||||
"Test: Errors on unsuccessful Expression validation": [
|
||||
{
|
||||
"json": {
|
||||
"error": "Using variables failed:\n1\n\nGraphQL variables should be either an object or a string."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"meta": {
|
||||
"templateId": "216",
|
||||
"instanceId": "ee90fdf8d57662f949e6c691dc07fa0fd2f66e1eee28ed82ef06658223e67255"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "5e2ef15b-2c6c-412f-a9da-515b5211386e",
|
||||
"name": "When clicking ‘Execute workflow’",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [420, 100]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"endpoint": "https://api.n8n.io/graphql",
|
||||
"requestFormat": "json",
|
||||
"query": "query {\n nodes(pagination: { limit: 1 }) {\n data {\n id\n attributes {\n name\n displayName\n description\n group\n codex\n createdAt\n }\n }\n }\n}"
|
||||
},
|
||||
"name": "Fetch Request Format JSON",
|
||||
"type": "n8n-nodes-base.graphql",
|
||||
"typeVersion": 1,
|
||||
"position": [700, 140],
|
||||
"id": "e1c750a0-8d6c-4e81-8111-3218e1e6e69f"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Fetch Request Format JSON",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"Fetch Request Format JSON": [
|
||||
{
|
||||
"json": {
|
||||
"data": {
|
||||
"nodes": {
|
||||
"data": [
|
||||
{
|
||||
"id": "1",
|
||||
"attributes": {
|
||||
"name": "n8n-nodes-base.activeCampaign",
|
||||
"displayName": "ActiveCampaign",
|
||||
"description": "Create and edit data in ActiveCampaign",
|
||||
"group": "[\"transform\"]",
|
||||
"codex": {
|
||||
"data": {
|
||||
"details": "ActiveCampaign is a cloud software platform that allows customer experience automation, which combines email marketing, marketing automation, sales automation, and CRM categories. Use this node when you want to interact with your ActiveCampaign account.",
|
||||
"resources": {
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.activecampaign/"
|
||||
}
|
||||
],
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/activecampaign/"
|
||||
}
|
||||
]
|
||||
},
|
||||
"categories": ["Marketing"],
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0"
|
||||
}
|
||||
},
|
||||
"createdAt": "2019-08-30T22:54:39.934Z"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"name": "GraphQL Refersh Token",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "ae67b437-48c0-4c4b-9c8f-005b8911ca5f",
|
||||
"name": "When clicking ‘Execute workflow’",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [32, 80]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "oAuth2",
|
||||
"endpoint": "http://test/graphql",
|
||||
"requestFormat": "json",
|
||||
"query": "query { foo }"
|
||||
},
|
||||
"name": "GraphQL",
|
||||
"type": "n8n-nodes-base.graphql",
|
||||
"typeVersion": 1,
|
||||
"position": [256, 80],
|
||||
"id": "6cb4c117-907a-40b9-b3ba-cd3756b1cb7b",
|
||||
"credentials": {
|
||||
"oAuth2Api": {
|
||||
"id": "PpmTbnw41Q2nqqoW",
|
||||
"name": "Dummy (local)"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"GraphQL": [
|
||||
{
|
||||
"json": {
|
||||
"data": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "GraphQL",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "659c76f4-ac29-4c2f-bf40-70b476afbc1d",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "eeda9e3069aca300d1dfceeb64beb5b53d715db44a50461bbc5cb0cf6daa01e3"
|
||||
},
|
||||
"id": "O4IzQ2D7h7cfZtB4",
|
||||
"tags": []
|
||||
}
|
||||
Reference in New Issue
Block a user