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

fix(web, dashboard): replace BridgeStatus type with HealthCheck type, provide state for preview #6910

Merged
merged 2 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@codemirror/lang-liquid": "^6.2.1",
"@hookform/resolvers": "^3.9.0",
"@lezer/highlight": "^1.2.1",
"@novu/framework": "workspace:*",
"@novu/react": "workspace:*",
"@novu/shared": "workspace:*",
"@radix-ui/react-accordion": "^1.2.1",
Expand Down
5 changes: 2 additions & 3 deletions apps/dashboard/src/api/bridge.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { BridgeStatus } from '@/utils/types';
import type { HealthCheck } from '@novu/framework/internal';
import { get, post } from './api.client';

export const getBridgeHealthCheck = async () => {
const { data } = await get<{ data: BridgeStatus }>('/bridge/status');
const { data } = await get<{ data: HealthCheck }>('/bridge/status');

return data;
};
Expand Down
5 changes: 3 additions & 2 deletions apps/dashboard/src/hooks/use-bridge-health-check.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { useQuery } from '@tanstack/react-query';
import { getBridgeHealthCheck } from '@/api/bridge';
import { BridgeStatus, ConnectionStatus } from '@/utils/types';
import { ConnectionStatus } from '@/utils/types';
import { QueryKeys } from '@/utils/query-keys';
import { useMemo } from 'react';
import { useEnvironment } from '@/context/environment/hooks';
import type { HealthCheck } from '@novu/framework/internal';

const BRIDGE_STATUS_REFRESH_INTERVAL_IN_MS = 10 * 1000;

