Skip to content

Commit

Permalink
Merge branch 'master' into pay-1803-update-ownership-pills-on-workflo…
Browse files Browse the repository at this point in the history
…wcredential-cards-and
  • Loading branch information
r00gm committed Oct 28, 2024
2 parents e46d571 + 351134f commit 444a596
Show file tree
Hide file tree
Showing 42 changed files with 835 additions and 276 deletions.
23 changes: 22 additions & 1 deletion cypress/e2e/2-credentials.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
TRELLO_NODE_NAME,
} from '../constants';
import { CredentialsModal, CredentialsPage, NDV, WorkflowPage } from '../pages';
import { successToast } from '../pages/notifications';
import { errorToast, successToast } from '../pages/notifications';
import { getVisibleSelect } from '../utils';

const credentialsPage = new CredentialsPage();
Expand Down Expand Up @@ -278,4 +278,25 @@ describe('Credentials', () => {
credentialsModal.getters.credentialAuthTypeRadioButtons().first().click();
nodeDetailsView.getters.copyInput().should('not.exist');
});

it('ADO-2583 should show notifications above credential modal overlay', () => {
// check error notifications because they are sticky
cy.intercept('POST', '/rest/credentials', { forceNetworkError: true });
credentialsPage.getters.createCredentialButton().click();

credentialsModal.getters.newCredentialModal().should('be.visible');
credentialsModal.getters.newCredentialTypeSelect().should('be.visible');
credentialsModal.getters.newCredentialTypeOption('Notion API').click();

credentialsModal.getters.newCredentialTypeButton().click();
credentialsModal.getters.connectionParameter('Internal Integration Secret').type('1234567890');

credentialsModal.actions.setName('My awesome Notion account');
credentialsModal.getters.saveButton().click({ force: true });
errorToast().should('have.length', 1);
errorToast().should('be.visible');

errorToast().should('have.css', 'z-index', '2100');
cy.get('.el-overlay').should('have.css', 'z-index', '2001');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const insertFields: INodeProperties[] = [
},
];

export const VectorStoreInMemory = createVectorStoreNode({
export class VectorStoreInMemory extends createVectorStoreNode({
meta: {
displayName: 'In-Memory Vector Store',
name: 'vectorStoreInMemory',
Expand Down Expand Up @@ -56,4 +56,4 @@ export const VectorStoreInMemory = createVectorStoreNode({

void vectorStoreInstance.addDocuments(`${workflowId}__${memoryKey}`, documents, clearStore);
},
});
}) {}
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ class ExtendedPGVectorStore extends PGVectorStore {
}
}

export const VectorStorePGVector = createVectorStoreNode({
export class VectorStorePGVector extends createVectorStoreNode({
meta: {
description: 'Work with your data in Postgresql with the PGVector extension',
icon: 'file:postgres.svg',
Expand Down Expand Up @@ -308,4 +308,4 @@ export const VectorStorePGVector = createVectorStoreNode({

await PGVectorStore.fromDocuments(documents, embeddings, config);
},
});
}) {}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const insertFields: INodeProperties[] = [
},
];

export const VectorStorePinecone = createVectorStoreNode({
export class VectorStorePinecone extends createVectorStoreNode({
meta: {
displayName: 'Pinecone Vector Store',
name: 'vectorStorePinecone',
Expand Down Expand Up @@ -132,4 +132,4 @@ export const VectorStorePinecone = createVectorStoreNode({
pineconeIndex,
});
},
});
}) {}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const retrieveFields: INodeProperties[] = [
},
];

export const VectorStoreQdrant = createVectorStoreNode({
export class VectorStoreQdrant extends createVectorStoreNode({
meta: {
displayName: 'Qdrant Vector Store',
name: 'vectorStoreQdrant',
Expand Down Expand Up @@ -134,4 +134,4 @@ export const VectorStoreQdrant = createVectorStoreNode({

await QdrantVectorStore.fromDocuments(documents, embeddings, config);
},
});
}) {}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const retrieveFields: INodeProperties[] = [

const updateFields: INodeProperties[] = [...insertFields];

export const VectorStoreSupabase = createVectorStoreNode({
export class VectorStoreSupabase extends createVectorStoreNode({
meta: {
description: 'Work with your data in Supabase Vector Store',
icon: 'file:supabase.svg',
Expand Down Expand Up @@ -109,4 +109,4 @@ export const VectorStoreSupabase = createVectorStoreNode({
}
}
},
});
}) {}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const retrieveFields: INodeProperties[] = [
},
];

export const VectorStoreZep = createVectorStoreNode({
export class VectorStoreZep extends createVectorStoreNode({
meta: {
displayName: 'Zep Vector Store',
name: 'vectorStoreZep',
Expand Down Expand Up @@ -130,4 +130,4 @@ export const VectorStoreZep = createVectorStoreNode({
throw new NodeOperationError(context.getNode(), error as Error, { itemIndex });
}
},
});
}) {}
1 change: 1 addition & 0 deletions packages/cli/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ module.exports = {
],
coveragePathIgnorePatterns: ['/src/databases/migrations/'],
testTimeout: 10_000,
prettierPath: null,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { GlobalConfig } from '@n8n/config';
import type express from 'express';
import { mock } from 'jest-mock-extended';
import type { InstanceSettings } from 'n8n-core';
import promClient from 'prom-client';

import { EventMessageWorkflow } from '@/eventbus/event-message-classes/event-message-workflow';
import type { EventService } from '@/events/event.service';
import { mockInstance } from '@test/mocking';

import { MessageEventBus } from '../../eventbus/message-event-bus/message-event-bus';
import { PrometheusMetricsService } from '../prometheus-metrics.service';

jest.unmock('@/eventbus/message-event-bus/message-event-bus');

const customPrefix = 'custom_';

const eventService = mock<EventService>();
const instanceSettings = mock<InstanceSettings>({ instanceType: 'main' });
const app = mock<express.Application>();
const eventBus = new MessageEventBus(
mock(),
mock(),
mock(),
mock(),
mock(),
mock(),
mock(),
mock(),
);

describe('workflow_success_total', () => {
test('support workflow id labels', async () => {
// ARRANGE
const globalConfig = mockInstance(GlobalConfig, {
endpoints: {
metrics: {
prefix: '',
includeMessageEventBusMetrics: true,
includeWorkflowIdLabel: true,
},
},
});

const prometheusMetricsService = new PrometheusMetricsService(
mock(),
eventBus,
globalConfig,
eventService,
instanceSettings,
);

await prometheusMetricsService.init(app);

// ACT
const event = new EventMessageWorkflow({
eventName: 'n8n.workflow.success',
payload: { workflowId: '1234' },
});

eventBus.emit('metrics.eventBus.event', event);

// ASSERT
const workflowSuccessCounter =
await promClient.register.getSingleMetricAsString('workflow_success_total');

expect(workflowSuccessCounter).toMatchInlineSnapshot(`
"# HELP workflow_success_total Total number of n8n.workflow.success events.
# TYPE workflow_success_total counter
workflow_success_total{workflow_id="1234"} 1"
`);
});

test('support a custom prefix', async () => {
// ARRANGE
const globalConfig = mockInstance(GlobalConfig, {
endpoints: {
metrics: {
prefix: customPrefix,
},
},
});

const prometheusMetricsService = new PrometheusMetricsService(
mock(),
eventBus,
globalConfig,
eventService,
instanceSettings,
);

await prometheusMetricsService.init(app);

// ACT
const event = new EventMessageWorkflow({
eventName: 'n8n.workflow.success',
payload: { workflowId: '1234' },
});

eventBus.emit('metrics.eventBus.event', event);

// ASSERT
const versionInfoMetric = promClient.register.getSingleMetric(`${customPrefix}version_info`);

if (!versionInfoMetric) {
fail(`Could not find a metric called "${customPrefix}version_info"`);
}
});
});
5 changes: 3 additions & 2 deletions packages/cli/src/metrics/prometheus-metrics.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,6 @@ export class PrometheusMetricsService {
help: `Total number of ${eventName} events.`,
labelNames: Object.keys(labels),
});
counter.labels(labels).inc(0);
this.counters[eventName] = counter;
}

Expand All @@ -224,7 +223,9 @@ export class PrometheusMetricsService {
this.eventBus.on('metrics.eventBus.event', (event: EventMessageTypes) => {
const counter = this.toCounter(event);
if (!counter) return;
counter.inc(1);

const labels = this.toLabels(event);
counter.inc(labels, 1);
});
}

Expand Down
12 changes: 7 additions & 5 deletions packages/cli/src/runners/task-runner-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,13 @@ export class TaskRunnerServer {
const { app } = this;

// Augment errors sent to Sentry
const {
Handlers: { requestHandler, errorHandler },
} = await import('@sentry/node');
app.use(requestHandler());
app.use(errorHandler());
if (this.globalConfig.sentry.backendDsn) {
const {
Handlers: { requestHandler, errorHandler },
} = await import('@sentry/node');
app.use(requestHandler());
app.use(errorHandler());
}
}

private setupCommonMiddlewares() {
Expand Down
44 changes: 25 additions & 19 deletions packages/cli/src/webhooks/waiting-forms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,25 @@ export class WaitingForms extends WaitingWebhooks {
});
}

private async reloadForm(req: WaitingWebhookRequest, res: express.Response) {
try {
await sleep(1000);

const url = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
const page = await axios({ url });

if (page) {
res.send(`
<script>
setTimeout(function() {
window.location.reload();
}, 1);
</script>
`);
}
} catch (error) {}
}

async executeWebhook(
req: WaitingWebhookRequest,
res: express.Response,
Expand All @@ -56,30 +75,17 @@ export class WaitingForms extends WaitingWebhooks {
}

if (execution.data.resultData.error) {
throw new ConflictError(`The execution "${executionId}" has finished with error.`);
const message = `The execution "${executionId}" has finished with error.`;
this.logger.debug(message, { error: execution.data.resultData.error });
throw new ConflictError(message);
}

if (execution.status === 'running') {
if (this.includeForms && req.method === 'GET') {
await sleep(1000);

const url = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
const page = await axios({ url });

if (page) {
res.send(`
<script>
setTimeout(function() {
window.location.reload();
}, 1);
</script>
`);
}

return {
noWebhookResponse: true,
};
await this.reloadForm(req, res);
return { noWebhookResponse: true };
}

throw new ConflictError(`The execution "${executionId}" is running already.`);
}

Expand Down
16 changes: 10 additions & 6 deletions packages/cli/src/webhooks/waiting-webhooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ export class WaitingWebhooks implements IWebhookManager {
}

if (execution.data?.resultData?.error) {
throw new ConflictError(`The execution "${executionId} has finished already.`);
const message = `The execution "${executionId}" has finished with error.`;
this.logger.debug(message, { error: execution.data.resultData.error });
throw new ConflictError(message);
}

if (execution.finished) {
Expand Down Expand Up @@ -182,23 +184,25 @@ export class WaitingWebhooks implements IWebhookManager {
if (this.isSendAndWaitRequest(workflow.nodes, suffix)) {
res.render('send-and-wait-no-action-required', { isTestWebhook: false });
return { noWebhookResponse: true };
} else if (!execution.data.resultData.error && execution.status === 'waiting') {
}

if (!execution.data.resultData.error && execution.status === 'waiting') {
const childNodes = workflow.getChildNodes(
execution.data.resultData.lastNodeExecuted as string,
);

const hasChildForms = childNodes.some(
(node) =>
workflow.nodes[node].type === FORM_NODE_TYPE ||
workflow.nodes[node].type === WAIT_NODE_TYPE,
);

if (hasChildForms) {
return { noWebhookResponse: true };
} else {
throw new NotFoundError(errorMessage);
}
} else {
throw new NotFoundError(errorMessage);
}

throw new NotFoundError(errorMessage);
}

const runExecutionData = execution.data;
Expand Down
Loading

0 comments on commit 444a596

Please sign in to comment.