Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(echo): add skip support #5619

Merged
merged 37 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
90326d4
feat: add skip support
djabarovgeorge May 21, 2024
7d52c53
Merge branch 'refs/heads/next' into nv-3775-support-skip-functionality
djabarovgeorge May 22, 2024
7ab4eb3
fix: after next merge
djabarovgeorge May 22, 2024
3d151a0
Merge branch 'next' into nv-3775-support-skip-functionality
djabarovgeorge May 27, 2024
31368d8
fix(worker): remove tenant repository in tests
djabarovgeorge May 28, 2024
5ccbd33
Merge branch 'next' into nv-3775-support-skip-functionality
djabarovgeorge May 28, 2024
df7cad4
Merge branch 'refs/heads/next' into nv-3775-support-skip-functionality
djabarovgeorge May 30, 2024
7244cd7
feat: refactor after next merge
djabarovgeorge Jun 1, 2024
3385cbf
Merge branch 'refs/heads/next' into nv-3775-support-skip-functionality
djabarovgeorge Jun 1, 2024
67b4cd3
feat: update after pr review
djabarovgeorge Jun 1, 2024
b481e3e
fix(api): remove faker dep
djabarovgeorge Jun 2, 2024
4440d0d
fix(api): faker import
djabarovgeorge Jun 2, 2024
cedea99
refactor(api,echo-api): rename chimera url to url
djabarovgeorge Jun 2, 2024
8eed227
feat: update submodule hash
djabarovgeorge Jun 2, 2024
9a0bdfb
feat: update submodule hash
djabarovgeorge Jun 2, 2024
b8e60cc
refactor(app-gen,echo-sdk): update after pr comments
djabarovgeorge Jun 2, 2024
5c6d772
Merge branch 'refs/heads/next' into nv-3775-support-skip-functionality
djabarovgeorge Jun 4, 2024
008c5f1
revert(echo): change skip to be function again
djabarovgeorge Jun 4, 2024
9def7a6
Merge branch 'next' into nv-3775-support-skip-functionality
djabarovgeorge Jun 4, 2024
24eed65
feat: next merge
djabarovgeorge Jun 9, 2024
6dedaa5
chore(echo): update submodule hash
djabarovgeorge Jun 9, 2024
e9ea4ec
feat(echo): refactor after pr comments
djabarovgeorge Jun 9, 2024
8f74e11
feat: update hash
djabarovgeorge Jun 10, 2024
ac9e4c0
fix(cli-next): sync request
djabarovgeorge Jun 10, 2024
35b08ca
feat: update hash
djabarovgeorge Jun 10, 2024
4040715
fix(echo): add skip inputs validation compilation
djabarovgeorge Jun 10, 2024
0a84b43
Merge remote-tracking branch 'refs/remotes/origin/next' into nv-3775-…
djabarovgeorge Jun 10, 2024
e25e1d7
refactor(echo): after pr comments
djabarovgeorge Jun 11, 2024
f080283
Merge branch 'next' into nv-3775-support-skip-functionality
djabarovgeorge Jun 11, 2024
726f056
fix: package name
scopsy Jun 12, 2024
7fb9bcf
fix: ee source
scopsy Jun 12, 2024
8d44de1
fix: still show preview for skipped steps
scopsy Jun 12, 2024
01bc40b
tests: add test for preview mode
scopsy Jun 12, 2024
b46bae5
Merge branch 'next' into nv-3775-support-skip-functionality
scopsy Jun 12, 2024
8d15403
Merge branch 'next' into nv-3775-support-skip-functionality
scopsy Jun 12, 2024
0969f9c
fix: skip tests
scopsy Jun 12, 2024
c152548
fix: resolve app
scopsy Jun 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .source
122 changes: 122 additions & 0 deletions apps/api/e2e/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//# cSpell:disable

export const fakerUtil = {
phoneNumber: () =>
`(${Math.floor(100 * Math.random()) + 100}) ${Math.floor(100 * Math.random()) + 100}-${
Math.floor(10000 * Math.random()) + 10000
}`,
firstName: () => `John-${new Date().toISOString()}`,
lastName: () => `Smith-${new Date().toISOString()}`,
email: () => `Doe-Smith-${new Date().toISOString()}@gmail.com`,
words: generateWords(),
url: generateUrl(),
jobTitle: jobTitle(),
};

function generateWords() {
return (length: number | undefined) => {
return Array.from({ length: length ?? 3 }, () => loremWords[Math.floor(Math.random() * loremWords.length)]).join(
' '
);
};
}

function jobTitle() {
return () => {
const prefixes = ['Senior', 'Lead', 'Junior', 'Assistant', 'Chief', 'Head'];
const roles = ['Engineer', 'Developer', 'Manager', 'Analyst', 'Consultant', 'Specialist'];
const domains = ['Software', 'Hardware', 'Network', 'Security', 'Data', 'Cloud'];

const prefix = prefixes[Math.floor(Math.random() * prefixes.length)];
const role = roles[Math.floor(Math.random() * roles.length)];
const domain = domains[Math.floor(Math.random() * domains.length)];

return `${prefix} ${domain} ${role}`;
};
}