export const useBridgeHealthCheck = () => {
const { currentEnvironment } = useEnvironment();
const bridgeURL = currentEnvironment?.bridge?.url || '';

const { data, isLoading, error } = useQuery<BridgeStatus>({
const { data, isLoading, error } = useQuery<HealthCheck>({
queryKey: [QueryKeys.bridgeHealthCheck, currentEnvironment?._id, bridgeURL],
queryFn: getBridgeHealthCheck,
enabled: !!bridgeURL,
Expand Down
8 changes: 0 additions & 8 deletions apps/dashboard/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
import type { StepResponseDto } from '@novu/shared';

export type BridgeStatus = {
rifont marked this conversation as resolved.
Show resolved Hide resolved
status: 'ok';
bridgeUrl?: string;
discovered: {
workflows: number;
};
};

export enum ConnectionStatus {
CONNECTED = 'connected',
DISCONNECTED = 'disconnected',
Expand Down
32 changes: 13 additions & 19 deletions apps/web/src/bridgeApi/bridgeApi.client.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import axios from 'axios';

import type { DiscoverWorkflowOutput } from '@novu/framework/internal';

export type StepPreviewParams = {
workflowId: string;
stepId: string;
payload: Record<string, unknown>;
controls: Record<string, unknown>;
};
import type { DiscoverWorkflowOutput, Event, ExecuteOutput, HealthCheck } from '@novu/framework/internal';

export type TriggerParams = {
workflowId: string;
Expand All @@ -19,14 +12,6 @@ export type TriggerParams = {
};
};

export type BridgeStatus = {
status: 'ok';
bridgeUrl?: string;
discovered: {
workflows: number;
};
};

export function buildBridgeHTTPClient(baseURL: string) {
const httpClient = axios.create({
baseURL,
Expand Down Expand Up @@ -69,7 +54,7 @@ export function buildBridgeHTTPClient(baseURL: string) {
});
},

async healthCheck(): Promise<BridgeStatus> {
async healthCheck(): Promise<HealthCheck> {
return get('', {
action: 'health-check',
});
Expand All @@ -78,7 +63,7 @@ export function buildBridgeHTTPClient(baseURL: string) {
/**
* TODO: Use framework shared types
*/
async getWorkflow(workflowId: string): Promise<any> {
async getWorkflow(workflowId: string): Promise<DiscoverWorkflowOutput | undefined> {
const { workflows } = await this.discover();

return workflows.find((workflow) => workflow.workflowId === workflowId);
Expand All @@ -87,10 +72,19 @@ export function buildBridgeHTTPClient(baseURL: string) {
/**
* TODO: Use framework shared types
*/
async getStepPreview({ workflowId, stepId, controls, payload }: StepPreviewParams): Promise<any> {
async getStepPreview({
workflowId,
stepId,
controls,
payload,
state,
subscriber,
}: Omit<Event, 'action'>): Promise<ExecuteOutput> {
return post(`${baseURL}?action=preview&workflowId=${workflowId}&stepId=${stepId}`, {
controls: controls || {},
payload: payload || {},
state: state || [],
subscriber: subscriber || {},
});
},

Expand Down
7 changes: 2 additions & 5 deletions apps/web/src/components/layout/components/BridgeStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Badge, Text } from '@mantine/core';
import { useQuery } from '@tanstack/react-query';
import { Popover } from '@novu/design-system';
import { useDisclosure } from '@mantine/hooks';
import type { HealthCheck } from '@novu/framework/internal';
import { api } from '../../../api/api.client';
import { useEnvironment } from '../../../hooks';
import { IS_SELF_HOSTED } from '../../../config';
Expand All @@ -11,11 +12,7 @@ export function BridgeStatus() {

const { environment } = useEnvironment();
const isBridgeEnabled = !!environment?.echo?.url && !IS_SELF_HOSTED;
const { data, error, isInitialLoading } = useQuery<{
status: 'ok' | 'down';
version: string;
discovered: { workflows: number };
}>(
const { data, error, isInitialLoading } = useQuery<HealthCheck>(
['/v1/bridge/status'],
() => {
return api.get('/v1/bridge/status');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { IconCheck } from '@novu/novui/icons';
import { Text } from '@novu/novui';
import { css } from '@novu/novui/css';
import { useColorScheme } from '@novu/design-system';
import { BridgeStatus } from '../../../bridgeApi/bridgeApi.client';
import type { HealthCheck } from '@novu/framework/internal';
import { CodeSnippet } from '../../get-started/legacy-onboarding/components/CodeSnippet';
import { useStudioState } from '../../../studio/StudioStateProvider';
import { timelineRecipe } from './SetupTimeline.recipe';
Expand All @@ -20,7 +20,7 @@ const Icon = () => (
/>
);

export const SetupTimeline = ({ testResponse }: { testResponse: { isLoading: boolean; data: BridgeStatus } }) => {
export const SetupTimeline = ({ testResponse }: { testResponse: { isLoading: boolean; data: HealthCheck } }) => {
const { devSecretKey } = useStudioState();
const [active, setActive] = useState(0);
const { colorScheme } = useColorScheme();
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/pages/studio-onboarding/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { useEffect } from 'react';
import { Title, Text } from '@novu/novui';
import { VStack } from '@novu/novui/jsx';
import { useNavigate } from 'react-router-dom';
import { HealthCheck } from '@novu/framework/internal';
import { Footer } from './components/Footer';
import { Header } from './components/Header';
import { SetupTimeline } from './components/SetupTimeline';
import { Wrapper } from './components/Wrapper';
import { ROUTES } from '../../constants/routes';
import { useHealthCheck } from '../../studio/hooks/useBridgeAPI';
import { BridgeStatus } from '../../bridgeApi/bridgeApi.client';
import { useStudioState } from '../../studio/StudioStateProvider';
import { capitalizeFirstLetter } from '../../utils/string';
import { setNovuOnboardingStepCookie } from '../../utils';
Expand Down Expand Up @@ -62,7 +62,7 @@ export const StudioOnboarding = () => {
Send your first email notification, by connecting to your Novu Bridge Endpoint. This setup will create a
sample Next.js project with a pre-configured <code>@novu/framework</code>.
</Text>
<SetupTimeline testResponse={{ data: data as BridgeStatus, isLoading }} />
<SetupTimeline testResponse={{ data: data as HealthCheck, isLoading }} />
</div>
</VStack>
<Footer
Expand Down
7 changes: 3 additions & 4 deletions apps/web/src/pages/studio-onboarding/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Prism } from '@mantine/prism';
import { errorMessage, Tabs } from '@novu/design-system';
import { errorMessage } from '@novu/design-system';
import { css } from '@novu/novui/css';
import { useEffect, useMemo, useState } from 'react';
import { createSearchParams, useNavigate } from 'react-router-dom';
import { VStack } from '@novu/novui/jsx';
import { Text, Title } from '@novu/novui';
import { isAxiosError } from 'axios';
import type { ExecuteOutput } from '@novu/framework/internal';
// TODO: This indicates that all onboarding pages for studio should move under the "Studio" folder
import { useWorkflowTrigger, useDiscover, useWorkflowPreview } from '../../studio/hooks/useBridgeAPI';
import { Footer } from './components/Footer';
Expand All @@ -23,7 +23,6 @@ export const StudioOnboardingPreview = () => {
const [controls, setStepControls] = useState({});
const [payload, setPayload] = useState({});
const { testUser } = useStudioState();
const [tab, setTab] = useState<string>('Preview');
const track = useTelemetry();
const navigate = useNavigate();
const { data: bridgeResponse, isLoading: isLoadingList } = useDiscover();
Expand Down Expand Up @@ -154,7 +153,7 @@ export const StudioOnboardingPreview = () => {
source="studio"
error={null}
step={step}
preview={preview}
preview={preview as ExecuteOutput}
isLoadingPreview={previewLoading || isLoadingList}
/>
<WorkflowStepEditorControlsPanel
Expand Down
12 changes: 12 additions & 0 deletions apps/web/src/studio/components/workflows/WorkflowNotFound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Text } from '@novu/novui';
import { PageContainer } from '../../layout';

export const WorkflowNotFound = () => {
return (
<PageContainer>
<Text color={'typography.text.secondary'} textAlign={'center'}>
Workflow not found
</Text>
</PageContainer>
);
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Skeleton } from '@mantine/core';
import { IconButton } from '@novu/novui';
import { IconButton, Text } from '@novu/novui';
import { css } from '@novu/novui/css';
import { IconCable, IconPlayArrow, IconSettings } from '@novu/novui/icons';
import { HStack, Stack } from '@novu/novui/jsx';
import { token } from '@novu/novui/tokens';
import { useEffect, useState } from 'react';
import type { DiscoverWorkflowOutput } from '@novu/framework/internal';
import { useTelemetry } from '../../../../hooks/useNovuAPI';
import { useWorkflow } from '../../../hooks/useBridgeAPI';
import { useStudioWorkflowsNavigation } from '../../../hooks/useStudioWorkflowsNavigation';
Expand All @@ -18,6 +17,7 @@ import { WorkflowDetailFormContextProvider } from '../preferences/WorkflowDetail
import { WorkflowBackgroundWrapper } from './WorkflowBackgroundWrapper';
import { WorkflowFloatingMenu } from './WorkflowFloatingMenu';
import { WorkflowNodes } from './WorkflowNodes';
import { WorkflowNotFound } from '../WorkflowNotFound';

const BaseWorkflowsDetailPage = () => {
const { currentWorkflowId, goToStep, goToTest } = useStudioWorkflowsNavigation();
Expand All @@ -39,10 +39,11 @@ const BaseWorkflowsDetailPage = () => {
return <WorkflowsContentLoading />;
}

// After loading has completed, we can safely cast the workflow to DiscoverWorkflowOutput
const fetchedWorkflow = workflow as DiscoverWorkflowOutput;
if (!workflow) {
return <WorkflowNotFound />;
}

const title = fetchedWorkflow?.name || fetchedWorkflow.workflowId;
const title = workflow?.name || workflow.workflowId;

return (
<WorkflowsPageTemplate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@ import { Prism } from '@mantine/prism';
import { Tabs } from '@novu/novui';
import { IconOutlineCode, IconVisibility } from '@novu/novui/icons';
import { VStack } from '@novu/novui/jsx';
import { ButtonTypeEnum, inAppMessageFromBridgeOutputs, StepTypeEnum } from '@novu/shared';
import { inAppMessageFromBridgeOutputs, StepTypeEnum } from '@novu/shared';
import { css } from '@novu/novui/css';
import type {
ChatOutput,
EmailOutput,
ExecuteOutput,
InAppOutput,
PushOutput,
SmsOutput,
} from '@novu/framework/internal';
import { PreviewWeb } from '../../../../components/workflow/preview/email/PreviewWeb';
import { useActiveIntegrations } from '../../../../hooks';
import {
Expand All @@ -17,7 +25,7 @@ import { MobileSimulator } from '../../../../components/workflow/preview/common'
import { ErrorPrettyRender } from '../../../../components/workflow/preview/ErrorPrettyRender';

interface IWorkflowStepEditorContentPanelProps {
preview: any;
preview: ExecuteOutput;
isLoadingPreview: boolean;
error?: any;
step: any;
Expand Down Expand Up @@ -94,7 +102,7 @@ export const PreviewStep = ({
source,
}: {
channel: StepTypeEnum;
preview: any;
preview: ExecuteOutput;
loadingPreview: boolean;
source?: 'studio' | 'playground' | 'dashboard';
}) => {
Expand All @@ -106,13 +114,15 @@ export const PreviewStep = ({
const props = { locales: [], loading: loadingPreview, onLocaleChange: () => {} };

switch (channel) {
case StepTypeEnum.EMAIL:
case StepTypeEnum.EMAIL: {
const previewOutputs = preview?.outputs as EmailOutput;

return (
<PreviewWeb
source={source}
integration={integration}
content={preview?.outputs?.body}
subject={preview?.outputs?.subject}
content={previewOutputs?.body}
subject={previewOutputs?.subject}
classNames={{
browser: css({ display: 'flex', flexDirection: 'column', gap: '0', flex: '1' }),
content: css({ display: 'flex' }),
Expand All @@ -128,12 +138,16 @@ export const PreviewStep = ({
{...props}
/>
);
}

case StepTypeEnum.SMS:
return <SmsBasePreview content={preview?.outputs?.body} {...props} />;
case StepTypeEnum.SMS: {
const previewOutputs = preview?.outputs as SmsOutput;

return <SmsBasePreview content={previewOutputs?.body} {...props} />;
}

case StepTypeEnum.IN_APP: {
const inAppMessage = inAppMessageFromBridgeOutputs(preview?.outputs);
const inAppMessage = inAppMessageFromBridgeOutputs(preview?.outputs as InAppOutput);

return (
<InAppBasePreview
Expand All @@ -148,24 +162,31 @@ export const PreviewStep = ({
);
}

case StepTypeEnum.CHAT:
return <ChatBasePreview content={preview?.outputs?.body} {...props} />;
case StepTypeEnum.CHAT: {
const previewOutputs = preview?.outputs as ChatOutput;

return <ChatBasePreview content={previewOutputs?.body} {...props} />;
}

case StepTypeEnum.PUSH: {
const previewOutputs = preview?.outputs as PushOutput;

case StepTypeEnum.PUSH:
return (
<MobileSimulator withBackground>
<PushBasePreview title={preview?.outputs?.subject} content={preview?.outputs?.body} {...props} />
<PushBasePreview title={previewOutputs?.subject} content={previewOutputs?.body} {...props} />
</MobileSimulator>
);
}

case StepTypeEnum.DIGEST:
case StepTypeEnum.DELAY:
case StepTypeEnum.CUSTOM:
case StepTypeEnum.CUSTOM: {
return (
<Prism styles={prismStyles} withLineNumbers language="javascript">
{`${JSON.stringify(preview?.outputs, null, 2)}`}
</Prism>
);
}

default:
return <>Unknown Step</>;
Expand Down
Loading
Loading