Skip to content

Commit

Permalink
feat(web): wip web containers (novuhq#6225)
Browse files Browse the repository at this point in the history
  • Loading branch information
djabarovgeorge authored Aug 5, 2024
1 parent d3427e6 commit 55ebfb5
Show file tree
Hide file tree
Showing 31 changed files with 3,107 additions and 5,673 deletions.
3 changes: 2 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,7 @@
"apps/web/env.sh",
"packages/js/src/ui/index.directcss",
"unreadRead",
"apps/web/src/studio/components/workflows/step-editor/editor/files.ts"
"apps/web/src/studio/components/workflows/step-editor/editor/files.ts",
"apps/web/src/pages/playground/web-container-configuration/sandbox-vite/*.ts"
]
}
4 changes: 2 additions & 2 deletions apps/web/src/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ import { IS_EE_AUTH_ENABLED } from './config/index';
import { EnterpriseAuthRoutes } from './ee/clerk/EnterpriseAuthRoutes';
import { novuOnboardedCookie } from './utils/cookies';
import { EnterprisePrivatePageLayout } from './ee/clerk/components/EnterprisePrivatePageLayout';
import { OnboardingPage } from './pages/auth/onboarding/Onboarding';
import { PlaygroundPage } from './pages/auth/onboarding/PlaygroundPage';
import { OnboardingPage } from './pages/playground/onboarding/Onboarding';
import { PlaygroundPage } from './pages/playground/onboarding/PlaygroundPage';
import { BillingRoutes } from './pages/BillingPages';
import { StudioStepEditorPage } from './studio/pages/StudioStepEditorPage';

Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ClerkProvider } from './ee/clerk/providers/ClerkProvider';
import { EnvironmentProvider } from './components/providers/EnvironmentProvider';
import { SegmentProvider } from './components/providers/SegmentProvider';
import { StudioStateProvider } from './studio/StudioStateProvider';
import { ContainerProvider } from './studio/components/workflows/step-editor/editor/useContainer';
import { ContainerProvider } from './hooks/useContainer';