function generateUrl() {
return () => {
const protocols = ['http', 'https'];
const domains = ['example.com', 'test.com', 'site.com'];
const paths = ['path', 'to', 'resource'];

const protocol = protocols[Math.floor(Math.random() * protocols.length)];
const domain = domains[Math.floor(Math.random() * domains.length)];
const path = Array.from({ length: 3 }, () => paths[Math.floor(Math.random() * paths.length)]).join('/');

return `${protocol}://${domain}/${path}`;
};
}

const loremWords = [
'Lorem',
'ipsum',
'dolor',
'sit',
'amet',
'consectetur',
'adipiscing',
'elit',
'sed',
'do',
'eiusmod',
'tempor',
'incididunt',
'ut',
'labore',
'et',
'dolore',
'magna',
'aliqua',
'Ut',
'enim',
'ad',
'minim',
'veniam',
'quis',
'nostrud',
'exercitation',
'ullamco',
'laboris',
'nisi',
'ut',
'aliquip',
'ex',
'ea',
'commodo',
'consequat',
'Duis',
'aute',
'irure',
'dolor',
'in',
'reprehenderit',
'in',
'voluptate',
'velit',
'esse',
'cillum',
'dolore',
'eu',
'fugiat',
'nulla',
'pariatur',
'Excepteur',
'sint',
'occaecat',
'cupidatat',
'non',
'proident',
'sunt',
'in',
'culpa',
'qui',
'officia',
'deserunt',
'mollit',
'anim',
'id',
'est',
'laborum',
];
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { expect } from 'chai';
import { faker } from '@faker-js/faker';
import { createHash } from 'crypto';

import { UserSession } from '@novu/testing';
Expand All @@ -8,6 +7,7 @@ import { EnvironmentRepository } from '@novu/dal';
import { decryptApiKey } from '@novu/application-generic';

import { encryptApiKeysMigration } from './encrypt-api-keys-migration';
import { fakerUtil } from '../../e2e/utils';

