Skip to content

Commit

Permalink
Merge branch 'next' into nv-2754-novu-integrations-allow-to-activated…
Browse files Browse the repository at this point in the history
…eactivate-and-set-as
  • Loading branch information
BiswaViraj authored Sep 11, 2023
2 parents 0170567 + 5d3922b commit f3fdb4b
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .github/actions/checkout-submodules/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ runs:

steps:
- name: Checkout submodule
if: ${{ inputs.run == 'true' }}
if: ${{ inputs.enabled == 'true' }}
uses: actions/checkout@v3
with:
token: ${{ inputs.submodule_token }}
Expand Down
44 changes: 44 additions & 0 deletions apps/api/src/app/integrations/e2e/create-integration.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,50 @@ describe('Create Integration - /integration (POST)', function () {
expect(second.active).to.equal(true);
expect(second.priority).to.equal(1);
});

it('should not allow creating the same novu provider on same environment twice', async function () {
const inAppPayload = {
name: InAppProviderIdEnum.Novu,
providerId: InAppProviderIdEnum.Novu,
channel: ChannelTypeEnum.IN_APP,
credentials: {},
active: true,
check: false,
};

const inAppResult = await session.testAgent.post('/v1/integrations').send(inAppPayload);

expect(inAppResult.body.statusCode).to.equal(400);
expect(inAppResult.body.message).to.equal('One environment can only have one In app provider');

const emailPayload = {
name: EmailProviderIdEnum.Novu,
providerId: EmailProviderIdEnum.Novu,
channel: ChannelTypeEnum.EMAIL,
credentials: {},
active: true,
check: false,
};

const emailResult = await session.testAgent.post('/v1/integrations').send(emailPayload);

expect(emailResult.body.statusCode).to.equal(409);
expect(emailResult.body.message).to.equal('Integration with novu provider for email channel already exists');

const smsPayload = {
name: SmsProviderIdEnum.Novu,
providerId: SmsProviderIdEnum.Novu,
channel: ChannelTypeEnum.SMS,
credentials: {},
active: true,
check: false,
};

const smsResult = await session.testAgent.post('/v1/integrations').send(smsPayload);

expect(smsResult.body.statusCode).to.equal(409);
expect(smsResult.body.message).to.equal('Integration with novu provider for sms channel already exists');
});
});

async function insertIntegrationTwice(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export class CreateIntegration {
if (command.providerId === SmsProviderIdEnum.Novu || command.providerId === EmailProviderIdEnum.Novu) {
const count = await this.integrationRepository.count({
_environmentId: command.environmentId,
providerId: EmailProviderIdEnum.Novu,
providerId: command.providerId,
channel: command.channel,
});

Expand Down
39 changes: 39 additions & 0 deletions apps/web/cypress/tests/integrations-list-page.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1000,4 +1000,43 @@ describe('Integrations List Page', function () {
expect(el.get(0).innerText).to.eq('20 messages per month');
});
});

it('should not allow creating a novu provider for the same environment if it already exists', () => {
cy.intercept('*/integrations', async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
}).as('getIntegrations');
cy.intercept('*/environments').as('getEnvironments');

cy.visit('/integrations');

cy.wait('@getIntegrations');
cy.wait('@getEnvironments');

cy.getByTestId('add-provider').should('be.enabled').click();
cy.getByTestId('select-provider-sidebar').should('be.visible');

cy.getByTestId(`provider-${EmailProviderIdEnum.Novu}`).contains('Novu').click();

cy.window().then((win) => {
if (win.isDarkTheme) {
cy.getByTestId(`selected-provider-image-${EmailProviderIdEnum.Novu}`).should(
'have.attr',
'src',
`/static/images/providers/dark/square/${EmailProviderIdEnum.Novu}.svg`
);
return;
}

cy.getByTestId(`selected-provider-image-${EmailProviderIdEnum.Novu}`).should(
'have.attr',
'src',
`/static/images/providers/light/square/${EmailProviderIdEnum.Novu}.svg`
);
});
cy.getByTestId('selected-provider-name').should('be.visible').contains('Novu');

cy.getByTestId('select-provider-sidebar-next').should('not.be.disabled').contains('Next').click();
cy.getByTestId('novu-provider-error').contains('You can only create one Novu Email per environment.');
cy.getByTestId('create-provider-instance-sidebar-create').should('be.disabled');
});
});
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import styled from '@emotion/styled';
import { ActionIcon, Group, Radio, Text } from '@mantine/core';
import { useEffect, useMemo } from 'react';
import { ChannelTypeEnum, ICreateIntegrationBodyDto, NOVU_PROVIDERS, providers } from '@novu/shared';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useEffect, useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form';
import styled from '@emotion/styled';
import { ChannelTypeEnum, ICreateIntegrationBodyDto, InAppProviderIdEnum, providers } from '@novu/shared';

import { createIntegration } from '../../../../api/integration';
import { QueryKeys } from '../../../../api/query.keys';
import { useSegment } from '../../../../components/providers/SegmentProvider';
import { When } from '../../../../components/utils/When';
import { Button, colors, NameInput, Sidebar } from '../../../../design-system';
import { ArrowLeft } from '../../../../design-system/icons';
import { inputStyles } from '../../../../design-system/config/inputs.styles';
import { ArrowLeft } from '../../../../design-system/icons';
import { useFetchEnvironments } from '../../../../hooks/useFetchEnvironments';
import { useSegment } from '../../../../components/providers/SegmentProvider';
import { createIntegration } from '../../../../api/integration';
import { IntegrationsStoreModalAnalytics } from '../../constants';
import { errorMessage, successMessage } from '../../../../utils/notifications';
import { QueryKeys } from '../../../../api/query.keys';
import { ProviderImage } from './SelectProviderSidebar';
import { CHANNEL_TYPE_TO_STRING } from '../../../../utils/channels';
import { errorMessage, successMessage } from '../../../../utils/notifications';
import { IntegrationsStoreModalAnalytics } from '../../constants';
import type { IntegrationEntity } from '../../types';
import { useProviders } from '../../useProviders';
import { When } from '../../../../components/utils/When';
import { ProviderImage } from './SelectProviderSidebar';

