From 8fe86c3751e87e72395c358e3dfdf5122136a052 Mon Sep 17 00:00:00 2001 From: Patrick Volum Date: Thu, 25 Mar 2021 20:59:29 -0400 Subject: [PATCH 1/4] Adding modal for node detection on app start --- Composer/packages/client/src/App.tsx | 6 +- .../components/CreationFlow/v2/NodeModal.tsx | 55 +++++++++++++++++++ .../packages/server/src/locales/en-US.json | 15 ++--- 3 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 Composer/packages/client/src/components/CreationFlow/v2/NodeModal.tsx diff --git a/Composer/packages/client/src/App.tsx b/Composer/packages/client/src/App.tsx index e72c40f125..e729222cad 100644 --- a/Composer/packages/client/src/App.tsx +++ b/Composer/packages/client/src/App.tsx @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import React, { Fragment, useEffect } from 'react'; +import React, { Fragment, useEffect, useState } from 'react'; import { initializeIcons } from 'office-ui-fabric-react/lib/Icons'; import { useRecoilValue } from 'recoil'; @@ -11,6 +11,7 @@ import { MainContainer } from './components/AppComponents/MainContainer'; import { dispatcherState, userSettingsState } from './recoilModel'; import { loadLocale } from './utils/fileUtil'; import { useInitializeLogger } from './telemetry/useInitializeLogger'; +import { NodeModal } from './components/CreationFlow/v2/NodeModal'; initializeIcons(undefined, { disableWarnings: true }); @@ -22,7 +23,7 @@ const Logger = () => { export const App: React.FC = () => { const { appLocale } = useRecoilValue(userSettingsState); const { fetchExtensions, fetchFeatureFlags } = useRecoilValue(dispatcherState); - + const [showNodeModal, setShowNodeModal] = useState(true); useEffect(() => { loadLocale(appLocale); }, [appLocale]); @@ -34,6 +35,7 @@ export const App: React.FC = () => { return ( + {showNodeModal && }
diff --git a/Composer/packages/client/src/components/CreationFlow/v2/NodeModal.tsx b/Composer/packages/client/src/components/CreationFlow/v2/NodeModal.tsx new file mode 100644 index 0000000000..8285349075 --- /dev/null +++ b/Composer/packages/client/src/components/CreationFlow/v2/NodeModal.tsx @@ -0,0 +1,55 @@ +/* eslint-disable react/no-danger */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** @jsx jsx */ +import { DialogTypes, DialogWrapper } from '@bfc/ui-shared/lib/components/DialogWrapper'; +import { jsx } from '@emotion/core'; +import formatMessage from 'format-message'; +import { DefaultButton, PrimaryButton } from 'office-ui-fabric-react/lib/components/Button'; +import { DialogFooter } from 'office-ui-fabric-react/lib/components/Dialog'; +import React from 'react'; +import { Text } from 'office-ui-fabric-react/lib/Text'; +import { mergeStyles } from 'office-ui-fabric-react/lib/Styling'; + +const dialogFooterClass = mergeStyles({ + marginTop: '25px', +}); + +type NodeModalProps = { + setIsOpen: Function; + isOpen: boolean; +}; + +export const NodeModal: React.FC = (props) => { + return ( + { + props.setIsOpen(false); + }} + > + + {formatMessage( + 'Bot Framework Composer requires Node.js in order to run. Click “Install Node.js” to install the latest version' + )} + + + + { + props.setIsOpen(false); + }} + /> + + + ); +}; diff --git a/Composer/packages/server/src/locales/en-US.json b/Composer/packages/server/src/locales/en-US.json index 6bf443ffd3..a1cb3db09b 100644 --- a/Composer/packages/server/src/locales/en-US.json +++ b/Composer/packages/server/src/locales/en-US.json @@ -299,6 +299,9 @@ "apr_9_2020_3c8b47d7": { "message": "Apr 9, 2020" }, + "are_you_sure_you_want_to_delete_your_bot_this_acti_214a9e11": { + "message": "Are you sure you want to delete your bot? This action cannot be undone and your bot and all related files in the bot project folder will be permanently deleted. Your Azure resources will remain unchanged." + }, "are_you_sure_you_want_to_exit_the_onboarding_produ_c2de1b23": { "message": "Are you sure you want to exit the Onboarding Product Tour? You can restart the tour in the onboarding settings." }, @@ -1427,9 +1430,6 @@ "extension_settings_899ccb55": { "message": "Extension Settings" }, - "external_resources_will_not_be_changed_c08b0009": { - "message": "External resources will not be changed." - }, "external_service_adapters_5218e6a3": { "message": "External service adapters" }, @@ -1640,9 +1640,6 @@ "http_request_79847109": { "message": "HTTP Request" }, - "i_want_to_delete_this_bot_f81a4735": { - "message": "I want to delete this bot" - }, "i_want_to_keep_the_template_content_in_the_file_ju_769331d9": { "message": "I want to keep the template content in the file, just want to dereference from this response (hint: keep the content if you currently, or plan to re-use in another location)" }, @@ -3638,9 +3635,6 @@ "warning_aacb8c24": { "message": "Warning" }, - "warning_the_action_you_are_about_to_take_cannot_be_1071a3c3": { - "message": "Warning: the action you are about to take cannot be undone. Going further will delete this bot and any related files in the bot project folder." - }, "warningsmsg_e2c04bfe": { "message": "{ warningsMsg }" }, @@ -3731,6 +3725,9 @@ "yes_dde87d5": { "message": "Yes" }, + "yes_delete_d43476ee": { + "message": "Yes, delete" + }, "you_already_have_a_kb_with_that_name_choose_anothe_b7f7c517": { "message": "You already have a KB with that name. Choose another name and try again." }, From 4773888069844527ceb47d6822de057c4e241d35 Mon Sep 17 00:00:00 2001 From: Patrick Volum Date: Fri, 26 Mar 2021 16:39:04 -0400 Subject: [PATCH 2/4] - Adding server side checking of node - Adding node modal on app start if node is not installed --- Composer/packages/client/src/App.tsx | 15 +++++++++--- .../client/src/recoilModel/atoms/appState.ts | 5 ++++ .../recoilModel/dispatchers/application.ts | 18 ++++++++++++++ .../server/src/controllers/utilities.ts | 24 +++++++++++++++++++ .../packages/server/src/locales/en-US.json | 6 +++++ Composer/packages/server/src/router/api.ts | 2 ++ 6 files changed, 67 insertions(+), 3 deletions(-) diff --git a/Composer/packages/client/src/App.tsx b/Composer/packages/client/src/App.tsx index e729222cad..86062151ea 100644 --- a/Composer/packages/client/src/App.tsx +++ b/Composer/packages/client/src/App.tsx @@ -8,7 +8,7 @@ import { useRecoilValue } from 'recoil'; import { Header } from './components/Header'; import { Announcement } from './components/AppComponents/Announcement'; import { MainContainer } from './components/AppComponents/MainContainer'; -import { dispatcherState, userSettingsState } from './recoilModel'; +import { dispatcherState, userHasNodeInstalledState, userSettingsState } from './recoilModel'; import { loadLocale } from './utils/fileUtil'; import { useInitializeLogger } from './telemetry/useInitializeLogger'; import { NodeModal } from './components/CreationFlow/v2/NodeModal'; @@ -22,17 +22,26 @@ const Logger = () => { export const App: React.FC = () => { const { appLocale } = useRecoilValue(userSettingsState); - const { fetchExtensions, fetchFeatureFlags } = useRecoilValue(dispatcherState); - const [showNodeModal, setShowNodeModal] = useState(true); + const userHasNode = useRecoilValue(userHasNodeInstalledState); + + const { fetchExtensions, fetchFeatureFlags, checkNodeVersion } = useRecoilValue(dispatcherState); + const [showNodeModal, setShowNodeModal] = useState(false); useEffect(() => { loadLocale(appLocale); }, [appLocale]); useEffect(() => { + checkNodeVersion(); fetchExtensions(); fetchFeatureFlags(); }, []); + useEffect(() => { + if (!userHasNode) { + setShowNodeModal(true); + } + }, [userHasNode]); + return ( {showNodeModal && } diff --git a/Composer/packages/client/src/recoilModel/atoms/appState.ts b/Composer/packages/client/src/recoilModel/atoms/appState.ts index 84364406ff..833451bb48 100644 --- a/Composer/packages/client/src/recoilModel/atoms/appState.ts +++ b/Composer/packages/client/src/recoilModel/atoms/appState.ts @@ -334,3 +334,8 @@ export const isWebChatPanelVisibleState = atom({ key: getFullyQualifiedKey('isWebChatPanelVisible'), default: false, }); + +export const userHasNodeInstalledState = atom({ + key: getFullyQualifiedKey('userHasNodeInstalled'), + default: true, +}); diff --git a/Composer/packages/client/src/recoilModel/dispatchers/application.ts b/Composer/packages/client/src/recoilModel/dispatchers/application.ts index 906f1d2a35..259d31253e 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/application.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/application.ts @@ -4,6 +4,7 @@ import { CallbackInterface, useRecoilCallback } from 'recoil'; import debounce from 'lodash/debounce'; +import formatMessage from 'format-message'; import { appUpdateState, @@ -15,11 +16,14 @@ import { pageElementState, debugPanelExpansionState, debugPanelActiveTabState, + userHasNodeInstalledState, + applicationErrorState, } from '../atoms/appState'; import { AppUpdaterStatus, CreationFlowStatus, CreationFlowType } from '../../constants'; import OnboardingState from '../../utils/onboardingStorage'; import { StateError, AppUpdateState } from '../../recoilModel/types'; import { DebugDrawerKeys } from '../../pages/design/DebugPanel/TabExtensions/types'; +import httpClient from '../../utils/httpUtil'; import { setError } from './shared'; @@ -130,7 +134,21 @@ export const applicationDispatcher = () => { } ); + const checkNodeVersion = useRecoilCallback(({ set }: CallbackInterface) => async () => { + try { + const response = await httpClient.get(`/utilities/checkNode`); + const userHasNode = response.data?.userHasNode; + set(userHasNodeInstalledState, userHasNode); + } catch (err) { + set(applicationErrorState, { + message: formatMessage('Error checking node version'), + summary: err.message, + }); + } + }); + return { + checkNodeVersion, setAppUpdateStatus, setAppUpdateShowing, setAppUpdateError, diff --git a/Composer/packages/server/src/controllers/utilities.ts b/Composer/packages/server/src/controllers/utilities.ts index 72c76c6187..61ae91c18b 100644 --- a/Composer/packages/server/src/controllers/utilities.ts +++ b/Composer/packages/server/src/controllers/utilities.ts @@ -1,8 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { exec } from 'child_process'; +import { promisify } from 'util'; + import { Request, Response } from 'express'; import { parseQnAContent } from '../models/utilities/parser'; +const execAsync = promisify(exec); async function getQnaContent(req: Request, res: Response) { try { @@ -16,6 +20,26 @@ async function getQnaContent(req: Request, res: Response) { } } +async function checkNodeVersion(req: Request, res: Response) { + try { + const command = 'node -v'; + const { stderr: checkNodeError, stdout: nodeVersion } = await execAsync(command); + if (checkNodeError) { + throw new Error(); + } else { + res.status(200).json({ + userHasNode: true, + nodeVersion: nodeVersion, + }); + } + } catch (e) { + res.status(200).json({ + userHasNode: false, + }); + } +} + export const UtilitiesController = { getQnaContent, + checkNodeVersion, }; diff --git a/Composer/packages/server/src/locales/en-US.json b/Composer/packages/server/src/locales/en-US.json index a1cb3db09b..4644bc24d2 100644 --- a/Composer/packages/server/src/locales/en-US.json +++ b/Composer/packages/server/src/locales/en-US.json @@ -467,6 +467,9 @@ "bot_framework_composer_is_an_open_source_visual_au_2be2e02b": { "message": "Bot Framework Composer is an open-source visual authoring canvas for developers and multi-disciplinary teams to build bots. Composer integrates LUIS and QnA Maker, and allows sophisticated composition of bot replies using language generation." }, + "bot_framework_composer_requires_node_js_in_order_t_a1d3dfb": { + "message": "Bot Framework Composer requires Node.js in order to run. Click “Install Node.js” to install the latest version" + }, "bot_framework_provides_the_most_comprehensive_expe_e34a7f5d": { "message": "Bot Framework provides the most comprehensive experience for building conversational applications." }, @@ -1739,6 +1742,9 @@ "install_more_adapters_in_a_the_package_manager_a_156fb028": { "message": "Install more adapters in the package manager." }, + "install_node_js_1857298c": { + "message": "Install Node.js" + }, "install_pre_release_versions_of_composer_daily_to__ceb41b54": { "message": "Install pre-release versions of Composer, daily, to access and test the latest features. Learn more." }, diff --git a/Composer/packages/server/src/router/api.ts b/Composer/packages/server/src/router/api.ts index f3ae8d7e4b..f7375dac87 100644 --- a/Composer/packages/server/src/router/api.ts +++ b/Composer/packages/server/src/router/api.ts @@ -96,6 +96,8 @@ router.use('/assets/locales/', express.static(path.join(__dirname, '..', '..', ' //help api router.get('/utilities/qna/parse', UtilitiesController.getQnaContent); +router.get('/utilities/checkNode', UtilitiesController.checkNodeVersion); + // extensions router.get('/extensions', ExtensionsController.listExtensions); router.post('/extensions', ExtensionsController.addExtension); From b8bfb57c23f93aaf6f65d9d13db366ba737df287 Mon Sep 17 00:00:00 2001 From: Patrick Volum Date: Wed, 7 Apr 2021 13:47:55 -0400 Subject: [PATCH 3/4] Moving node check to creation flow as opposed to app start --- Composer/packages/client/src/App.tsx | 13 ++----------- .../components/CreationFlow/v2/CreateOptions.tsx | 12 ++++++++++++ .../src/components/CreationFlow/v2/NodeModal.tsx | 2 +- Composer/packages/server/src/locales/en-US.json | 3 +++ 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Composer/packages/client/src/App.tsx b/Composer/packages/client/src/App.tsx index 86062151ea..724b865eb0 100644 --- a/Composer/packages/client/src/App.tsx +++ b/Composer/packages/client/src/App.tsx @@ -8,10 +8,9 @@ import { useRecoilValue } from 'recoil'; import { Header } from './components/Header'; import { Announcement } from './components/AppComponents/Announcement'; import { MainContainer } from './components/AppComponents/MainContainer'; -import { dispatcherState, userHasNodeInstalledState, userSettingsState } from './recoilModel'; +import { dispatcherState, userSettingsState } from './recoilModel'; import { loadLocale } from './utils/fileUtil'; import { useInitializeLogger } from './telemetry/useInitializeLogger'; -import { NodeModal } from './components/CreationFlow/v2/NodeModal'; initializeIcons(undefined, { disableWarnings: true }); @@ -22,10 +21,9 @@ const Logger = () => { export const App: React.FC = () => { const { appLocale } = useRecoilValue(userSettingsState); - const userHasNode = useRecoilValue(userHasNodeInstalledState); const { fetchExtensions, fetchFeatureFlags, checkNodeVersion } = useRecoilValue(dispatcherState); - const [showNodeModal, setShowNodeModal] = useState(false); + useEffect(() => { loadLocale(appLocale); }, [appLocale]); @@ -36,15 +34,8 @@ export const App: React.FC = () => { fetchFeatureFlags(); }, []); - useEffect(() => { - if (!userHasNode) { - setShowNodeModal(true); - } - }, [userHasNode]); - return ( - {showNodeModal && }
diff --git a/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx b/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx index 72b8c99f2e..94e8eab8ca 100644 --- a/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx +++ b/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx @@ -13,11 +13,14 @@ import { DialogWrapper, DialogTypes } from '@bfc/ui-shared'; import { navigate, RouteComponentProps } from '@reach/router'; import querystring from 'query-string'; import axios from 'axios'; +import { useRecoilValue } from 'recoil'; import { DialogCreationCopy } from '../../../constants'; import { getAliasFromPayload } from '../../../utils/electronUtil'; +import { userHasNodeInstalledState } from '../../../recoilModel'; import { CreateBotV2 } from './CreateBot'; +import { NodeModal } from './NodeModal'; // -------------------- CreateOptions -------------------- // type CreateOptionsProps = { @@ -34,6 +37,8 @@ export function CreateOptionsV2(props: CreateOptionsProps) { const [option, setOption] = useState('Create'); const [isOpenCreateModal, setIsOpenCreateModal] = useState(false); const { templates, onDismiss, onNext, onJumpToOpenModal, fetchTemplates, fetchReadMe } = props; + const [showNodeModal, setShowNodeModal] = useState(false); + const userHasNode = useRecoilValue(userHasNodeInstalledState); useEffect(() => { // open bot directly if alias exist. @@ -78,6 +83,12 @@ export function CreateOptionsV2(props: CreateOptionsProps) { } }; + useEffect(() => { + if (!userHasNode) { + setShowNodeModal(true); + } + }, [userHasNode]); + return ( + {showNodeModal && } ); } diff --git a/Composer/packages/client/src/components/CreationFlow/v2/NodeModal.tsx b/Composer/packages/client/src/components/CreationFlow/v2/NodeModal.tsx index 8285349075..0feaa94798 100644 --- a/Composer/packages/client/src/components/CreationFlow/v2/NodeModal.tsx +++ b/Composer/packages/client/src/components/CreationFlow/v2/NodeModal.tsx @@ -33,7 +33,7 @@ export const NodeModal: React.FC = (props) => { > {formatMessage( - 'Bot Framework Composer requires Node.js in order to run. Click “Install Node.js” to install the latest version' + 'Bot Framework Composer requires Node.js in order to create and run a new bot. Click “Install Node.js” to install the latest version' )} diff --git a/Composer/packages/server/src/locales/en-US.json b/Composer/packages/server/src/locales/en-US.json index 83a2780afc..ceee299bad 100644 --- a/Composer/packages/server/src/locales/en-US.json +++ b/Composer/packages/server/src/locales/en-US.json @@ -1445,6 +1445,9 @@ "error_afac7133": { "message": "Error:" }, + "error_checking_node_version_98bfbf4c": { + "message": "Error checking node version" + }, "error_encountered_when_getting_template_readme_17dcbc61": { "message": "### Error encountered when getting template readMe" }, From bf9dd12e3d4859b872aa8aaa8ee4057e44994670 Mon Sep 17 00:00:00 2001 From: Patrick Volum Date: Wed, 7 Apr 2021 14:40:42 -0400 Subject: [PATCH 4/4] Add isElectron check for node modal --- Composer/packages/client/src/App.tsx | 2 +- .../client/src/components/CreationFlow/v2/CreateOptions.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Composer/packages/client/src/App.tsx b/Composer/packages/client/src/App.tsx index 724b865eb0..fe83d396dd 100644 --- a/Composer/packages/client/src/App.tsx +++ b/Composer/packages/client/src/App.tsx @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import React, { Fragment, useEffect, useState } from 'react'; +import React, { Fragment, useEffect } from 'react'; import { initializeIcons } from 'office-ui-fabric-react/lib/Icons'; import { useRecoilValue } from 'recoil'; diff --git a/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx b/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx index 2a239db458..3f7a601f7c 100644 --- a/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx +++ b/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx @@ -18,7 +18,7 @@ import axios from 'axios'; import { useRecoilValue } from 'recoil'; import { DialogCreationCopy } from '../../../constants'; -import { getAliasFromPayload } from '../../../utils/electronUtil'; +import { getAliasFromPayload, isElectron } from '../../../utils/electronUtil'; import { userHasNodeInstalledState } from '../../../recoilModel'; import { CreateBotV2 } from './CreateBot'; @@ -135,7 +135,7 @@ export function CreateOptionsV2(props: CreateOptionsProps) { onDismiss={onDismiss} onNext={onNext} /> - {showNodeModal && } + {isElectron() && showNodeModal && } ); }