const defaultQueryFn = async ({ queryKey }: { queryKey: string }) => {
const response = await api.get(`${queryKey[0]}`);
Expand Down
3 changes: 1 addition & 2 deletions apps/web/src/ee/clerk/components/QuestionnaireForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ import { Button, inputStyles, Select } from '@novu/design-system';

import { api } from '../../../api/api.client';
import { useAuth } from '../../../hooks/useAuth';
import { useFeatureFlag, useVercelIntegration, useVercelParams, useEffectOnce } from '../../../hooks';
import { useFeatureFlag, useVercelIntegration, useVercelParams, useEffectOnce, useContainer } from '../../../hooks';
import { ROUTES } from '../../../constants/routes';
import styled from '@emotion/styled/macro';
import { useSegment } from '../../../components/providers/SegmentProvider';
import { BRIDGE_SYNC_SAMPLE_ENDPOINT } from '../../../config/index';
import { DynamicCheckBox } from '../../../pages/auth/components/dynamic-checkbox/DynamicCheckBox';
import { useContainer } from '../../../studio/components/workflows/step-editor/editor/useContainer';
import { useWebContainerSupported } from '../../../hooks/useWebContainerSupport';

function updateClerkOrgMetadata(data: UpdateExternalOrganizationDto) {
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ export * from './useVariablesManager';
export * from './useVercelIntegration';
export * from './useVercelParams';
export * from './useApiKey';
export * from './useContainer';
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import React, { useEffect, useRef, useState } from 'react';
import { ITerminalDimensions } from 'xterm-addon-fit';

import { dynamicFiles } from './files';
import { useEffectOnce } from '../../../../../hooks';
import { useDiscover, useStudioState } from '../../../../hooks';
import { BRIDGE_CODE, REACT_EMAIL_CODE } from './sandbox-code-snippets';
import { FCWithChildren } from '../../../../../types';
import { TUNNEL_CODE } from './tunnel.service.const';
import { useSegment } from '../../../../../components/providers/SegmentProvider';
import { captureException } from '@sentry/react';

import { FCWithChildren } from '../types';
import { useStudioState } from '../studio/hooks';
import { useEffectOnce } from './useEffectOnce';
import { configureFiles } from '../pages/playground/web-container-configuration/files-configuration';
import { useSegment } from '../components/providers/SegmentProvider';
import { REACT_EMAIL_CODE, WORKFLOW } from '../pages/playground/web-container-configuration';

const { WebContainer } = require('@webcontainer/api');

type ContainerState = {
Expand All @@ -33,7 +32,7 @@ type FileNames = 'workflow.ts' | 'react-email.tsx';

export const ContainerProvider: FCWithChildren = ({ children }) => {
const [code, setCode] = useState<Record<FileNames, string>>({
'workflow.ts': BRIDGE_CODE,
'workflow.ts': WORKFLOW,
'react-email.tsx': REACT_EMAIL_CODE,
});
const [isBridgeAppLoading, setIsBridgeAppLoading] = useState<boolean>(true);
Expand Down Expand Up @@ -126,15 +125,16 @@ export const ContainerProvider: FCWithChildren = ({ children }) => {
return await startOutput.exit;
}

await webContainer.mount(dynamicFiles(BRIDGE_CODE, REACT_EMAIL_CODE));
await webContainer?.mount(configureFiles(code['workflow.ts'], code['react-email.tsx']));

const installResult = await installDependencies();
if (installResult !== 0) {
writeOutput('Failed to install dependencies, please try again by refreshing the page.');
throw new Error('Failed to install dependencies');
}

writeOutput('Installed dependencies');
await new Promise((resolve) => setTimeout(resolve, 1000));
writeOutput('Installed dependencies \n');
await new Promise((resolve) => setTimeout(resolve, 0));
writeOutput('Starting Server');

const startServerResponse = await startDevServer();
Expand Down Expand Up @@ -188,10 +188,10 @@ export const ContainerProvider: FCWithChildren = ({ children }) => {
useEffect(() => {
let debounceTimeout;

if (BRIDGE_CODE !== code['workflow.ts'] || REACT_EMAIL_CODE !== code['react-email.tsx']) {
if (WORKFLOW !== code['workflow.ts'] || REACT_EMAIL_CODE !== code['react-email.tsx']) {
debounceTimeout = setTimeout(() => {
segment.track('Sandbox bridge app code was updated - [Playground]');
webContainer?.mount(dynamicFiles(code['workflow.ts'], code['react-email.tsx']));
webContainer?.mount(configureFiles(code['workflow.ts'], code['react-email.tsx']));
}, DEBOUNCE_DELAY);
}

Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/pages/auth/components/QuestionnaireForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import styled from '@emotion/styled/macro';
import { useSegment } from '../../../components/providers/SegmentProvider';
import { BRIDGE_SYNC_SAMPLE_ENDPOINT } from '../../../config/index';
import { QueryKeys } from '../../../api/query.keys';
import { useContainer } from '../../../studio/components/workflows/step-editor/editor/useContainer';
import { useWebContainerSupported } from '../../../hooks/useWebContainerSupport';
import { useContainer } from '../../../hooks/useContainer';

export function QuestionnaireForm() {
const queryClient = useQueryClient();
Expand Down
22 changes: 19 additions & 3 deletions apps/web/src/pages/get-started/GetStartedPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useSegment } from '../../components/providers/SegmentProvider';
import { useEffect } from 'react';
import { useCallback, useEffect } from 'react';
import { css } from '@novu/novui/css';
import { Stepper, Group } from '@mantine/core';

Expand All @@ -10,6 +10,8 @@ import { OnboardingStepsTimeline } from './OnboardingSteps';
import { stepperClassNames } from './GetStartedPage.styles';
import { onboardingTabs } from './form-tabs.config';
import { motion } from 'framer-motion';
import { navigatePlayground } from '../../utils';
import { OutlineButton } from '../../studio/components/OutlineButton';
const PAGE_TITLE = 'Get started with the Novu Flow';

export function GetStartedPage() {
Expand All @@ -19,6 +21,10 @@ export function GetStartedPage() {
segment.track('Page visit - [Get Started]');
}, [segment]);

const handleClick = useCallback(() => {
navigatePlayground();
}, []);

return (
<PageContainer>
<div
Expand All @@ -28,7 +34,17 @@ export function GetStartedPage() {
width: '100%',
})}
>
<Title className={css({ fontWeight: 'bold', marginBottom: '34px' })}>{PAGE_TITLE}</Title>
<div
className={css({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '34px',
})}
>
<Title className={css({ fontWeight: 'bold' })}>{PAGE_TITLE}</Title>
<OutlineButton onClick={handleClick}>Go back to playground</OutlineButton>
</div>
<StepperForm />
</div>
</PageContainer>
Expand Down Expand Up @@ -84,7 +100,7 @@ function StepperForm() {
Back
</Button>

{active !== 2 && (
{active !== onboardingTabs.length - 1 && (
<Button onClick={nextStep} variant="filled" disabled={active === 2}>
Next step
</Button>
Expand Down
21 changes: 7 additions & 14 deletions apps/web/src/pages/get-started/form-tabs.config.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import {
IconSettings,
IconLaptopMac,
IconOutlineCloudUpload,
IconOutlineRocketLaunch,
IconGroup,
} from '@novu/novui/icons';
import { IconLaptopMac, IconOutlineCloudUpload, IconGroup } from '@novu/novui/icons';
import { SetupTab } from './tabs/Setup';
import { CodeSnippet } from './legacy-onboarding/components/CodeSnippet';
import { css } from '@novu/novui/css';
import { DeployTab } from './tabs/Deploy';
import { collaborateSteps } from './tabs/Collaborate';
Expand All @@ -18,24 +11,24 @@ const iconStyles = css({

export const onboardingTabs = [
{
title: 'Build your first workflow',
description: 'Build and test your first workflow in Novu Studio',
icon: <IconLaptopMac className={iconStyles} />,
stepperTitle: 'Build',
content: <SetupTab />,
description: 'Build and test your first workflow in Novu Studio',
title: 'Build your first workflow',
},
{
title: 'Push your flow to the cloud',
description: 'Publish your changes to share with your team',
icon: <IconOutlineCloudUpload className={iconStyles} />,
stepperTitle: 'Deploy',
content: <DeployTab />,
description: 'Publish your changes to share with your team',
title: 'Push your flow to the cloud',
},
{
title: 'Collaborate with your team',
description: 'Enable your team members to modify notification content',
icon: <IconGroup className={iconStyles} />,
stepperTitle: 'Collaborate',
steps: collaborateSteps,
description: 'Enable your team members to modify notification content',
title: 'Collaborate with your team',
},
];
2 changes: 1 addition & 1 deletion apps/web/src/pages/get-started/tabs/Setup.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NextJSLogo, SvelteLogo, H3Logo, RemixLogo, ExpressLogo, NuxtLogo } from '../Logos';
import { motion } from 'framer-motion';
import { useState } from 'react';
import { Button, Code } from '@mantine/core';
import { Code } from '@mantine/core';
import { OnboardingStepsTimeline } from '../OnboardingSteps';
import { CodeSnippet } from '../legacy-onboarding/components/CodeSnippet';
import { useEnvironment } from '../../../hooks/useEnvironment';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
/* eslint-disable max-len */
import React, { useEffect, useRef, useState } from 'react';
import { css, cx } from '@emotion/css';
import styled from '@emotion/styled';
import { useEffect, useRef, useState } from 'react';
import { css } from '@emotion/css';
import { useMantineTheme } from '@mantine/core';
import { Editor, Monaco } from '@monaco-editor/react';
import { editor as NEditor, Range } from 'monaco-editor';
import { editor as NEditor } from 'monaco-editor';
import { Tabs } from '@mantine/core';

import { colors } from '@novu/design-system';
import { BrowserScreenWrapper } from '../../../../../pages/auth/onboarding/TitleBarWrapper';
import { BrowserScreenWrapper } from './TitleBarWrapper';

export function CodeEditor({
files,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { navigateToWorkflows } from '../../../utils/playground-navigation';
import { HStack } from '@novu/novui/jsx';
import { useStudioState } from '../../../studio/StudioStateProvider';
import { useDisclosure } from '@mantine/hooks';
import { IconPlayArrow, successMessage, Tooltip } from '@novu/design-system';
import { IconPlayArrow, successMessage, errorMessage, Tooltip } from '@novu/design-system';
import { ExecutionDetailsModalWrapper } from '../../templates/components/ExecutionDetailsModalWrapper';

export function Header({ handleTestClick }: { handleTestClick: () => Promise<any> }) {
Expand Down Expand Up @@ -101,9 +101,14 @@ const TriggerActionModal = ({
setIsLoading(true);
try {
const res = await handleTestClick();
successMessage('Workflow triggered successfully');

segment.track('Workflow triggered successfully - [Playground]');
if (res?.data?.status === 'processed') {
successMessage('Workflow triggered successfully');
segment.track('Workflow triggered successfully - [Playground]');
} else {
errorMessage('Workflow triggered unsuccessfully');
segment.track('Workflow triggered unsuccessfully - [Playground]');
}

setTransactionId(res.data.transactionId);
openExecutionModal();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,23 @@ import 'allotment/dist/style.css';
const { Pane } = Allotment;
const RootView = Allotment;
const EditorView = Allotment;

import { css } from '@novu/novui/css';
import { DiscoverStepOutput, DiscoverWorkflowOutput } from '@novu/framework';

import { useContainer } from '../../../studio/components/workflows/step-editor/editor/useContainer';
import { TerminalComponent } from '../../../studio/components/workflows/step-editor/editor/Terminal';
import { CodeEditor } from '../../../studio/components/workflows/step-editor/editor/CodeEditor';
import { TerminalComponent } from './Terminal';
import { CodeEditor } from './CodeEditor';
import { PlaygroundWorkflowComponent } from './PlaygroundWorkflowComponent';
import { useSegment } from '../../../components/providers/SegmentProvider';
import { useStudioState } from '../../../studio/StudioStateProvider';
import { useEffectOnce } from '../../../hooks/useEffectOnce';
import useThemeChange from '../../../hooks/useThemeChange';
import { useDiscover } from '../../../studio/hooks/useBridgeAPI';
import { DiscoverStepOutput, DiscoverWorkflowOutput } from '@novu/framework';
import { API_ROOT } from '../../../config/index';
import { useAPIKeys } from '../../../hooks/useApiKey';
import { TourGuideComponent } from './PlaygroundTourGuide';
import { Header } from './PlaygroundHeader';
import { useContainer } from '../../../hooks/useContainer';

export function PlaygroundPage() {
const { apiKey } = useAPIKeys();
Expand Down Expand Up @@ -175,6 +176,16 @@ function Playground({
const { code, setCode, terminalRef, isBridgeAppLoading } = useContainer();
const filteredCode = Object.fromEntries(Object.entries(code).filter(([key]) => key !== 'tunnel.ts'));

useEffectOnce(() => {
setTimeout(() => {
if (terminalRef.current) {
terminalRef.current.write(
'\nWelcome to the Novu Playground! Feel free to edit the code above and see the results in the editor on the right side\n\n'
);
}
}, 1000);
}, !isBridgeAppLoading);

const [editorSizes, setEditorSizes] = useState<number[]>([300, 200]);

function handleEditorSizeChange() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';

import type { DiscoverStepOutput, DiscoverWorkflowOutput } from '@novu/framework';
import { css, cx } from '@novu/novui/css';
Expand All @@ -9,7 +9,7 @@ import { WorkflowNodes } from '../../../studio/components/workflows/node-view/Wo
import { When } from '../../../components/utils/When';
import { HStack, VStack } from '@novu/novui/jsx';
import { StepNode } from '../../../studio/components/workflows/node-view/StepNode';
import { useBridgeAPI, useDiscover } from '../../../studio/hooks/useBridgeAPI';
import { useBridgeAPI } from '../../../studio/hooks/useBridgeAPI';
import { useControlsHandler } from '../../../hooks/workflow/useControlsHandler';
import { WorkflowsStepEditor } from '../../../components/workflow_v2/StepEditorComponent';
import { BackButton } from '../../../components/layout/components/LocalStudioHeader/BackButton';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, { useEffect, useImperativeHandle, useRef } from 'react';
import { Terminal } from '@xterm/xterm';
import { FitAddon } from 'xterm-addon-fit';
import { IconMenuBook, IconTerminal } from '@novu/novui/icons';
import 'xterm/css/xterm.css';

import { TerminalHandle } from './useContainer';
import { IconMenuBook, IconTerminal } from '@novu/novui/icons';
import { css } from '@novu/novui/css';
import { Button } from '@novu/novui';

import { TerminalHandle } from '../../../hooks/useContainer';

interface TerminalComponentProps {
onChange?: (data: string) => void;
height?: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { PACKAGE_JSON, PNPM_LOCK_YAML, TS_CONFIG, TUNNEL, VITE_CONFIG } from './sandbox-vite';

export const configureFiles = (workflowsCode: string, reactEmailCode: string) => {
return {
'workflows.ts': {
file: {
contents: workflowsCode,
},
},
'react-email.tsx': {
file: {
contents: reactEmailCode,
},
},
'tunnel.ts': {
file: {
contents: TUNNEL,
},
},
'vite.config.ts': {
file: {
contents: VITE_CONFIG,
},
},
'package.json': {
file: {
contents: PACKAGE_JSON,
},
},
'tsconfig.json': {
file: {
contents: TS_CONFIG,
},
},
'pnpm-lock.yaml': {
file: {
contents: PNPM_LOCK_YAML,
},
},
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './sandbox-vite';
Loading

0 comments on commit 55ebfb5

Please sign in to comment.