interface ICreateProviderInstanceForm {
name: string;
Expand Down Expand Up @@ -67,8 +67,8 @@ export function CreateProviderInstanceSidebar({

const selectedEnvironmentId = watch('environmentId');

const showInAppErrorMessage = useMemo(() => {
if (!provider || integrations.length === 0 || provider.id !== InAppProviderIdEnum.Novu) {
const showNovuProvidersErrorMessage = useMemo(() => {
if (!provider || integrations.length === 0 || !NOVU_PROVIDERS.includes(provider.id)) {
return false;
}

Expand Down Expand Up @@ -170,7 +170,7 @@ export function CreateProviderInstanceSidebar({
Cancel
</Button>
<Button
disabled={isLoading || isLoadingCreate || showInAppErrorMessage}
disabled={isLoading || isLoadingCreate || showNovuProvidersErrorMessage}
loading={isLoadingCreate}
submit
data-test-id="create-provider-instance-sidebar-create"
Expand Down Expand Up @@ -231,9 +231,11 @@ export function CreateProviderInstanceSidebar({
);
}}
/>
<When truthy={showInAppErrorMessage}>
<When truthy={showNovuProvidersErrorMessage}>
<WarningMessage>
<Text>You can only create one {provider.displayName} per environment.</Text>
<Text data-test-id="novu-provider-error">
You can only create one {provider.displayName} per environment.
</Text>
</WarningMessage>
</When>
</Sidebar>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
inAppProviders,
chatProviders,
InAppProviderIdEnum,
NOVU_SMS_EMAIL_PROVIDERS,
} from '@novu/shared';

import { colors, Sidebar } from '../../../../design-system';
Expand All @@ -32,14 +31,12 @@ const filterSearch = (list, search: string) =>
list.filter((prov) => prov.displayName.toLowerCase().includes(search.toLowerCase()));

const mapStructure = (listProv): IIntegratedProvider[] =>
listProv
.filter((providerItem) => !NOVU_SMS_EMAIL_PROVIDERS.includes(providerItem.id))
.map((providerItem) => ({
providerId: providerItem.id,
displayName: providerItem.displayName,
channel: providerItem.channel,
docReference: providerItem.docReference,
}));
listProv.map((providerItem) => ({
providerId: providerItem.id,
displayName: providerItem.displayName,
channel: providerItem.channel,
docReference: providerItem.docReference,
}));

const initialProvidersList = {
[ChannelTypeEnum.EMAIL]: mapStructure(emailProviders),
Expand Down
3 changes: 3 additions & 0 deletions packages/node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,9 @@ await novu.integrations.delete("integrationId")

// get novu in-app status
await novu.integrations.getInAppStatus()

// set an integration as primary
await novu.integrations.setIntegrationAsPrimary("integrationId")
```

### Feeds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface IIntegrations {
update(integrationId: string, data: IIntegrationsUpdatePayload);
delete(integrationId: string);
getInAppStatus();
setIntegrationAsPrimary(integrationId: string);
}

export interface IIntegrationsPayload extends IIntegrationsUpdatePayload {
Expand Down
21 changes: 21 additions & 0 deletions packages/node/src/lib/integrations/integrations.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,25 @@ describe('test use of novus node package - Integrations class', () => {
'/integrations/INTEGRATION_ID'
);
});

test('should set the integration as primary', async () => {
mockedAxios.post.mockResolvedValue({});

await novu.integrations.setIntegrationAsPrimary('INTEGRATION_ID');

expect(mockedAxios.post).toHaveBeenCalled();
expect(mockedAxios.post).toHaveBeenCalledWith(
'/integrations/INTEGRATION_ID/set-primary',
{}
);
});

test('should get the in-app status of the integration', async () => {
mockedAxios.post.mockResolvedValue({});

await novu.integrations.getInAppStatus();

expect(mockedAxios.get).toHaveBeenCalled();
expect(mockedAxios.get).toHaveBeenCalledWith('/integrations/in-app/status');
});
});
21 changes: 16 additions & 5 deletions packages/node/src/lib/integrations/integrations.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ChannelTypeEnum } from '@novu/shared';
import { WithHttp } from '../novu.interface';
import {
IIntegrations,
IIntegrationsPayload,
IIntegrationsUpdatePayload,
} from './integrations.interface';
import { WithHttp } from '../novu.interface';

export class Integrations extends WithHttp implements IIntegrations {
async getAll() {
Expand All @@ -14,6 +15,10 @@ export class Integrations extends WithHttp implements IIntegrations {
return await this.http.get('/integrations/active');
}

async getInAppStatus() {
return await this.http.get('/integrations/in-app/status');
}

/**
* @param {string} providerId - Id of the provider to get status
*/
Expand Down Expand Up @@ -44,14 +49,20 @@ export class Integrations extends WithHttp implements IIntegrations {
});
}

/**
* @param {string} integrationId - integrationId of the integration to set it as primary
*/
async setIntegrationAsPrimary(integrationId: string) {
return await this.http.post(
`/integrations/${integrationId}/set-primary`,
{}
);
}

/**
* @param {string} integrationId - integrationId of the integration to delete
*/
async delete(integrationId: string) {
return await this.http.delete(`/integrations/${integrationId}`);
}

async getInAppStatus() {
return await this.http.get('/integrations/in-app/status');
}
}

0 comments on commit f3fdb4b

Please sign in to comment.