From c623832736ac78f6e9d804cfc1e1f65766e5eb1f Mon Sep 17 00:00:00 2001 From: Srinaath Ravichandran Date: Wed, 7 Apr 2021 22:20:58 -0700 Subject: [PATCH 01/11] Runtime log data websocket Signed-off-by: Srinaath Ravichandran --- .../BotRuntimeController/BotErrorViewer.tsx | 4 +- .../BotRuntimeController/BotRuntimeStatus.tsx | 5 +- .../BotRuntimeController/ErrorCallout.tsx | 21 +- .../BotRuntimeController/useBotOperations.ts | 8 +- .../BotRuntimeStatus.test.tsx | 4 +- .../BotStatusIndicator.test.tsx | 4 +- Composer/packages/client/src/constants.ts | 3 + ...utsTabContent.tsx => OutputTabContent.tsx} | 0 ...abLogHeader.tsx => OutputTabLogHeader.tsx} | 2 +- .../RuntimeOutputLog/RuntimeOutputLog.tsx | 101 ++++- .../TabExtensions/RuntimeOutputLog/index.ts | 4 +- .../__tests__/RuntimeOutputLog.test.tsx | 4 +- .../client/src/recoilModel/atoms/botState.ts | 14 +- .../src/recoilModel/dispatchers/builder.ts | 6 +- .../src/recoilModel/dispatchers/publisher.ts | 54 ++- .../packages/client/src/recoilModel/types.ts | 4 +- .../client/src/utils/runtimeErrors.ts | 20 + .../server/src/controllers/publisher.ts | 20 + .../packages/server/src/locales/en-US.json | 9 +- Composer/packages/server/src/router/api.ts | 1 + Composer/packages/types/src/publish.ts | 2 - extensions/localPublish/package.json | 7 +- .../localPublish/src/WebSocketServer.ts | 83 ++++ extensions/localPublish/src/index.ts | 49 ++- extensions/localPublish/src/socketPort.ts | 20 + extensions/localPublish/yarn.lock | 386 +++++++++++++++++- 26 files changed, 724 insertions(+), 111 deletions(-) rename Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/{OutputsTabContent.tsx => OutputTabContent.tsx} (100%) rename Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/{OutputsTabLogHeader.tsx => OutputTabLogHeader.tsx} (94%) create mode 100644 Composer/packages/client/src/utils/runtimeErrors.ts create mode 100644 extensions/localPublish/src/WebSocketServer.ts create mode 100644 extensions/localPublish/src/socketPort.ts diff --git a/Composer/packages/client/src/components/BotRuntimeController/BotErrorViewer.tsx b/Composer/packages/client/src/components/BotRuntimeController/BotErrorViewer.tsx index 6b96f98690..9398e8bb53 100644 --- a/Composer/packages/client/src/components/BotRuntimeController/BotErrorViewer.tsx +++ b/Composer/packages/client/src/components/BotRuntimeController/BotErrorViewer.tsx @@ -9,7 +9,7 @@ import { ActionButton } from 'office-ui-fabric-react/lib/Button'; import formatMessage from 'format-message'; import { CommunicationColors } from '@uifabric/fluent-theme'; -import { botRuntimeErrorState, dispatcherState } from '../../recoilModel'; +import { botBuildTimeErrorState, dispatcherState } from '../../recoilModel'; type BotErrorViewerProps = { projectId: string; @@ -17,7 +17,7 @@ type BotErrorViewerProps = { export const BotErrorViewer: React.FC = ({ projectId }) => { const { setActiveTabInDebugPanel, setDebugPanelExpansion } = useRecoilValue(dispatcherState); - const botRuntimeErrorMsg = useRecoilValue(botRuntimeErrorState(projectId)); + const botRuntimeErrorMsg = useRecoilValue(botBuildTimeErrorState(projectId)); const openErrorDialog = () => { setActiveTabInDebugPanel('RuntimeLog'); diff --git a/Composer/packages/client/src/components/BotRuntimeController/BotRuntimeStatus.tsx b/Composer/packages/client/src/components/BotRuntimeController/BotRuntimeStatus.tsx index 32d90cbc27..4dbfe5766d 100644 --- a/Composer/packages/client/src/components/BotRuntimeController/BotRuntimeStatus.tsx +++ b/Composer/packages/client/src/components/BotRuntimeController/BotRuntimeStatus.tsx @@ -51,11 +51,8 @@ export const BotRuntimeStatus = React.memo((props: BotRuntimeStatusProps) => { case BotStatus.connected: { if (isRunning) { - setTimeout(() => { - getPublishStatus(projectId, defaultPublishConfig); - }, pollingInterval); + return; } - setIntervalRunning(false); TelemetryClient.track('StartBotCompleted', { projectId, status: currentBotStatus }); break; } diff --git a/Composer/packages/client/src/components/BotRuntimeController/ErrorCallout.tsx b/Composer/packages/client/src/components/BotRuntimeController/ErrorCallout.tsx index 49886b5171..eb2e9ef313 100644 --- a/Composer/packages/client/src/components/BotRuntimeController/ErrorCallout.tsx +++ b/Composer/packages/client/src/components/BotRuntimeController/ErrorCallout.tsx @@ -3,21 +3,18 @@ /** @jsx jsx */ import { jsx, css } from '@emotion/core'; +import { SharedColors } from '@uifabric/fluent-theme'; import { Link } from 'office-ui-fabric-react/lib/Link'; import { FontWeights } from 'office-ui-fabric-react/lib/Styling'; -import { FontSizes } from '@uifabric/fluent-theme'; + +import { getDefaultFontSettings } from '../../recoilModel/utils/fontUtil'; // -------------------- Styles -------------------- // const calloutLabel = css` - font-size: ${FontSizes.size18}; font-weight: ${FontWeights.bold}; `; -const calloutContainer = css` - padding: 10px; -`; - const calloutDescription = css` word-break: break-word; `; @@ -81,6 +78,8 @@ fieldsWhiteList.set('code', { visible: false, name: 'code' }); fieldsWhiteList.set('signal', { visible: false, name: 'signal' }); fieldsWhiteList.set('stderr', { visible: false, name: 'stderr' }); +const DEFAULT_FONT_SETTINGS = getDefaultFontSettings(); + export const ErrorCallout: React.FC = (props) => { const { error } = props; @@ -137,7 +136,15 @@ export const ErrorCallout: React.FC = (props) => { }; return ( -
+

{error.title}

{buildErrorMessage(error)} diff --git a/Composer/packages/client/src/components/BotRuntimeController/useBotOperations.ts b/Composer/packages/client/src/components/BotRuntimeController/useBotOperations.ts index 544df576ec..163ab8aebf 100644 --- a/Composer/packages/client/src/components/BotRuntimeController/useBotOperations.ts +++ b/Composer/packages/client/src/components/BotRuntimeController/useBotOperations.ts @@ -16,9 +16,11 @@ export function useBotOperations() { const botRuntimeOperations = useRecoilValue(botRuntimeOperationsSelector); const rootBotId = useRecoilValue(rootBotProjectIdSelector); const [trackedProjectIds, setProjectsToTrack] = useState([]); - const { updateSettingsForSkillsWithoutManifest, resetBotRuntimeError, setBotStatus } = useRecoilValue( - dispatcherState - ); + const { + updateSettingsForSkillsWithoutManifest, + resetBotRuntimeLog: resetBotRuntimeError, + setBotStatus, + } = useRecoilValue(dispatcherState); const handleBotStart = async ( projectId: string, diff --git a/Composer/packages/client/src/components/__tests__/BotRuntimeController/BotRuntimeStatus.test.tsx b/Composer/packages/client/src/components/__tests__/BotRuntimeController/BotRuntimeStatus.test.tsx index 9f1e68fb36..e246a41bd2 100644 --- a/Composer/packages/client/src/components/__tests__/BotRuntimeController/BotRuntimeStatus.test.tsx +++ b/Composer/packages/client/src/components/__tests__/BotRuntimeController/BotRuntimeStatus.test.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import httpClient from '../../../utils/httpUtil'; import { renderWithRecoil } from '../../../../__tests__/testUtils/renderWithRecoil'; -import { botRuntimeErrorState, botStatusState } from '../../../recoilModel'; +import { botBuildTimeErrorState, botStatusState } from '../../../recoilModel'; import { BotStatus } from '../../../constants'; import { BotRuntimeStatus } from '../../BotRuntimeController/BotRuntimeStatus'; @@ -100,7 +100,7 @@ describe('', () => { it('should show error if bot start failed', async () => { const { findByText } = renderWithRecoil(, ({ set }) => { set(botStatusState(projectId), BotStatus.failed); - set(botRuntimeErrorState(projectId), { + set(botBuildTimeErrorState(projectId), { title: 'Error', message: 'Failed to bind to port 3979', }); diff --git a/Composer/packages/client/src/components/__tests__/BotRuntimeController/BotStatusIndicator.test.tsx b/Composer/packages/client/src/components/__tests__/BotRuntimeController/BotStatusIndicator.test.tsx index c9359a002d..fdf9ef28c0 100644 --- a/Composer/packages/client/src/components/__tests__/BotRuntimeController/BotStatusIndicator.test.tsx +++ b/Composer/packages/client/src/components/__tests__/BotRuntimeController/BotStatusIndicator.test.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { renderWithRecoil } from '../../../../__tests__/testUtils/renderWithRecoil'; -import { botRuntimeErrorState, botStatusState } from '../../../recoilModel'; +import { botBuildTimeErrorState, botStatusState } from '../../../recoilModel'; import { BotStatus, BotStatusesCopy } from '../../../constants'; import { BotStatusIndicator } from '../../BotRuntimeController/BotStatusIndicator'; @@ -41,7 +41,7 @@ describe('', () => { it('should show error if bot start failed', async () => { const { findByText } = renderWithRecoil(, ({ set }) => { set(botStatusState(projectId), BotStatus.failed); - set(botRuntimeErrorState(projectId), { + set(botBuildTimeErrorState(projectId), { title: 'Error', message: 'Failed to bind to port 3979', }); diff --git a/Composer/packages/client/src/constants.ts b/Composer/packages/client/src/constants.ts index a741cf6bb3..d4dc2db50a 100644 --- a/Composer/packages/client/src/constants.ts +++ b/Composer/packages/client/src/constants.ts @@ -95,6 +95,9 @@ export const Text = { get DOTNETFAILURE() { return formatMessage('Composer needs .NET Core SDK'); }, + get BOTRUNTIMEERROR() { + return formatMessage('Composer Runtime Error'); + }, }; export enum LuisConfig { diff --git a/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/OutputsTabContent.tsx b/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/OutputTabContent.tsx similarity index 100% rename from Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/OutputsTabContent.tsx rename to Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/OutputTabContent.tsx diff --git a/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/OutputsTabLogHeader.tsx b/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/OutputTabLogHeader.tsx similarity index 94% rename from Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/OutputsTabLogHeader.tsx rename to Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/OutputTabLogHeader.tsx index 08d0501ea7..8feb4e93a6 100644 --- a/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/OutputsTabLogHeader.tsx +++ b/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/OutputTabLogHeader.tsx @@ -22,7 +22,7 @@ export const OutputsTabLogHeader: React.FC = () => { marginRight: '4px', }} > - {formatMessage('Outputs')} + {formatMessage('Output')}

); diff --git a/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/RuntimeOutputLog.tsx b/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/RuntimeOutputLog.tsx index a35bb00c5f..aa05d8d790 100644 --- a/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/RuntimeOutputLog.tsx +++ b/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/RuntimeOutputLog.tsx @@ -3,15 +3,19 @@ /** @jsx jsx */ import { jsx } from '@emotion/core'; -import { NeutralColors, SharedColors } from '@uifabric/fluent-theme'; +import { NeutralColors } from '@uifabric/fluent-theme'; import { useRecoilValue } from 'recoil'; import { default as AnsiUp } from 'ansi_up'; import { useEffect, useRef } from 'react'; import sanitizeHtml from 'sanitize-html'; -import { botRuntimeErrorState, botRuntimeLogState } from '../../../../../recoilModel'; +import { botBuildTimeErrorState, dispatcherState, runtimeStandardOutputDataState } from '../../../../../recoilModel'; import { getDefaultFontSettings } from '../../../../../recoilModel/utils/fontUtil'; +import httpClient from '../../../../../utils/httpUtil'; import { ErrorCallout } from '../../../../../components/BotRuntimeController/ErrorCallout'; +import { checkIfDotnetVersionMissing, missingDotnetVersionError } from '../../../../../utils/runtimeErrors'; +import { BotStartError } from '../../../../../recoilModel/types'; +import { Text } from '../../../../../constants'; const ansiUp = new AnsiUp(); const DEFAULT_FONT_SETTINGS = getDefaultFontSettings(); @@ -21,15 +25,71 @@ const createMarkup = (txt: string) => { }; export const RuntimeOutputLog: React.FC<{ projectId: string }> = ({ projectId }) => { - const runtimeLogs = useRecoilValue(botRuntimeLogState(projectId)); - const botRuntimeErrors = useRecoilValue(botRuntimeErrorState(projectId)); + const runtimeData = useRecoilValue(runtimeStandardOutputDataState(projectId)); + const botBuildErrors = useRecoilValue(botBuildTimeErrorState(projectId)); + const { setRuntimeStandardOutputData } = useRecoilValue(dispatcherState); + const runtimeLogsContainerRef = useRef(null); + const runtimeTrafficChannel = useRef(null); + useEffect(() => { if (runtimeLogsContainerRef?.current) { runtimeLogsContainerRef.current.scrollTop = runtimeLogsContainerRef.current.scrollHeight; } - }, [runtimeLogs, botRuntimeErrors]); + }, [runtimeData]); + + useEffect(() => { + const setupLogConnection = async () => { + try { + const runtimeStreamUrl = await httpClient.get(`/publish/runtimeLogUrl/${projectId}`); + runtimeTrafficChannel.current = new WebSocket(runtimeStreamUrl.data); + if (runtimeTrafficChannel.current) { + runtimeTrafficChannel.current.onmessage = (event) => { + try { + const data: { standardError: string; standardOutput: string } = JSON.parse(event.data); + + let standardError: BotStartError | null = null; + if (data.standardError) { + const isDotnetError = checkIfDotnetVersionMissing({ + message: data.standardError ?? '', + }); + + if (isDotnetError) { + standardError = { + title: Text.DOTNETFAILURE, + ...missingDotnetVersionError, + }; + } else { + standardError = { + title: Text.BOTRUNTIMEERROR, + message: data.standardError, + }; + } + } + setRuntimeStandardOutputData(projectId, { + standardError, + standardOutput: data.standardOutput, + }); + } catch (ex) { + // // No need handle the exception here. The old state can continue to exist. + } + }; + } + } catch (ex) { + // No need handle the exception here. The Outputs window would be empty + } + }; + + if (!runtimeTrafficChannel.current) { + setupLogConnection(); + } + + return () => { + runtimeTrafficChannel.current?.close(); + runtimeTrafficChannel.current = null; + }; + }, []); return (
= ({ projectId }) }} data-testid="Runtime-Output-Logs" > -
-
- -
+ {runtimeData.standardOutput && ( +
+ )} + {botBuildErrors && } + {runtimeData.standardError && }
); }; diff --git a/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/index.ts b/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/index.ts index 79a4727145..7120f31f93 100644 --- a/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/index.ts +++ b/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/index.ts @@ -5,8 +5,8 @@ import formatMessage from 'format-message'; import { TabExtensionConfig, RuntimeLogTabKey } from '../types'; -import { OutputsTabContent } from './OutputsTabContent'; -import { OutputsTabLogHeader } from './OutputsTabLogHeader'; +import { OutputsTabContent } from './OutputTabContent'; +import { OutputsTabLogHeader } from './OutputTabLogHeader'; export const RuntimeOutputTabConfig: TabExtensionConfig = { key: RuntimeLogTabKey, diff --git a/Composer/packages/client/src/pages/design/__tests__/RuntimeOutputLog.test.tsx b/Composer/packages/client/src/pages/design/__tests__/RuntimeOutputLog.test.tsx index bb58f2f6d5..5441b1e74c 100644 --- a/Composer/packages/client/src/pages/design/__tests__/RuntimeOutputLog.test.tsx +++ b/Composer/packages/client/src/pages/design/__tests__/RuntimeOutputLog.test.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { renderWithRecoil } from '../../../../__tests__/testUtils/renderWithRecoil'; -import { botRuntimeErrorState, botRuntimeLogState } from '../../../recoilModel'; +import { botBuildTimeErrorState } from '../../../recoilModel'; import { RuntimeOutputLog } from '../DebugPanel/TabExtensions/RuntimeOutputLog/RuntimeOutputLog'; const projectId = '123-avw'; @@ -21,7 +21,7 @@ describe('', () => { it('should render Runtime errors', async () => { const { findByText } = renderWithRecoil(, ({ set }) => { - set(botRuntimeErrorState(projectId), { + set(botBuildTimeErrorState(projectId), { message: '.NET 3.1 needs to be installed', title: '.NET runtime error', }); diff --git a/Composer/packages/client/src/recoilModel/atoms/botState.ts b/Composer/packages/client/src/recoilModel/atoms/botState.ts index 54fc8338ba..171beda8dc 100644 --- a/Composer/packages/client/src/recoilModel/atoms/botState.ts +++ b/Composer/packages/client/src/recoilModel/atoms/botState.ts @@ -21,7 +21,7 @@ import { import { ConversationTrafficItem } from '@botframework-composer/types'; import { atomFamily } from 'recoil'; -import { BotRuntimeError, DesignPageLocation, WebChatInspectionData } from '../../recoilModel/types'; +import { BotStartError, DesignPageLocation, WebChatInspectionData, RuntimeOutputData } from '../../recoilModel/types'; import FilePersistence from '../persistence/FilePersistence'; import { BotStatus } from './../../constants'; @@ -214,7 +214,7 @@ export const botDiagnosticsState = atomFamily({ }, }); -export const botRuntimeErrorState = atomFamily({ +export const botBuildTimeErrorState = atomFamily({ key: getFullyQualifiedKey('botLoadErrorMsg'), default: (id) => { return { title: '', message: '' }; @@ -334,6 +334,7 @@ export const projectMetaDataState = atomFamily<{ isRootBot: boolean; isRemote: b return { isRootBot: false, isRemote: false, + runtimeLogStreamingUrl: '', }; }, }); @@ -445,7 +446,10 @@ export const projectIndexingState = atomFamily({ default: false, }); -export const botRuntimeLogState = atomFamily({ - key: getFullyQualifiedKey('botRuntimeLogState'), - default: '', +export const runtimeStandardOutputDataState = atomFamily({ + key: getFullyQualifiedKey('runtimeStandardOutputData'), + default: { + standardError: '', + standardOutput: '', + }, }); diff --git a/Composer/packages/client/src/recoilModel/dispatchers/builder.ts b/Composer/packages/client/src/recoilModel/dispatchers/builder.ts index bbf82ccdf8..4f07c26317 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/builder.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/builder.ts @@ -11,7 +11,7 @@ import { Text, BotStatus } from '../../constants'; import httpClient from '../../utils/httpUtil'; import luFileStatusStorage from '../../utils/luFileStatusStorage'; import qnaFileStatusStorage from '../../utils/qnaFileStatusStorage'; -import { botStatusState, botRuntimeErrorState } from '../atoms'; +import { botStatusState, botBuildTimeErrorState } from '../atoms'; import { dialogsWithLuProviderSelectorFamily, luFilesSelectorFamily, qnaFilesSelectorFamily } from '../selectors'; const checkEmptyQuestionOrAnswerInQnAFile = (sections) => { @@ -46,7 +46,7 @@ export const builderDispatcher = () => { { title: Text.LUISDEPLOYFAILURE, message: '' } ); if (errorMsg.message) { - set(botRuntimeErrorState(projectId), errorMsg); + set(botBuildTimeErrorState(projectId), errorMsg); set(botStatusState(projectId), BotStatus.failed); return; } @@ -64,7 +64,7 @@ export const builderDispatcher = () => { set(botStatusState(projectId), BotStatus.published); } catch (err) { set(botStatusState(projectId), BotStatus.failed); - set(botRuntimeErrorState(projectId), { + set(botBuildTimeErrorState(projectId), { title: Text.LUISDEPLOYFAILURE, message: err.response?.data?.message || err.message, }); diff --git a/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts b/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts index 34f85c7b57..20bca9e2be 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts @@ -10,11 +10,11 @@ import { publishTypesState, botStatusState, publishHistoryState, - botRuntimeErrorState, + botBuildTimeErrorState, isEjectRuntimeExistState, filePersistenceState, settingsState, - botRuntimeLogState, + runtimeStandardOutputDataState, } from '../atoms/botState'; import { openInEmulator } from '../../utils/navigation'; import { botEndpointsState } from '../atoms'; @@ -27,6 +27,8 @@ import { import * as luUtil from '../../utils/luUtil'; import * as qnaUtil from '../../utils/qnaUtil'; import { ClientStorage } from '../../utils/storage'; +import { checkIfDotnetVersionMissing, missingDotnetVersionError } from '../../utils/runtimeErrors'; +import { RuntimeOutputData } from '../types'; import { BotStatus, Text } from './../../constants'; import httpClient from './../../utils/httpUtil'; @@ -37,29 +39,13 @@ const PUBLISH_SUCCESS = 200; const PUBLISH_PENDING = 202; const PUBLISH_FAILED = 500; -const missingDotnetVersionError = { - message: formatMessage('To run this bot, Composer needs .NET Core SDK.'), - linkAfterMessage: { - text: formatMessage('Learn more.'), - url: 'https://aka.ms/install-composer', - }, - link: { - text: formatMessage('Install Microsoft .NET Core SDK'), - url: 'https://dotnet.microsoft.com/download/dotnet-core/3.1', - }, -}; - -const checkIfDotnetVersionMissing = (err: any) => { - return /(Command failed: dotnet user-secrets)|(install[\w\r\s\S\t\n]*\.NET Core SDK)/.test(err.message as string); -}; - export const publishStorage = new ClientStorage(window.sessionStorage, 'publish'); export const publisherDispatcher = () => { const publishFailure = async ({ set }: CallbackInterface, title: string, error, target, projectId: string) => { if (target.name === defaultPublishConfig.name) { set(botStatusState(projectId), BotStatus.failed); - set(botRuntimeErrorState(projectId), { ...error, title }); + set(botBuildTimeErrorState(projectId), { ...error, title }); } // prepend the latest publish results to the history @@ -117,9 +103,6 @@ export const publisherDispatcher = () => { // the action below only applies to when a bot is being started using the "start bot" button // a check should be added to this that ensures this ONLY applies to the "default" profile. if (target.name === defaultPublishConfig.name) { - if (data.runtimeLog) { - set(botRuntimeLogState(projectId), data.runtimeLog); - } if (status === PUBLISH_SUCCESS && endpointURL) { const rootBotId = await snapshot.getPromise(rootBotProjectIdSelector); if (rootBotId === projectId) { @@ -143,11 +126,7 @@ export const publisherDispatcher = () => { set(botStatusState(projectId), BotStatus.starting); } else if (status === PUBLISH_FAILED) { set(botStatusState(projectId), BotStatus.failed); - if (checkIfDotnetVersionMissing(data)) { - set(botRuntimeErrorState(projectId), { title: Text.DOTNETFAILURE, ...missingDotnetVersionError }); - return; - } - set(botRuntimeErrorState(projectId), { ...data, title: formatMessage('Start bot failed') }); + set(botBuildTimeErrorState(projectId), { ...data, title: formatMessage('Start bot failed') }); } } @@ -310,10 +289,10 @@ export const publisherDispatcher = () => { } ); - const resetBotRuntimeError = useRecoilCallback((callbackHelpers: CallbackInterface) => async (projectId: string) => { + const resetBotRuntimeLog = useRecoilCallback((callbackHelpers: CallbackInterface) => async (projectId: string) => { const { reset } = callbackHelpers; - reset(botRuntimeErrorState(projectId)); - reset(botRuntimeLogState(projectId)); + reset(botBuildTimeErrorState(projectId)); + reset(runtimeStandardOutputDataState(projectId)); }); const openBotInEmulator = useRecoilCallback((callbackHelpers: CallbackInterface) => async (projectId: string) => { @@ -333,6 +312,18 @@ export const publisherDispatcher = () => { } }); + const setRuntimeStandardOutputData = useRecoilCallback( + (callbackHelpers: CallbackInterface) => async (projectId: string, data: RuntimeOutputData) => { + const { set } = callbackHelpers; + try { + set(runtimeStandardOutputDataState(projectId), data); + } catch (err) { + setError(callbackHelpers, err); + logMessage(callbackHelpers, err.message); + } + } + ); + return { getPublishTargetTypes, publishToTarget, @@ -343,6 +334,7 @@ export const publisherDispatcher = () => { getPublishHistory, setEjectRuntimeExist, openBotInEmulator, - resetBotRuntimeError, + resetBotRuntimeLog, + setRuntimeStandardOutputData, }; }; diff --git a/Composer/packages/client/src/recoilModel/types.ts b/Composer/packages/client/src/recoilModel/types.ts index 82070d1b05..ee1da96e23 100644 --- a/Composer/packages/client/src/recoilModel/types.ts +++ b/Composer/packages/client/src/recoilModel/types.ts @@ -61,7 +61,7 @@ export interface RuntimeTemplate { startCommand: string; } -export interface BotRuntimeError { +export interface BotStartError { title: string; message: string; linkAfterMessage?: { url: string; text: string }; @@ -127,3 +127,5 @@ export type WebChatInspectionData = { item: ConversationTrafficItem; mode?: TrafficInspectionMode; }; + +export type RuntimeOutputData = { standardOutput: string; standardError: BotStartError | null }; diff --git a/Composer/packages/client/src/utils/runtimeErrors.ts b/Composer/packages/client/src/utils/runtimeErrors.ts new file mode 100644 index 0000000000..dcf04e2fc4 --- /dev/null +++ b/Composer/packages/client/src/utils/runtimeErrors.ts @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import formatMessage from 'format-message'; + +export const missingDotnetVersionError = { + message: formatMessage('To run this bot, Composer needs .NET Core SDK.'), + linkAfterMessage: { + text: formatMessage('Learn more.'), + url: 'https://aka.ms/install-composer', + }, + link: { + text: formatMessage('Install Microsoft .NET Core SDK'), + url: 'https://dotnet.microsoft.com/download/dotnet-core/3.1', + }, +}; + +export const checkIfDotnetVersionMissing = (err: { message: string }) => { + return /(Command failed: dotnet user-secrets)|(install[\w\r\s\S\t\n]*\.NET Core SDK)/.test(err.message as string); +}; diff --git a/Composer/packages/server/src/controllers/publisher.ts b/Composer/packages/server/src/controllers/publisher.ts index 660c79fe54..4b4880b1cd 100644 --- a/Composer/packages/server/src/controllers/publisher.ts +++ b/Composer/packages/server/src/controllers/publisher.ts @@ -331,6 +331,26 @@ export const PublishController = { message: `${extensionName} is not a valid publishing target type. There may be a missing plugin.`, }); }, + setupRuntimeLogForBot: async (req, res) => { + log('Setting up runtime log server'); + const profile = defaultPublishConfig; + const extensionName = profile.type; + const projectId = req.params.projectId; + if (profile && extensionImplementsMethod(extensionName, 'setupRuntimeLogServer') && projectId) { + const pluginMethod = ExtensionContext.extensions.publish[extensionName].methods.setupRuntimeLogServer; + if (typeof pluginMethod === 'function') { + try { + const runtimeLogUrl = await pluginMethod.call(null, projectId); + return res.status(200).send(runtimeLogUrl); + } catch (ex) { + res.status(400).json({ + statusCode: '400', + message: `${extensionName} is not a valid publishing target type. There may be a missing plugin.`, + }); + } + } + } + }, pull: async (req, res) => { log('Starting pull'); diff --git a/Composer/packages/server/src/locales/en-US.json b/Composer/packages/server/src/locales/en-US.json index e314109a3b..817441948b 100644 --- a/Composer/packages/server/src/locales/en-US.json +++ b/Composer/packages/server/src/locales/en-US.json @@ -602,9 +602,6 @@ "cannot_parse_attachment_c3e552a5": { "message": "Cannot parse attachment." }, - "cannot_post_activity_conversation_not_found_c1e26d2d": { - "message": "Cannot post activity. Conversation not found." - }, "cannot_upload_file_conversation_not_found_8a983504": { "message": "Cannot upload file. Conversation not found." }, @@ -2483,6 +2480,9 @@ "no_uploads_were_attached_as_a_part_of_the_request_63e92f54": { "message": "No uploads were attached as a part of the request." }, + "no_web_chat_activity_yet_4227dc1a": { + "message": "No Web Chat activity yet." + }, "no_wildcard_ff439e76": { "message": "no wildcard" }, @@ -3956,9 +3956,6 @@ "web_chat_c5ca7ab6": { "message": "Web Chat" }, - "webchat_inspector_4d0dfeb7": { - "message": "Webchat Inspector" - }, "webchat_log_b7213a9e": { "message": "Webchat log." }, diff --git a/Composer/packages/server/src/router/api.ts b/Composer/packages/server/src/router/api.ts index dcbf05d18c..5ffbbe5be0 100644 --- a/Composer/packages/server/src/router/api.ts +++ b/Composer/packages/server/src/router/api.ts @@ -74,6 +74,7 @@ router.get('/provision/:projectId/:type/resources', ProvisionController.getResou router.post('/provision/:projectId/:type', ProvisionController.provision); // publishing +router.get('/publish/runtimeLogUrl/:projectId', PublishController.setupRuntimeLogForBot); router.get('/publish/types', PublishController.getTypes); router.get('/publish/:projectId/status/:target/:jobId', PublishController.status); router.get('/publish/:projectId/status/:target', PublishController.status); diff --git a/Composer/packages/types/src/publish.ts b/Composer/packages/types/src/publish.ts index 9261661978..909e47e07c 100644 --- a/Composer/packages/types/src/publish.ts +++ b/Composer/packages/types/src/publish.ts @@ -10,8 +10,6 @@ import { AuthParameters } from './auth'; export type PublishResult = { message: string; - /** for local runtime output */ - runtimeLog?: string; /** for azure or pva publish */ comment?: string; eTag?: string; diff --git a/extensions/localPublish/package.json b/extensions/localPublish/package.json index 8868ab465e..f6dce7456e 100644 --- a/extensions/localPublish/package.json +++ b/extensions/localPublish/package.json @@ -13,6 +13,8 @@ "@botframework-composer/types": "file:../../Composer/packages/types", "adm-zip": "^0.4.14", "archiver": "^5.0.2", + "express": "^4.17.1", + "express-serve-static-core": "^0.1.1", "globby": "^11.0.0", "kill-port": "^1.6.1", "lodash": "^4.17.20", @@ -20,13 +22,16 @@ "portfinder": "^1.0.28", "rimraf": "^3.0.2", "tcp-port-used": "^1.0.1", - "uuid": "^7.0.1" + "uuid": "^7.0.1", + "ws": "^7.4.4" }, "resolutions": { "@botframework-composer/types": "file:../../Composer/packages/types", "bl": "^4.0.3" }, "devDependencies": { + "@types/express": "^4.16.1", + "@types/express-serve-static-core": "^4.16.1", "@types/lodash": "^4.14.162", "@types/node": "^14.11.8", "@types/rimraf": "^3.0.0", diff --git a/extensions/localPublish/src/WebSocketServer.ts b/extensions/localPublish/src/WebSocketServer.ts new file mode 100644 index 0000000000..e490db824a --- /dev/null +++ b/extensions/localPublish/src/WebSocketServer.ts @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import http from 'http'; + +import portfinder from 'portfinder'; +import express, { Request, Response } from 'express'; +import { Server as WSServer } from 'ws'; + +interface WebSocket { + close(): void; + send(data: string, cb?: (err?: Error) => void): void; +} + +export class WebSocketServer { + private static restServer: http.Server; + private static servers: WSServer = {}; + private static sockets: Record = {}; + private static port: number; + + public static getRuntimeLogStreamingUrl(projectId: string): string { + return `ws://localhost:${this.port}/ws/runtimeLog/${projectId}`; + } + + public static async init(): Promise { + if (!this.restServer) { + const app = express(); + this.restServer = http.createServer(app); + + this.restServer.on('upgrade', (req, socket, head) => { + req.claimUpgrade = () => ({ + head, + socket, + }); + const res: any = new http.ServerResponse(req); + return app(req, res); + }); + const port = await portfinder.getPortPromise(); + this.restServer.listen(port); + + app.use('/ws/runtimeLog/:projectId', (req: Request, res: Response) => { + if (!(req as any).claimUpgrade) { + return (res as any).status(426).send('Connection must upgrade for web sockets.'); + } + + const projectId = (req as any).params.projectId; + // initialize a new web socket server for each new conversation + if (projectId && !this.servers[projectId]) { + const { head, socket } = (req as any).claimUpgrade(); + + const wsServer = new WSServer({ + noServer: true, + }); + + wsServer.on('connection', (socket, req) => { + this.sockets[projectId] = socket; + socket.on('close', () => { + delete this.servers[projectId]; + delete this.sockets[projectId]; + }); + }); + + // upgrade the connection to a ws connection + wsServer.handleUpgrade(req as any, socket, head, (socket) => { + wsServer.emit('connection', socket, req); + }); + this.servers[projectId] = wsServer; + } + }); + this.port = port; + return this.port; + } + } + + public static sendRuntimeLogToSubscribers(projectId: string, standardOutput: string, standardError: string): void { + this.sockets[projectId]?.send( + JSON.stringify({ + standardOutput: standardOutput ?? '', + standardError: standardError ?? '', + }) + ); + } +} diff --git a/extensions/localPublish/src/index.ts b/extensions/localPublish/src/index.ts index a9bf9479da..3eefe1a982 100644 --- a/extensions/localPublish/src/index.ts +++ b/extensions/localPublish/src/index.ts @@ -18,6 +18,8 @@ import killPort from 'kill-port'; import map from 'lodash/map'; import * as tcpPortUsed from 'tcp-port-used'; +import { WebSocketServer } from './WebSocketServer'; + const removeDirAndFiles = promisify(rimraf); const mkdir = promisify(fs.mkdir); const readFile = promisify(fs.readFile); @@ -32,6 +34,7 @@ interface RunningBot { result: { message: string; runtimeLog?: string; + runtimeError?: string; }; } interface PublishConfig { @@ -94,20 +97,35 @@ class LocalPublisher implements PublishPlugin { message: data.result.message, }; } - LocalPublisher.runningBots[botId] = updatedBotData; }; - private appendRuntimeLogs = (botId: string, newContent: string) => { + private appendRuntimeLogs = (botId: string, newContent: string, logType: 'stdout' | 'stderr' = 'stdout') => { const botData = LocalPublisher.runningBots[botId]; - const runtimeLog = botData.result.runtimeLog ? botData.result.runtimeLog + newContent : newContent; - LocalPublisher.runningBots[botId] = { - ...botData, - result: { - ...botData.result, - runtimeLog, - }, - }; + if (logType === 'stdout') { + const runtimeLog = botData.result.runtimeLog ? botData.result.runtimeLog + newContent : newContent; + LocalPublisher.runningBots[botId] = { + ...botData, + result: { + ...botData.result, + runtimeLog, + }, + }; + } else { + const runtimeError = botData.result.runtimeError ? botData.result.runtimeError + newContent : newContent; + LocalPublisher.runningBots[botId] = { + ...botData, + result: { + ...botData.result, + runtimeError: runtimeError, + }, + }; + } + WebSocketServer.sendRuntimeLogToSubscribers( + botId, + LocalPublisher.runningBots[botId].result.runtimeLog, + LocalPublisher.runningBots[botId].result.runtimeError + ); }; private publishAsync = async (botId: string, version: string, fullSettings: any, project: any, user) => { @@ -188,6 +206,7 @@ class LocalPublisher implements PublishPlugin { }; getStatus = async (config: PublishConfig, project, user) => { const botId = project.id; + const runtimeLogStreamingUrl = WebSocketServer.getRuntimeLogStreamingUrl(botId); if (LocalPublisher.runningBots[botId]) { if (LocalPublisher.runningBots[botId].status === 200) { const port = LocalPublisher.runningBots[botId].port; @@ -197,13 +216,14 @@ class LocalPublisher implements PublishPlugin { result: { message: 'Running', endpointURL: url, - runtimeLog: LocalPublisher.runningBots[botId].result.runtimeLog, + runtimeLogStreamingUrl, }, }; } else { const publishResult = { status: LocalPublisher.runningBots[botId].status, result: LocalPublisher.runningBots[botId].result, + runtimeLogStreamingUrl, }; if (LocalPublisher.runningBots[botId].status === 500) { // after we return the 500 status once, delete it out of the running bots list. @@ -234,6 +254,11 @@ class LocalPublisher implements PublishPlugin { } }; + setupRuntimeLogServer = async (projectId: string) => { + await WebSocketServer.init(); + return WebSocketServer.getRuntimeLogStreamingUrl(projectId); + }; + private getBotsDir = () => process.env.LOCAL_PUBLISH_PATH || path.resolve(this.baseDir, 'hostedBots'); private getBotDir = (botId: string) => path.resolve(this.getBotsDir(), botId); @@ -494,7 +519,7 @@ class LocalPublisher implements PublishPlugin { child.on('exit', (code) => { if (code !== 0) { logger('error on exit: %s, exit code %d', errOutput, code); - this.appendRuntimeLogs(botId, errOutput); + this.appendRuntimeLogs(botId, errOutput, 'stderr'); this.setBotStatus(botId, { status: 500, result: { message: errOutput }, diff --git a/extensions/localPublish/src/socketPort.ts b/extensions/localPublish/src/socketPort.ts new file mode 100644 index 0000000000..b892c171ef --- /dev/null +++ b/extensions/localPublish/src/socketPort.ts @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { Request, Response } from 'express'; + +import { WebSocketServer } from './webSocketServer'; + +export async function getWebSocketPort(req: Request, res: Response): Promise { + try { + let socketPort: any = WebSocketServer.port; + let newRestServerSetup = false; + if (!socketPort) { + socketPort = await WebSocketServer.init(); + newRestServerSetup = true; + } + res.status(StatusCodes.OK).json({ port: socketPort, newRestServerSetup }); + } catch (e) { + res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(e); + } +} diff --git a/extensions/localPublish/yarn.lock b/extensions/localPublish/yarn.lock index 8756163e3f..4186e5e9b0 100644 --- a/extensions/localPublish/yarn.lock +++ b/extensions/localPublish/yarn.lock @@ -8,6 +8,7 @@ "@types/express" "^4.16.1" "@types/passport" "^1.0.4" axios "^0.21.1" + botframework-schema "^4.11.1" express-serve-static-core "0.1.1" json-schema "^0.2.5" @@ -56,6 +57,15 @@ "@types/qs" "*" "@types/range-parser" "*" +"@types/express-serve-static-core@^4.16.1": + version "4.17.19" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz#00acfc1632e729acac4f1530e9e16f6dd1508a1d" + integrity sha512-DJOSHzX7pCiSElWaGR8kCprwibCB/3yW6vcT8VG3P0SJjnv19gnWG/AZMfM60Xj/YJIp/YCaDHyvzsFVeniARA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/express@*", "@types/express@^4.16.1": version "4.17.8" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.8.tgz#3df4293293317e61c60137d273a2e96cd8d5f27a" @@ -137,6 +147,14 @@ resolved "https://registry.yarnpkg.com/@types/tcp-port-used/-/tcp-port-used-1.0.0.tgz#2a7b674fe81580d7c205297e26594ee12d07100f" integrity sha512-UbspV5WZNhfM55HyvLEFyVc5n6K6OKuKep0mzvsgoUXQU1FS42GbePjreBnTCoKXfNzK/3/RJVCRlUDTuszFPg== +accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + adm-zip@^0.4.14: version "0.4.16" resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" @@ -171,6 +189,11 @@ archiver@^5.0.2: tar-stream "^2.1.4" zip-stream "^4.0.0" +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -214,6 +237,34 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" +body-parser@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + +botbuilder-stdlib@4.12.0-internal: + version "4.12.0-internal" + resolved "https://registry.yarnpkg.com/botbuilder-stdlib/-/botbuilder-stdlib-4.12.0-internal.tgz#21540a6bef6e571dfd4ef16033083314df9871f0" + integrity sha512-AwtWOZlgTaQte+uBeDOAy83veksMOzKHR62k3UYgUeqgsUamtqi6+OGV633hLi49ZmF/Gs8dP4iid2jCLDEQBg== + +botframework-schema@^4.11.1: + version "4.12.0" + resolved "https://registry.yarnpkg.com/botframework-schema/-/botframework-schema-4.12.0.tgz#42431b5949fb666087399fd555c21c0322099397" + integrity sha512-W0/0xvrxafRrusWfGsGiwK2X4FOoIL7obvitkS+3XZfwhr6XeDKLClkQMbaU0GCawVF6DtbiPKErHpPUhd2Oig== + dependencies: + botbuilder-stdlib "4.12.0-internal" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -238,6 +289,14 @@ buffer@^5.1.0, buffer@^5.5.0: version "5.6.0" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== compress-commons@^4.0.0: version "4.0.1" @@ -254,6 +313,28 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +content-disposition@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== + dependencies: + safe-buffer "5.1.2" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -274,6 +355,13 @@ crc@^3.4.4: dependencies: buffer "^5.1.0" +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + debug@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" @@ -293,6 +381,16 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -300,6 +398,16 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -307,11 +415,57 @@ end-of-stream@^1.4.1: dependencies: once "^1.4.0" -express-serve-static-core@0.1.1: +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +express-serve-static-core@0.1.1, express-serve-static-core@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/express-serve-static-core/-/express-serve-static-core-0.1.1.tgz#222358112a79bc9fbe00838232e8cd2e3132ef37" integrity sha1-IiNYESp5vJ++AIOCMujNLjEy7zc= +express@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + fast-glob@^3.1.1: version "3.2.4" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" @@ -338,11 +492,34 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + follow-redirects@^1.10.0: version "1.13.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -394,6 +571,35 @@ graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -412,7 +618,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -427,6 +633,11 @@ ip-regex@^2.1.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -513,11 +724,26 @@ lodash@^4.17.14, lodash@^4.17.20: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" @@ -526,6 +752,23 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" +mime-db@1.47.0: + version "1.47.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c" + integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw== + +mime-types@~2.1.24: + version "2.1.30" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d" + integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg== + dependencies: + mime-db "1.47.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -545,16 +788,38 @@ mkdirp@^0.5.5: dependencies: minimist "^1.2.5" +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + once@^1.3.0, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -562,11 +827,21 @@ once@^1.3.0, once@^1.4.0: dependencies: wrappy "1" +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -604,6 +879,34 @@ process@^0.11.1: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= +proxy-addr@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" + integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.9.1" + +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + readable-stream@^2.0.0, readable-stream@^2.0.5: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" @@ -650,7 +953,7 @@ run-parallel@^1.1.9: resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== -safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -660,6 +963,45 @@ safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serve-static@1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + shell-exec@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/shell-exec/-/shell-exec-1.0.2.tgz#2e9361b0fde1d73f476c4b6671fa17785f696756" @@ -670,6 +1012,11 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -710,6 +1057,24 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -722,16 +1087,31 @@ util@^0.10.3: dependencies: inherits "2.0.3" +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + uuid@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +ws@^7.4.4: + version "7.4.4" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59" + integrity sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw== + zip-stream@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.0.2.tgz#3a20f1bd7729c2b59fd4efa04df5eb7a5a217d2e" From a20456cd560418f74253f65f3257f95837946526 Mon Sep 17 00:00:00 2001 From: Srinaath Ravichandran Date: Thu, 8 Apr 2021 14:46:02 -0700 Subject: [PATCH 02/11] All tests passing Signed-off-by: Srinaath Ravichandran --- Composer/packages/client/package.json | 2 + .../BotRuntimeController/BotErrorViewer.tsx | 4 +- .../BotRuntimeController/BotRuntimeStatus.tsx | 4 +- .../BotRuntimeController/ErrorCallout.tsx | 2 - .../BotRuntimeController/useBotOperations.ts | 8 +-- .../useBotOperations.test.tsx | 8 +-- .../RuntimeOutputLog/RuntimeOutputLog.tsx | 23 +++++--- .../__tests__/RuntimeOutputLog.test.tsx | 54 +++++++++++++++++-- .../src/recoilModel/dispatchers/publisher.ts | 9 +--- Composer/yarn.lock | 12 +++++ extensions/localPublish/src/index.ts | 4 +- 11 files changed, 95 insertions(+), 35 deletions(-) diff --git a/Composer/packages/client/package.json b/Composer/packages/client/package.json index 5e0fc12cf2..81c26cd653 100644 --- a/Composer/packages/client/package.json +++ b/Composer/packages/client/package.json @@ -117,7 +117,9 @@ "format-message-cli": "^6.2.3", "fs-extra": "7.0.1", "html-webpack-plugin": "4.0.0-beta.8", + "jest-websocket-mock": "^2.2.0", "mini-css-extract-plugin": "0.8.0", + "mock-socket": "9.0.3", "optimize-css-assets-webpack-plugin": "^5.0.3", "pnp-webpack-plugin": "1.5.0", "postcss-flexbugs-fixes": "4.1.0", diff --git a/Composer/packages/client/src/components/BotRuntimeController/BotErrorViewer.tsx b/Composer/packages/client/src/components/BotRuntimeController/BotErrorViewer.tsx index 9398e8bb53..f971f98fa6 100644 --- a/Composer/packages/client/src/components/BotRuntimeController/BotErrorViewer.tsx +++ b/Composer/packages/client/src/components/BotRuntimeController/BotErrorViewer.tsx @@ -17,7 +17,7 @@ type BotErrorViewerProps = { export const BotErrorViewer: React.FC = ({ projectId }) => { const { setActiveTabInDebugPanel, setDebugPanelExpansion } = useRecoilValue(dispatcherState); - const botRuntimeErrorMsg = useRecoilValue(botBuildTimeErrorState(projectId)); + const botBuildTimeError = useRecoilValue(botBuildTimeErrorState(projectId)); const openErrorDialog = () => { setActiveTabInDebugPanel('RuntimeLog'); @@ -26,7 +26,7 @@ export const BotErrorViewer: React.FC = ({ projectId }) => return ( - {botRuntimeErrorMsg?.message && ( + {botBuildTimeError?.message && ( { break; case BotStatus.connected: { - if (isRunning) { - return; - } + setIntervalRunning(false); TelemetryClient.track('StartBotCompleted', { projectId, status: currentBotStatus }); break; } diff --git a/Composer/packages/client/src/components/BotRuntimeController/ErrorCallout.tsx b/Composer/packages/client/src/components/BotRuntimeController/ErrorCallout.tsx index eb2e9ef313..2fa38c2c31 100644 --- a/Composer/packages/client/src/components/BotRuntimeController/ErrorCallout.tsx +++ b/Composer/packages/client/src/components/BotRuntimeController/ErrorCallout.tsx @@ -134,11 +134,9 @@ export const ErrorCallout: React.FC = (props) => { return
{parsed.map(renderRow)}
; } }; - return (
([]); - const { - updateSettingsForSkillsWithoutManifest, - resetBotRuntimeLog: resetBotRuntimeError, - setBotStatus, - } = useRecoilValue(dispatcherState); + const { updateSettingsForSkillsWithoutManifest, resetBotRuntimeLog, setBotStatus } = useRecoilValue(dispatcherState); const handleBotStart = async ( projectId: string, @@ -28,7 +24,7 @@ export function useBotOperations() { sensitiveSettings, botBuildRequired: boolean ) => { - resetBotRuntimeError(projectId); + resetBotRuntimeLog(projectId); setBotStatus(projectId, BotStatus.pending); if (botBuildRequired) { // Default recognizer diff --git a/Composer/packages/client/src/components/__tests__/BotRuntimeController/useBotOperations.test.tsx b/Composer/packages/client/src/components/__tests__/BotRuntimeController/useBotOperations.test.tsx index 4ec47d13a6..f3a0b5daa7 100644 --- a/Composer/packages/client/src/components/__tests__/BotRuntimeController/useBotOperations.test.tsx +++ b/Composer/packages/client/src/components/__tests__/BotRuntimeController/useBotOperations.test.tsx @@ -16,7 +16,7 @@ const state = { }; const mocks = { - resetBotRuntimeError: jest.fn(), + resetBotRuntimeLog: jest.fn(), publishToTarget: jest.fn(), setBotStatus: jest.fn(), stopBot: jest.fn(), @@ -32,7 +32,7 @@ const initRecoilState = ({ set }) => { }); set(dispatcherState, { - resetBotRuntimeError: mocks.resetBotRuntimeError, + resetBotRuntimeLog: mocks.resetBotRuntimeLog, publishToTarget: mocks.publishToTarget, setBotStatus: mocks.setBotStatus, stopPublishBot: mocks.stopBot, @@ -42,7 +42,7 @@ const initRecoilState = ({ set }) => { // TODO: An integration test needs to be added to test this component better. describe('useBotOperations', () => { afterEach(() => { - mocks.resetBotRuntimeError.mockReset(); + mocks.resetBotRuntimeLog.mockReset(); mocks.publishToTarget.mockReset(); mocks.setBotStatus.mockReset(); mocks.stopBot.mockReset(); @@ -61,7 +61,7 @@ describe('useBotOperations', () => { await act(async () => { result.current.startSingleBot(state.skillId); }); - expect(mocks.resetBotRuntimeError).toHaveBeenLastCalledWith(state.skillId); + expect(mocks.resetBotRuntimeLog).toHaveBeenLastCalledWith(state.skillId); expect(mocks.publishToTarget).toHaveBeenLastCalledWith( state.skillId, defaultPublishConfig, diff --git a/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/RuntimeOutputLog.tsx b/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/RuntimeOutputLog.tsx index aa05d8d790..c45a45a363 100644 --- a/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/RuntimeOutputLog.tsx +++ b/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/RuntimeOutputLog.tsx @@ -8,6 +8,7 @@ import { useRecoilValue } from 'recoil'; import { default as AnsiUp } from 'ansi_up'; import { useEffect, useRef } from 'react'; import sanitizeHtml from 'sanitize-html'; +import formatMessage from 'format-message'; import { botBuildTimeErrorState, dispatcherState, runtimeStandardOutputDataState } from '../../../../../recoilModel'; import { getDefaultFontSettings } from '../../../../../recoilModel/utils/fontUtil'; @@ -17,6 +18,13 @@ import { checkIfDotnetVersionMissing, missingDotnetVersionError } from '../../.. import { BotStartError } from '../../../../../recoilModel/types'; import { Text } from '../../../../../constants'; +const genericErrorMessage = () => { + return { + message: 'Runtime Log', + summary: formatMessage('Error occured trying to fetch runtime standard output'), + }; +}; + const ansiUp = new AnsiUp(); const DEFAULT_FONT_SETTINGS = getDefaultFontSettings(); @@ -27,7 +35,7 @@ const createMarkup = (txt: string) => { export const RuntimeOutputLog: React.FC<{ projectId: string }> = ({ projectId }) => { const runtimeData = useRecoilValue(runtimeStandardOutputDataState(projectId)); const botBuildErrors = useRecoilValue(botBuildTimeErrorState(projectId)); - const { setRuntimeStandardOutputData } = useRecoilValue(dispatcherState); + const { setRuntimeStandardOutputData, setApplicationLevelError } = useRecoilValue(dispatcherState); const runtimeLogsContainerRef = useRef(null); @@ -43,7 +51,9 @@ export const RuntimeOutputLog: React.FC<{ projectId: string }> = ({ projectId }) const setupLogConnection = async () => { try { const runtimeStreamUrl = await httpClient.get(`/publish/runtimeLogUrl/${projectId}`); + runtimeTrafficChannel.current = new WebSocket(runtimeStreamUrl.data); + if (runtimeTrafficChannel.current) { runtimeTrafficChannel.current.onmessage = (event) => { try { @@ -72,12 +82,12 @@ export const RuntimeOutputLog: React.FC<{ projectId: string }> = ({ projectId }) standardOutput: data.standardOutput, }); } catch (ex) { - // // No need handle the exception here. The old state can continue to exist. + setApplicationLevelError(genericErrorMessage()); } }; } } catch (ex) { - // No need handle the exception here. The Outputs window would be empty + setApplicationLevelError(genericErrorMessage()); } }; @@ -98,7 +108,7 @@ export const RuntimeOutputLog: React.FC<{ projectId: string }> = ({ projectId }) height: 'calc(100% - 25px)', display: 'flex', flexDirection: 'column', - padding: '15px 24px', + padding: '10px 16px', fontSize: DEFAULT_FONT_SETTINGS.fontSize, fontFamily: DEFAULT_FONT_SETTINGS.fontFamily, color: `${NeutralColors.black}`, @@ -106,7 +116,7 @@ export const RuntimeOutputLog: React.FC<{ projectId: string }> = ({ projectId }) overflowY: 'auto', overflowX: 'hidden', }} - data-testid="Runtime-Output-Logs" + data-testid="runtime-output-logs" > {runtimeData.standardOutput && (
= ({ projectId }) }} // eslint-disable-next-line react/no-danger dangerouslySetInnerHTML={createMarkup(runtimeData.standardOutput)} + data-testid="runtime-standard-output" /> )} {botBuildErrors && } - {runtimeData.standardError && } + {runtimeData.standardError && }
); }; diff --git a/Composer/packages/client/src/pages/design/__tests__/RuntimeOutputLog.test.tsx b/Composer/packages/client/src/pages/design/__tests__/RuntimeOutputLog.test.tsx index 5441b1e74c..550b158bf7 100644 --- a/Composer/packages/client/src/pages/design/__tests__/RuntimeOutputLog.test.tsx +++ b/Composer/packages/client/src/pages/design/__tests__/RuntimeOutputLog.test.tsx @@ -2,21 +2,67 @@ // Licensed under the MIT License. import * as React from 'react'; +import WS from 'jest-websocket-mock'; +import { act } from '@testing-library/react'; +import httpClient from '../../../utils/httpUtil'; import { renderWithRecoil } from '../../../../__tests__/testUtils/renderWithRecoil'; import { botBuildTimeErrorState } from '../../../recoilModel'; import { RuntimeOutputLog } from '../DebugPanel/TabExtensions/RuntimeOutputLog/RuntimeOutputLog'; const projectId = '123-avw'; +jest.mock('../../../utils/httpUtil'); +const standardOut = `/Users/tester/Desktop/conversational_core_3/conversational_core_3/conversational_core_3.csproj : warning NU1701: Package 'Microsoft.Azure.KeyVault.Core 1.0.0' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8' instead of the project target framework '.NETCoreApp,Version=v3.1'. This package may not be fully compatible with your project. + /Users/tester/Desktop/conversational_core_3/conversational_core_3/conversational_core_3.csproj : warning NU1701: Package 'Microsoft.Azure.KeyVault.Core 1.0.0' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8' instead of the project target framework '.NETCoreApp,Version=v3.1'. This package may not be fully compatible with your project. + info: Microsoft.Hosting.Lifetime[0] + Now listening on: http://0.0.0.0:3988 + info: Microsoft.Hosting.Lifetime[0] + Application started. Press Ctrl+C to shut down. + info: Microsoft.Hosting.Lifetime[0] + Hosting environment: Development + info: Microsoft.Hosting.Lifetime[0] + Content root path: /Users/tester/Desktop/conversational_core_3/conversational_core_3 + `; + describe('', () => { - beforeEach(() => {}); + let mockWSServer; + beforeAll(() => { + const url = `ws://localhost:1234/test/${projectId}`; + (httpClient.get as jest.Mock).mockResolvedValue({ + data: url, + }); + mockWSServer = new WS(`ws://localhost:1234/test/${projectId}`); + }); + + afterAll(() => { + mockWSServer = null; + }); describe('', () => { it('should render Runtime logs', async () => { - const { findByText } = renderWithRecoil(, ({ set }) => { - set(botRuntimeLogState(projectId), 'Bot started at http://localhost:3978/api/messages'); + const { findByTestId } = renderWithRecoil(); + await mockWSServer.connected; + act(() => { + const stringified = JSON.stringify({ + standardOutput: standardOut, + standardError: '', + }); + mockWSServer.send(stringified); + }); + await findByTestId('runtime-standard-output'); + }); + + it('should render Runtime standard errors', async () => { + const { findByText } = renderWithRecoil(); + await mockWSServer.connected; + act(() => { + const stringified = JSON.stringify({ + standardOutput: '', + standardError: 'Command failed: dotnet user-secrets', + }); + mockWSServer.send(stringified); }); - await findByText('Bot started at http://localhost:3978/api/messages'); + await findByText('Install Microsoft .NET Core SDK'); }); it('should render Runtime errors', async () => { diff --git a/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts b/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts index 20bca9e2be..1a87d6bf9d 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts @@ -27,7 +27,6 @@ import { import * as luUtil from '../../utils/luUtil'; import * as qnaUtil from '../../utils/qnaUtil'; import { ClientStorage } from '../../utils/storage'; -import { checkIfDotnetVersionMissing, missingDotnetVersionError } from '../../utils/runtimeErrors'; import { RuntimeOutputData } from '../types'; import { BotStatus, Text } from './../../constants'; @@ -126,7 +125,7 @@ export const publisherDispatcher = () => { set(botStatusState(projectId), BotStatus.starting); } else if (status === PUBLISH_FAILED) { set(botStatusState(projectId), BotStatus.failed); - set(botBuildTimeErrorState(projectId), { ...data, title: formatMessage('Start bot failed') }); + set(botBuildTimeErrorState(projectId), { ...data, title: formatMessage('Error occured building the bot') }); } } @@ -196,11 +195,7 @@ export const publisherDispatcher = () => { await publishSuccess(callbackHelpers, projectId, response.data, target); } catch (err) { // special case to handle dotnet issues - if (checkIfDotnetVersionMissing(err?.response?.data)) { - await publishFailure(callbackHelpers, Text.DOTNETFAILURE, missingDotnetVersionError, target, projectId); - } else { - await publishFailure(callbackHelpers, Text.CONNECTBOTFAILURE, err.response?.data, target, projectId); - } + await publishFailure(callbackHelpers, Text.CONNECTBOTFAILURE, err.response?.data, target, projectId); } } ); diff --git a/Composer/yarn.lock b/Composer/yarn.lock index 3e667d4112..f55be4a66f 100644 --- a/Composer/yarn.lock +++ b/Composer/yarn.lock @@ -14945,6 +14945,11 @@ jest-watcher@^26.0.1: jest-util "^26.0.1" string-length "^4.0.1" +jest-websocket-mock@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/jest-websocket-mock/-/jest-websocket-mock-2.2.0.tgz#0eed73eb3c14d48b15dd046c9e40e9571377d06e" + integrity sha512-lc3wwXOEyNa4ZpcgJtUG3mmKMAq5FAsKYiZph0p/+PAJrAPuX4JCIfJMdJ/urRsLBG51fwm/wlVPNbR6s2nzNw== + jest-worker@^25.4.0: version "25.5.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.5.0.tgz#2611d071b79cea0f43ee57a3d118593ac1547db1" @@ -16555,6 +16560,13 @@ mock-fs@^4.10.1: resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.10.1.tgz#50a07a20114a6cdb119f35762f61f46266a1e323" integrity sha512-w22rOL5ZYu6HbUehB5deurghGM0hS/xBVyHMGKOuQctkk93J9z9VEOhDsiWrXOprVNQpP9uzGKdl8v9mFspKuw== +mock-socket@9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.0.3.tgz#4bc6d2aea33191e4fed5ec71f039e2bbeb95e414" + integrity sha512-SxIiD2yE/By79p3cNAAXyLQWTvEFNEzcAO7PH+DzRqKSFaplAPFjiQLmw8ofmpCsZf+Rhfn2/xCJagpdGmYdTw== + dependencies: + url-parse "^1.4.4" + moment-timezone@*, moment-timezone@^0.5.28: version "0.5.33" resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c" diff --git a/extensions/localPublish/src/index.ts b/extensions/localPublish/src/index.ts index 3eefe1a982..c1b920690b 100644 --- a/extensions/localPublish/src/index.ts +++ b/extensions/localPublish/src/index.ts @@ -519,7 +519,9 @@ class LocalPublisher implements PublishPlugin { child.on('exit', (code) => { if (code !== 0) { logger('error on exit: %s, exit code %d', errOutput, code); - this.appendRuntimeLogs(botId, errOutput, 'stderr'); + if (LocalPublisher.runningBots[botId].status === 200) { + this.appendRuntimeLogs(botId, errOutput, 'stderr'); + } this.setBotStatus(botId, { status: 500, result: { message: errOutput }, From a3f3f55523a4d6908d019acfc285e1dcfabba343 Mon Sep 17 00:00:00 2001 From: Srinaath Ravichandran Date: Thu, 8 Apr 2021 15:01:28 -0700 Subject: [PATCH 03/11] Conditional inputs removal Signed-off-by: Srinaath Ravichandran --- extensions/localPublish/src/WebSocketServer.ts | 4 ++-- extensions/localPublish/src/index.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/localPublish/src/WebSocketServer.ts b/extensions/localPublish/src/WebSocketServer.ts index e490db824a..58dedb58fb 100644 --- a/extensions/localPublish/src/WebSocketServer.ts +++ b/extensions/localPublish/src/WebSocketServer.ts @@ -75,8 +75,8 @@ export class WebSocketServer { public static sendRuntimeLogToSubscribers(projectId: string, standardOutput: string, standardError: string): void { this.sockets[projectId]?.send( JSON.stringify({ - standardOutput: standardOutput ?? '', - standardError: standardError ?? '', + standardOutput: standardOutput, + standardError: standardError, }) ); } diff --git a/extensions/localPublish/src/index.ts b/extensions/localPublish/src/index.ts index c1b920690b..219270e1b8 100644 --- a/extensions/localPublish/src/index.ts +++ b/extensions/localPublish/src/index.ts @@ -123,8 +123,8 @@ class LocalPublisher implements PublishPlugin { } WebSocketServer.sendRuntimeLogToSubscribers( botId, - LocalPublisher.runningBots[botId].result.runtimeLog, - LocalPublisher.runningBots[botId].result.runtimeError + LocalPublisher.runningBots[botId].result.runtimeLog ?? '', + LocalPublisher.runningBots[botId].result.runtimeError ?? '' ); }; From b77496fa613b36e9bfeb68474d900b8f9c6b5c58 Mon Sep 17 00:00:00 2001 From: Srinaath Ravichandran Date: Thu, 8 Apr 2021 15:10:30 -0700 Subject: [PATCH 04/11] Remove unused classes Signed-off-by: Srinaath Ravichandran --- .../RuntimeOutputLog/RuntimeOutputLog.tsx | 2 +- .../client/src/recoilModel/atoms/botState.ts | 1 - .../src/recoilModel/dispatchers/publisher.ts | 2 +- .../localPublish/src/WebSocketServer.ts | 6 +++--- extensions/localPublish/src/index.ts | 3 --- extensions/localPublish/src/socketPort.ts | 20 ------------------- 6 files changed, 5 insertions(+), 29 deletions(-) delete mode 100644 extensions/localPublish/src/socketPort.ts diff --git a/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/RuntimeOutputLog.tsx b/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/RuntimeOutputLog.tsx index c45a45a363..8fb0e95a75 100644 --- a/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/RuntimeOutputLog.tsx +++ b/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/RuntimeOutputLog/RuntimeOutputLog.tsx @@ -21,7 +21,7 @@ import { Text } from '../../../../../constants'; const genericErrorMessage = () => { return { message: 'Runtime Log', - summary: formatMessage('Error occured trying to fetch runtime standard output'), + summary: formatMessage('Error occurred trying to fetch runtime standard output'), }; }; diff --git a/Composer/packages/client/src/recoilModel/atoms/botState.ts b/Composer/packages/client/src/recoilModel/atoms/botState.ts index 171beda8dc..d1b999d58a 100644 --- a/Composer/packages/client/src/recoilModel/atoms/botState.ts +++ b/Composer/packages/client/src/recoilModel/atoms/botState.ts @@ -334,7 +334,6 @@ export const projectMetaDataState = atomFamily<{ isRootBot: boolean; isRemote: b return { isRootBot: false, isRemote: false, - runtimeLogStreamingUrl: '', }; }, }); diff --git a/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts b/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts index 1a87d6bf9d..df9d590854 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts @@ -125,7 +125,7 @@ export const publisherDispatcher = () => { set(botStatusState(projectId), BotStatus.starting); } else if (status === PUBLISH_FAILED) { set(botStatusState(projectId), BotStatus.failed); - set(botBuildTimeErrorState(projectId), { ...data, title: formatMessage('Error occured building the bot') }); + set(botBuildTimeErrorState(projectId), { ...data, title: formatMessage('Error occurred building the bot') }); } } diff --git a/extensions/localPublish/src/WebSocketServer.ts b/extensions/localPublish/src/WebSocketServer.ts index 58dedb58fb..1cd28a34e1 100644 --- a/extensions/localPublish/src/WebSocketServer.ts +++ b/extensions/localPublish/src/WebSocketServer.ts @@ -44,7 +44,7 @@ export class WebSocketServer { } const projectId = (req as any).params.projectId; - // initialize a new web socket server for each new conversation + // initialize a new web socket server for each new projectId if (projectId && !this.servers[projectId]) { const { head, socket } = (req as any).claimUpgrade(); @@ -75,8 +75,8 @@ export class WebSocketServer { public static sendRuntimeLogToSubscribers(projectId: string, standardOutput: string, standardError: string): void { this.sockets[projectId]?.send( JSON.stringify({ - standardOutput: standardOutput, - standardError: standardError, + standardOutput, + standardError, }) ); } diff --git a/extensions/localPublish/src/index.ts b/extensions/localPublish/src/index.ts index 219270e1b8..d03cc4562d 100644 --- a/extensions/localPublish/src/index.ts +++ b/extensions/localPublish/src/index.ts @@ -206,7 +206,6 @@ class LocalPublisher implements PublishPlugin { }; getStatus = async (config: PublishConfig, project, user) => { const botId = project.id; - const runtimeLogStreamingUrl = WebSocketServer.getRuntimeLogStreamingUrl(botId); if (LocalPublisher.runningBots[botId]) { if (LocalPublisher.runningBots[botId].status === 200) { const port = LocalPublisher.runningBots[botId].port; @@ -216,14 +215,12 @@ class LocalPublisher implements PublishPlugin { result: { message: 'Running', endpointURL: url, - runtimeLogStreamingUrl, }, }; } else { const publishResult = { status: LocalPublisher.runningBots[botId].status, result: LocalPublisher.runningBots[botId].result, - runtimeLogStreamingUrl, }; if (LocalPublisher.runningBots[botId].status === 500) { // after we return the 500 status once, delete it out of the running bots list. diff --git a/extensions/localPublish/src/socketPort.ts b/extensions/localPublish/src/socketPort.ts deleted file mode 100644 index b892c171ef..0000000000 --- a/extensions/localPublish/src/socketPort.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import { Request, Response } from 'express'; - -import { WebSocketServer } from './webSocketServer'; - -export async function getWebSocketPort(req: Request, res: Response): Promise { - try { - let socketPort: any = WebSocketServer.port; - let newRestServerSetup = false; - if (!socketPort) { - socketPort = await WebSocketServer.init(); - newRestServerSetup = true; - } - res.status(StatusCodes.OK).json({ port: socketPort, newRestServerSetup }); - } catch (e) { - res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(e); - } -} From 8cdafab06956038a32d51d8fa321eba776dfdf57 Mon Sep 17 00:00:00 2001 From: Srinaath Ravichandran Date: Thu, 8 Apr 2021 20:18:54 -0700 Subject: [PATCH 05/11] Layout updates Signed-off-by: Srinaath Ravichandran --- .../packages/client/src/components/Page.tsx | 4 +- .../components/ProjectTree/ProjectTree.tsx | 2 +- .../client/src/pages/design/DesignPage.tsx | 84 +++++++++++-------- 3 files changed, 54 insertions(+), 36 deletions(-) diff --git a/Composer/packages/client/src/components/Page.tsx b/Composer/packages/client/src/components/Page.tsx index 9e0d6ed04b..03c66b6f20 100644 --- a/Composer/packages/client/src/components/Page.tsx +++ b/Composer/packages/client/src/components/Page.tsx @@ -64,7 +64,7 @@ export const headerContent = css` export const main = (hasRenderHeaderContent) => css` margin-left: 2px; - height: ${hasRenderHeaderContent ? 'calc(100vh - 181px)' : 'calc(100vh - 165px)'}; + max-height: ${hasRenderHeaderContent ? 'calc(100vh - 181px)' : 'calc(100vh - 165px)'}; display: flex; flex-grow: 1; border-top: 1px solid #dddddd; @@ -220,10 +220,10 @@ const Page: React.FC = (props) => { role="region" >
{children}
- {useDebugPane ? : null}
+ {useDebugPane ? : null}
); diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx index dfd4b6c439..b7f9a85074 100644 --- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx +++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx @@ -42,7 +42,7 @@ const root = css` width: 100%; height: 100%; box-sizing: border-box; - overflow: hidden; + overflow: auto; .ms-List-cell { min-height: 36px; } diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx index dac1a406c0..7d3e4202d6 100644 --- a/Composer/packages/client/src/pages/design/DesignPage.tsx +++ b/Composer/packages/client/src/pages/design/DesignPage.tsx @@ -2,7 +2,7 @@ // Licensed under the MIT License. /** @jsx jsx */ -import { jsx } from '@emotion/core'; +import { css, jsx } from '@emotion/core'; import { RouteComponentProps } from '@reach/router'; import { useRecoilValue } from 'recoil'; import { Split, SplitMeasuredSizes } from '@geoffcox/react-splitter'; @@ -16,10 +16,26 @@ import CommandBar from './CommandBar'; import VisualPanel from './VisualPanel'; import PropertyPanel from './PropertyPanel'; import useEmptyPropsHandler from './useEmptyPropsHandler'; -import { contentWrapper, editorContainer, editorWrapper, pageRoot } from './styles'; +import { contentWrapper, editorContainer, editorWrapper } from './styles'; import Modals from './Modals'; import { DebugPanel } from './DebugPanel/DebugPanel'; +export const root = css` + height: calc(100vh - 50px); + display: flex; + flex-direction: row; + + label: Page; +`; + +export const pageWrapper = css` + display: flex; + flex-direction: column; + flex-grow: 1; + + label: PageWrapper; +`; + const DesignPage: React.FC> = ( props ) => { @@ -35,37 +51,39 @@ const DesignPage: React.FC - - -
- - -
- - - - -
-
- -
-
- +
+
+ + +
+ + +
+ + + + +
+
+
+
+ + +
); }; From 7c11156edc3f4201ea4c6c1f26efe245217363a7 Mon Sep 17 00:00:00 2001 From: Srinaath Ravichandran Date: Fri, 9 Apr 2021 10:16:52 -0700 Subject: [PATCH 06/11] Style updates Signed-off-by: Srinaath Ravichandran --- .../client/src/components/ProjectTree/ProjectTree.tsx | 2 +- .../client/src/pages/design/DebugPanel/DebugPanel.tsx | 2 +- .../packages/client/src/pages/design/DebugPanel/styles.ts | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx index b7f9a85074..dfd4b6c439 100644 --- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx +++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx @@ -42,7 +42,7 @@ const root = css` width: 100%; height: 100%; box-sizing: border-box; - overflow: auto; + overflow: hidden; .ms-List-cell { min-height: 36px; } diff --git a/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx b/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx index cb280efbb2..0471f20639 100644 --- a/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx +++ b/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx @@ -67,7 +67,7 @@ export const DebugPanel: React.FC = () => { return { key: tabKey, element }; }; - const computedPivotHeight = isPanelExpanded ? 36 : 24; + const computedPivotHeight = isPanelExpanded ? 36 : 32; const headerPivot = useMemo(() => { const tabTitles = debugExtensions diff --git a/Composer/packages/client/src/pages/design/DebugPanel/styles.ts b/Composer/packages/client/src/pages/design/DebugPanel/styles.ts index db41a0fca7..9b02391f24 100644 --- a/Composer/packages/client/src/pages/design/DebugPanel/styles.ts +++ b/Composer/packages/client/src/pages/design/DebugPanel/styles.ts @@ -35,7 +35,9 @@ export const debugPaneBarStyle = css` background: #faf9f8; `; -export const leftBarStyle = css``; +export const leftBarStyle = css` + padding: 0 16px; +`; export const rightBarStyle = css` display: flex; From 064bb92ace2804cde79f18b42bae40b60e02c7e6 Mon Sep 17 00:00:00 2001 From: Srinaath Ravichandran Date: Fri, 9 Apr 2021 13:00:55 -0700 Subject: [PATCH 07/11] Moved variables around Signed-off-by: Srinaath Ravichandran --- .../pages/design/DebugPanel/DebugPanel.tsx | 121 +++++++++--------- .../src/pages/design/DebugPanel/styles.ts | 13 +- 2 files changed, 71 insertions(+), 63 deletions(-) diff --git a/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx b/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx index 0471f20639..c83d2ed9a1 100644 --- a/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx +++ b/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx @@ -23,6 +23,11 @@ import { debugPaneHeaderStyle, debugPaneContentStyle, debugPaneFooterStyle, + expandedPanelHeaderHeight, + collapsedPaneHeaderHeight, + debugPanelDefaultHeight, + debugPanelMaxExpandedHeight, + debugPanelMinHeight, } from './styles'; import debugExtensions from './TabExtensions'; import { DebugDrawerKeys, DebugPanelTabHeaderProps } from './TabExtensions/types'; @@ -67,7 +72,7 @@ export const DebugPanel: React.FC = () => { return { key: tabKey, element }; }; - const computedPivotHeight = isPanelExpanded ? 36 : 32; + const computedPivotHeight = isPanelExpanded ? expandedPanelHeaderHeight : collapsedPaneHeaderHeight; const headerPivot = useMemo(() => { const tabTitles = debugExtensions @@ -124,67 +129,69 @@ export const DebugPanel: React.FC = () => { }, [isPanelExpanded, activeTab]); return ( - -
+ -
- {headerPivot} -
-
- { - if (isPanelExpanded) { - onCollapsePanel(); - } else { - onExpandPanel('Diagnostics'); - } - }} + css={css` + ${debugPaneBarStyle} + ${isPanelExpanded ? debugPaneHeaderStyle : debugPaneFooterStyle} + `} + data-testid={isPanelExpanded ? 'debug-panel__header' : 'debug-panel__footer'} + > +
+ {headerPivot} +
+
+
+ { + if (isPanelExpanded) { + onCollapsePanel(); + } else { + onExpandPanel('Diagnostics'); + } + }} + /> +
+
+
+ {debugExtensions.map((debugTabs) => { + const { ContentWidget } = debugTabs; + return ; + })}
-
-
- {debugExtensions.map((debugTabs) => { - const { ContentWidget } = debugTabs; - return ; - })} -
-
+ +
); }; diff --git a/Composer/packages/client/src/pages/design/DebugPanel/styles.ts b/Composer/packages/client/src/pages/design/DebugPanel/styles.ts index 9b02391f24..ac2b86445a 100644 --- a/Composer/packages/client/src/pages/design/DebugPanel/styles.ts +++ b/Composer/packages/client/src/pages/design/DebugPanel/styles.ts @@ -3,9 +3,11 @@ import { css } from '@emotion/core'; -export const DebugPaneHeaderHeight = 36; - -export const DebugPaneFooterHeight = 24; +export const expandedPanelHeaderHeight = 36; +export const collapsedPaneHeaderHeight = 32; +export const debugPanelMaxExpandedHeight = 500; +export const debugPanelDefaultHeight = 300; +export const debugPanelMinHeight = 200; export const debugPaneContainerStyle = css` display: flex; @@ -14,18 +16,17 @@ export const debugPaneContainerStyle = css` `; export const debugPaneHeaderStyle = css` - height: ${DebugPaneHeaderHeight}px; border-top: 1px solid #dfdfdf; `; export const debugPaneContentStyle = css` - height: calc(100% - ${DebugPaneHeaderHeight}px); + height: calc(100% - ${expandedPanelHeaderHeight}px); overflow-y: hidden; overflow-x: auto; `; export const debugPaneFooterStyle = css` - height: ${DebugPaneFooterHeight}px; + height: ${collapsedPaneHeaderHeight}px; border-top: 1px solid #dfdfdf; `; From 81b889c4ed2aec5fbae58b43d16b84f9c69c7623 Mon Sep 17 00:00:00 2001 From: Srinaath Ravichandran Date: Mon, 12 Apr 2021 09:41:17 -0700 Subject: [PATCH 08/11] Deelte unused file Signed-off-by: Srinaath Ravichandran --- .../localPublish/src/WebSocketServer.ts | 83 ------------------- 1 file changed, 83 deletions(-) delete mode 100644 extensions/localPublish/src/WebSocketServer.ts diff --git a/extensions/localPublish/src/WebSocketServer.ts b/extensions/localPublish/src/WebSocketServer.ts deleted file mode 100644 index 1cd28a34e1..0000000000 --- a/extensions/localPublish/src/WebSocketServer.ts +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import http from 'http'; - -import portfinder from 'portfinder'; -import express, { Request, Response } from 'express'; -import { Server as WSServer } from 'ws'; - -interface WebSocket { - close(): void; - send(data: string, cb?: (err?: Error) => void): void; -} - -export class WebSocketServer { - private static restServer: http.Server; - private static servers: WSServer = {}; - private static sockets: Record = {}; - private static port: number; - - public static getRuntimeLogStreamingUrl(projectId: string): string { - return `ws://localhost:${this.port}/ws/runtimeLog/${projectId}`; - } - - public static async init(): Promise { - if (!this.restServer) { - const app = express(); - this.restServer = http.createServer(app); - - this.restServer.on('upgrade', (req, socket, head) => { - req.claimUpgrade = () => ({ - head, - socket, - }); - const res: any = new http.ServerResponse(req); - return app(req, res); - }); - const port = await portfinder.getPortPromise(); - this.restServer.listen(port); - - app.use('/ws/runtimeLog/:projectId', (req: Request, res: Response) => { - if (!(req as any).claimUpgrade) { - return (res as any).status(426).send('Connection must upgrade for web sockets.'); - } - - const projectId = (req as any).params.projectId; - // initialize a new web socket server for each new projectId - if (projectId && !this.servers[projectId]) { - const { head, socket } = (req as any).claimUpgrade(); - - const wsServer = new WSServer({ - noServer: true, - }); - - wsServer.on('connection', (socket, req) => { - this.sockets[projectId] = socket; - socket.on('close', () => { - delete this.servers[projectId]; - delete this.sockets[projectId]; - }); - }); - - // upgrade the connection to a ws connection - wsServer.handleUpgrade(req as any, socket, head, (socket) => { - wsServer.emit('connection', socket, req); - }); - this.servers[projectId] = wsServer; - } - }); - this.port = port; - return this.port; - } - } - - public static sendRuntimeLogToSubscribers(projectId: string, standardOutput: string, standardError: string): void { - this.sockets[projectId]?.send( - JSON.stringify({ - standardOutput, - standardError, - }) - ); - } -} From e83a3ff29a56eac598db9a402825cac8e90591c1 Mon Sep 17 00:00:00 2001 From: Srinaath Ravichandran Date: Mon, 12 Apr 2021 10:19:56 -0700 Subject: [PATCH 09/11] static data-test-id Signed-off-by: Srinaath Ravichandran --- Composer/packages/client/src/pages/design/DesignPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx index 7d3e4202d6..56c8412188 100644 --- a/Composer/packages/client/src/pages/design/DesignPage.tsx +++ b/Composer/packages/client/src/pages/design/DesignPage.tsx @@ -51,7 +51,7 @@ const DesignPage: React.FC +
Date: Mon, 12 Apr 2021 10:47:55 -0700 Subject: [PATCH 10/11] Add todo here Signed-off-by: Srinaath Ravichandran --- Composer/packages/client/src/components/Page.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/Composer/packages/client/src/components/Page.tsx b/Composer/packages/client/src/components/Page.tsx index 03c66b6f20..3bc24a1c6e 100644 --- a/Composer/packages/client/src/components/Page.tsx +++ b/Composer/packages/client/src/components/Page.tsx @@ -62,6 +62,7 @@ export const headerContent = css` label: PageHeaderContent; `; +// TODO: https://github.com/microsoft/BotFramework-Composer/issues/6873. Investigate static numbers export const main = (hasRenderHeaderContent) => css` margin-left: 2px; max-height: ${hasRenderHeaderContent ? 'calc(100vh - 181px)' : 'calc(100vh - 165px)'}; From b52f7196b7c8472318cab830892d12883f30f8d3 Mon Sep 17 00:00:00 2001 From: Srinaath Ravichandran Date: Mon, 12 Apr 2021 10:52:29 -0700 Subject: [PATCH 11/11] Codnitional title rendering Signed-off-by: Srinaath Ravichandran --- .../packages/client/src/pages/design/DebugPanel/DebugPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx b/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx index c83d2ed9a1..5f257ca99e 100644 --- a/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx +++ b/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx @@ -174,7 +174,7 @@ export const DebugPanel: React.FC = () => { { if (isPanelExpanded) { onCollapsePanel();