async function pruneIntegration({ environmentRepository }: { environmentRepository: EnvironmentRepository }) {
const old = await environmentRepository.find({});
Expand All @@ -32,7 +32,7 @@ describe('Encrypt Old api keys', function () {
for (let i = 0; i < 2; i++) {
await environmentRepository.create({
identifier: 'identifier' + i,
name: faker.name.jobTitle(),
name: fakerUtil.jobTitle(),
_organizationId: session.organization._id,
apiKeys: [
{
Expand Down Expand Up @@ -85,7 +85,7 @@ describe('Encrypt Old api keys', function () {
for (let i = 0; i < 2; i++) {
await environmentRepository.create({
identifier: 'identifier' + i,
name: faker.name.jobTitle(),
name: fakerUtil.jobTitle(),
_organizationId: session.organization._id,
apiKeys: [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { expect } from 'chai';
import { faker } from '@faker-js/faker';

import { UserSession } from '@novu/testing';
import { MessageRepository, MessageTemplateRepository } from '@novu/dal';
import { StepTypeEnum } from '@novu/shared';

import { normalizeMessageTemplateCtaAction } from './normalize-message-template-cta-action-migration';
import { normalizeMessageCtaAction } from './normalize-message-cta-action-migration';
import { fakerUtil } from '../../e2e/utils';

describe('Normalize cta action', function () {
let session: UserSession;
Expand All @@ -29,8 +29,8 @@ describe('Normalize cta action', function () {
action: {
buttons: [
{
title: faker.lorem.words(3),
url: faker.internet.url(),
title: fakerUtil.words(3),
url: fakerUtil.url(),
},
],
},
Expand Down Expand Up @@ -70,8 +70,8 @@ describe('Normalize cta action', function () {
action: {
buttons: [
{
title: faker.lorem.words(3),
url: faker.internet.url(),
title: fakerUtil.words(3),
url: fakerUtil.url(),
},
],
},
Expand Down
136 changes: 109 additions & 27 deletions apps/api/src/app/events/e2e/echo-trigger.e2e-ee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,22 @@ import {
MessageRepository,
SubscriberEntity,
NotificationTemplateRepository,
JobRepository,
ExecutionDetailsRepository,
} from '@novu/dal';
import { ExecutionDetailsStatusEnum, MarkMessagesAsEnum, StepTypeEnum } from '@novu/shared';
import { ExecutionDetailsStatusEnum, JobStatusEnum, MarkMessagesAsEnum, StepTypeEnum } from '@novu/shared';
import { echoServer } from '../../../../e2e/echo.server';

const eventTriggerPath = '/v1/events/trigger';

describe('Echo Trigger ', async () => {
let session: UserSession;
const messageRepository = new MessageRepository();
const workflowsRepository = new NotificationTemplateRepository();
const executionDetailsRepository = new ExecutionDetailsRepository();

const jobRepository = new JobRepository();
let subscriber: SubscriberEntity;
let subscriberService: SubscribersService;

const triggerEvent = async (workflowId: string, payload): Promise<void> => {
await axios.post(
`${session.serverUrl}/v1/events/trigger`,
{
name: workflowId,
to: [subscriber.subscriberId],
payload,
},
{
headers: {
authorization: `ApiKey ${session.apiKey}`,
},
}
);
};
const executionDetailsRepository = new ExecutionDetailsRepository();

beforeEach(async () => {
session = new UserSession();
Expand Down Expand Up @@ -86,8 +73,7 @@ describe('Echo Trigger ', async () => {
throw new Error('Workflow not found');
}

await triggerEvent(workflowId, { name: 'test_name' });

await triggerEvent(session, workflowId, subscriber, { name: 'test_name' });
await session.awaitRunningJobs(workflow._id);

const messagesAfter = await messageRepository.find({
Expand All @@ -100,6 +86,70 @@ describe('Echo Trigger ', async () => {
expect(messagesAfter[0].subject).to.include('This is an email subject TEST');
});

it('should skip step', async () => {
const workflowId = 'hello-world-2';
await echoServer.echo.workflow(
workflowId,
async ({ step, payload }) => {
await step.email(
'send-email',
async (inputs) => {
return {
subject: 'This is an email subject ' + inputs.name,
body: 'Body result ' + payload.name,
};
},
{
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', default: 'TEST' },
},
} as const,
skip: true,
}
);
},
{
payloadSchema: {
type: 'object',
properties: {
name: { type: 'string', default: 'default_name' },
},
required: [],
additionalProperties: false,
} as const,
}
);

await syncWorkflow(session);

const workflow = await workflowsRepository.findByTriggerIdentifier(session.environment._id, workflowId);

expect(workflow).to.be.ok;
if (!workflow) throw new Error('Workflow not found');

await triggerEvent(session, workflowId, subscriber);
await session.awaitRunningJobs(workflow._id);

const executedMessage = await messageRepository.find({
_environmentId: session.environment._id,
_subscriberId: subscriber._id,
channel: StepTypeEnum.EMAIL,
});

expect(executedMessage.length).to.be.eq(0);

const executedMessage2 = await jobRepository.find({
_environmentId: session.environment._id,
_subscriberId: subscriber._id,
type: StepTypeEnum.EMAIL,
});

expect(executedMessage2.length).to.be.eq(1);
expect(executedMessage2[0].status).to.be.eq(JobStatusEnum.CANCELED);
});

it('should have execution detail errors for invalid trigger payload', async () => {
const workflowId = 'missing-payload-var';
await echoServer.echo.workflow(
Expand Down Expand Up @@ -133,7 +183,7 @@ describe('Echo Trigger ', async () => {
throw new Error('Workflow not found');
}

await triggerEvent(workflowId, {});
await triggerEvent(session, workflowId, subscriber, {});

await session.awaitRunningJobs(workflow._id);

Expand All @@ -157,7 +207,7 @@ describe('Echo Trigger ', async () => {

await executionDetailsRepository.delete({ _environmentId: session.environment._id });

await triggerEvent(workflowId, { name: 4 });
await triggerEvent(session, workflowId, subscriber, { name: 4 });
await session.awaitRunningJobs(workflow._id);

const executionDetailsInvalidType = await executionDetailsRepository.find({
Expand Down Expand Up @@ -219,7 +269,7 @@ describe('Echo Trigger ', async () => {
throw new Error('Workflow not found');
}

await triggerEvent(workflowId, {});
await triggerEvent(session, workflowId, subscriber, {});

await session.awaitRunningJobs(workflow._id);

Expand Down Expand Up @@ -300,8 +350,8 @@ describe('Echo Trigger ', async () => {
throw new Error('Workflow not found');
}

await triggerEvent(workflowId, { name: 'John' });
await triggerEvent(workflowId, { name: 'Bela' });
await triggerEvent(session, workflowId, subscriber, { name: 'John' });
await triggerEvent(session, workflowId, subscriber, { name: 'Bela' });

await session.awaitRunningJobs(workflow?._id, false, 0);

Expand All @@ -316,11 +366,43 @@ describe('Echo Trigger ', async () => {
});
});

async function syncWorkflow(session) {
const resultDiscover = await axios.get(echoServer.serverPath + '/echo?action=discover');

await session.testAgent.post(`/v1/echo/sync`).send({
url: echoServer.serverPath + '/echo',
workflows: resultDiscover.data.workflows,
});
}

async function triggerEvent(session, workflowId: string, subscriber, payload?: any) {
const defaultPayload = {
name: 'test_name',
};

await axios.post(
`${session.serverUrl}${eventTriggerPath}`,
{
name: workflowId,
to: {
subscriberId: subscriber.subscriberId,
email: 'test@subscriber.com',
},
payload: payload ?? defaultPayload,
},
{
headers: {
authorization: `ApiKey ${session.apiKey}`,
},
}
);
}

async function discoverAndSyncEcho(session: UserSession) {
const resultDiscover = await axios.get(echoServer.serverPath + '/echo?action=discover');

await session.testAgent.post(`/v1/echo/sync`).send({
chimeraUrl: echoServer.serverPath + '/echo',
url: echoServer.serverPath + '/echo',
workflows: resultDiscover.data.workflows,
});
}
Expand Down
Loading
Loading