Skip to content

Commit

Permalink
feat(web): add changes sync changes dot indication (#6042)
Browse files Browse the repository at this point in the history
Co-authored-by: Dima Grossman <[email protected]>
  • Loading branch information
djabarovgeorge and scopsy authored Jul 15, 2024
1 parent a5f5657 commit aac8132
Show file tree
Hide file tree
Showing 11 changed files with 179 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ describe('Update workflow by id - /workflows/:workflowId (PUT)', async () => {
const createdTemplate: WorkflowResponse = body.data;

expect(createdTemplate.name).to.equal(testTemplate.name);
expect(createdTemplate.steps[0].replyCallback).to.equal(undefined);
expect(createdTemplate.steps[0].replyCallback).to.deep.equal({});

const template: INotificationTemplate = body.data;

Expand Down
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"axios": "^1.6.2",
"babel-plugin-import": "^1.13.3",
"chart.js": "^3.7.1",
"crypto-js": "^4.2.0",
"customize-cra": "^1.0.0",
"date-fns": "^2.29.2",
"dotenv": "^16.4.5",
Expand Down Expand Up @@ -161,6 +162,7 @@
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/testing-library__jest-dom": "^5.14.5",
"@types/crypto-js": "^4.2.2",
"@types/js-cookie": "^3.0.6",
"eslint-plugin-storybook": "^0.6.13",
"http-server": "^0.13.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const BridgeUpdateModal: FC<BridgeUpdateModalProps> = ({ isOpen, toggleOp

await storeInProperLocation(url);
track('Update endpoint clicked - [Bridge Modal]');
successMessage('You have successfuly updated your Novu Endpoint configuration');
successMessage('You have successfully updated your Novu Endpoint configuration');
toggleOpen();
} catch (error) {
const err = error as Error;
Expand Down
17 changes: 11 additions & 6 deletions apps/web/src/components/layout/components/v2/SyncInfoModal.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import { FC, useEffect, useState } from 'react';
import { QueryObserverResult } from '@tanstack/react-query';
import { showNotification } from '@mantine/notifications';
// TODO: replace with Novui Code Block when available
import { Prism } from '@mantine/prism';

// TODO: replace with Novui Modal when available
import { Modal } from '@novu/design-system';
import { Button, Input, Tabs, Text, Title } from '@novu/novui';
import { FC, useEffect, useState } from 'react';
import { css } from '@novu/novui/css';

import { useBridgeURL } from '../../../../studio/hooks/useBridgeURL';
import { API_ROOT, ENV } from '../../../../config';
import { useStudioState } from '../../../../studio/StudioStateProvider';
import { buildApiHttpClient } from '../../../../api';
import { showNotification } from '@mantine/notifications';
import { css } from '@novu/novui/css';
import { Divider } from '@novu/novui/jsx';

export type SyncInfoModalProps = {
isOpen: boolean;
toggleOpen: () => void;
refetchOriginWorkflows: () => Promise<QueryObserverResult<any, unknown>>;
};

const BRIDGE_ENDPOINT_PLACEHOLDER = '<YOUR_DEPLOYED_BRIDGE_URL>';

export const SyncInfoModal: FC<SyncInfoModalProps> = ({ isOpen, toggleOpen }) => {
export const SyncInfoModal: FC<SyncInfoModalProps> = ({ isOpen, toggleOpen, refetchOriginWorkflows }) => {
const { devSecretKey } = useStudioState();
const [manualUrl, setTunnelManualURl] = useState('');

Expand All @@ -42,7 +45,9 @@ export const SyncInfoModal: FC<SyncInfoModalProps> = ({ isOpen, toggleOpen }) =>

try {
setLoadingSync(true);
const result = await api.syncBridge(manualUrl);
await api.syncBridge(manualUrl);

refetchOriginWorkflows();

toggleOpen();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { Button } from '@novu/novui';
import React, { useState } from 'react';
import CryptoJS from 'crypto-js';

import { colors, Tooltip, useColorScheme } from '@novu/design-system';
import { Button, Text } from '@novu/novui';
import { css } from '@novu/novui/css';
import { IconOutlineCloudUpload } from '@novu/novui/icons';
import { useState } from 'react';

import { useTelemetry } from '../../../../hooks/useNovuAPI';
import { SyncInfoModal } from './SyncInfoModal';
import { When } from '../../../utils/When';
import { useIsSynced } from '../../../../hooks';

export function SyncInfoModalTrigger() {
const [showSyncInfoModal, setShowSyncInfoModal] = useState(false);
const { isSynced, refetchOriginWorkflows } = useIsSynced();

const track = useTelemetry();

const toggleSyncInfoModalShow = () => {
Expand All @@ -15,11 +24,69 @@ export function SyncInfoModalTrigger() {

return (
<>
<Button size="xs" Icon={IconOutlineCloudUpload} onClick={toggleSyncInfoModalShow}>
Sync
</Button>
<Tooltip
disabled={isSynced}
width="auto"
label={<Text>You have un-synced changes in your local application.</Text>}
withinPortal
>
<div
className={css({
position: 'relative',
})}
>
<Button size="xs" Icon={IconOutlineCloudUpload} onClick={toggleSyncInfoModalShow}>
Sync
</Button>
<When truthy={!isSynced}>
<Dot
className={css({
position: 'absolute',
top: -3,
right: -3,
width: 12,
height: 12,
})}
/>
</When>
</div>
</Tooltip>

{/** TODO: use a modal manager */}
<SyncInfoModal isOpen={showSyncInfoModal} toggleOpen={toggleSyncInfoModalShow} />
<SyncInfoModal
isOpen={showSyncInfoModal}
toggleOpen={toggleSyncInfoModalShow}
refetchOriginWorkflows={refetchOriginWorkflows}
/>
</>
);
}

function createHash(workflowDefine) {
return CryptoJS.SHA256(JSON.stringify(workflowDefine || '')).toString(CryptoJS.enc.Hex);
}

export function Dot(props) {
const { colorScheme } = useColorScheme();

return (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none" {...props}>
<rect
x="1.5"
y="1.5"
width="13"
height="13"
rx="6.5"
fill="url(#paint0_linear_1722_2699)"
stroke={colorScheme === 'light' ? colors.white : colors.B15}
strokeWidth="3"
/>
<defs>
<linearGradient id="paint0_linear_1722_2699" x1="8" y1="13" x2="8" y2="3" gradientUnits="userSpaceOnUse">
<stop stopColor="#FF512F" />
<stop offset="1" stopColor="#DD2476" />
</linearGradient>
</defs>
</svg>
);
}
1 change: 1 addition & 0 deletions apps/web/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ export * from './useThemeChange';
export * from './useVariablesManager';
export * from './useVercelIntegration';
export * from './useVercelParams';
export * from './useIsSynced';
39 changes: 39 additions & 0 deletions apps/web/src/hooks/useIsSynced.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';

import { INotificationTemplate } from '@novu/shared';

import { getNotificationsList } from '../api/notification-templates';
import { useDiscover } from '../studio/hooks';
import { createHash } from '../utils/create-hash';

export function useIsSynced() {
const { data: bridgeDiscoverData, isLoading: isLoadingBridgeWorkflows } = useDiscover();
const {
data: originData,
isLoading: isLoadingOriginWorkflows,
refetch,
} = useQuery(
['origin-workflows'],
async () => {
return getNotificationsList({ page: 0, limit: 100 });
},
{}
);

const isSynced = useMemo(() => {
if (isLoadingBridgeWorkflows || isLoadingOriginWorkflows) {
return true;
}

const bridgeDiscoverWorkflows = bridgeDiscoverData?.workflows || undefined;
const originWorkflows = originData?.data.map((workflow: INotificationTemplate) => workflow.rawData) || undefined;

const bridgeDiscoverWorkflowsHash = createHash(JSON.stringify(bridgeDiscoverWorkflows || ''));
const storedWorkflowsHash = createHash(JSON.stringify(originWorkflows || ''));

return storedWorkflowsHash === bridgeDiscoverWorkflowsHash;
}, [bridgeDiscoverData, originData, isLoadingBridgeWorkflows, isLoadingOriginWorkflows]);

return { isSynced, refetchOriginWorkflows: refetch };
}
5 changes: 5 additions & 0 deletions apps/web/src/utils/create-hash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import CryptoJS from 'crypto-js';

export const createHash = (message: string) => {
return CryptoJS.SHA256(message).toString(CryptoJS.enc.Hex);
};
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ const notificationTemplateSchema = new Schema<NotificationTemplateDBModel>(
rawData: Schema.Types.Mixed,
payloadSchema: Schema.Types.Mixed,
},
schemaOptions
{ ...schemaOptions, minimize: false }
);

notificationTemplateSchema.virtual('steps.template', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
TemplateVariableTypeEnum,
FilterParts,
WorkflowTypeEnum,
NotificationTemplateCustomData,
} from '../../types';
import { IMessageTemplate } from '../message-template';
import { IPreferenceChannels } from '../subscriber-preference';
Expand All @@ -30,9 +31,12 @@ export interface INotificationTemplate {
steps: INotificationTemplateStep[] | INotificationBridgeTrigger[];
triggers: INotificationTrigger[];
isBlueprint?: boolean;
blueprintId?: string;
type?: WorkflowTypeEnum;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
payloadSchema?: any;
rawData?: any;
data?: NotificationTemplateCustomData;
}

export class IGroupedBlueprint {
Expand Down
Loading

0 comments on commit aac8132

Please sign in to comment.