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 all 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
6 changes: 5 additions & 1 deletion apps/api/e2e/echo.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ class EchoServer {
this.server.use(express.json());
this.server.use(serve({ client: this.echo }));

this.app = await this.server.listen(this.port);
await new Promise<void>((resolve) => {
this.app = this.server.listen(this.port, () => {
resolve();
});
});
}

async stop() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from 'chai';
import { faker } from '@faker-js/faker';
import { createHash } from 'crypto';
import { faker } from '@faker-js/faker';

import { UserSession } from '@novu/testing';
import { ChannelTypeEnum } from '@novu/stateless';
Expand Down
205 changes: 179 additions & 26 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,141 @@ describe('Echo Trigger ', async () => {
expect(messagesAfter[0].subject).to.include('This is an email subject TEST');
});

it('should skip step', async () => {
// should skip static value
const workflowIdSkipByStatic = 'skip-by-static-value-workflow';
await echoServer.echo.workflow(
workflowIdSkipByStatic,
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 workflowByStatic = await workflowsRepository.findByTriggerIdentifier(
session.environment._id,
workflowIdSkipByStatic
);

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

await triggerEvent(session, workflowIdSkipByStatic, subscriber);
await session.awaitRunningJobs(workflowByStatic._id);

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

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

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

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

// should skip by variable default value
const workflowIdSkipByVariable = 'skip-by-variable-default-value';
await echoServer.echo.workflow(
workflowIdSkipByVariable,
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' },
shouldSkipVar: { type: 'boolean', default: true },
},
} as const,
skip: (inputs) => inputs.shouldSkipVar,
}
);
},
{
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,
workflowIdSkipByVariable
);

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

await triggerEvent(session, workflowIdSkipByVariable, 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 cancelledJobByVariable = await jobRepository.find({
_environmentId: session.environment._id,
_subscriberId: subscriber._id,
type: StepTypeEnum.EMAIL,
});

expect(cancelledJobByVariable.length).to.be.eq(2);
expect(cancelledJobByVariable[1].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 +254,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 +278,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 +340,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 +421,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,6 +437,38 @@ 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({
bridgeUrl: 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');

Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/app/integrations/usecases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
GetDecryptedIntegrations,
CalculateLimitNovuIntegration,
ConditionsFilter,
NormalizeVariables,
} from '@novu/application-generic';

import { GetWebhookSupportStatus } from './get-webhook-support-status/get-webhook-support-status.usecase';
Expand Down Expand Up @@ -33,4 +34,5 @@ export const USE_CASES = [
CalculateLimitNovuIntegration,
SetIntegrationAsPrimary,
CreateNovuIntegrations,
NormalizeVariables,
];
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { IntegrationRepository, SubscriberRepository } from '@novu/dal';
import { SubscribersService, UserSession } from '@novu/testing';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import { ChannelTypeEnum, ChatProviderIdEnum, PushProviderIdEnum } from '@novu/shared';
import { faker } from '@faker-js/faker';

import { IntegrationRepository, SubscriberRepository } from '@novu/dal';
import { SubscribersService, UserSession } from '@novu/testing';
import { ChannelTypeEnum, ChatProviderIdEnum, PushProviderIdEnum } from '@novu/shared';
import { OAuthHandlerEnum, UpdateSubscriberChannel, UpdateSubscriberChannelCommand } from '@novu/application-generic';

import { SharedModule } from '../../shared/shared.module';

describe('Update Subscriber channel credentials', function () {
Expand Down Expand Up @@ -418,8 +420,8 @@ describe('Update Subscriber channel credentials', function () {
it('should update deviceTokens without duplication on channel creation (addChannelToSubscriber)', async function () {
const subscriberId = SubscriberRepository.createObjectId();
const test = await subscriberRepository.create({
lastName: faker.name.lastName(),
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
email: faker.internet.email(),
phone: faker.phone.phoneNumber(),
_environmentId: session.environment._id,
Expand Down
Loading
Loading