From 70e13ef18838cd4dda6602fbf7dad4abba1433c2 Mon Sep 17 00:00:00 2001 From: zeye <2295905420@qq.com> Date: Fri, 29 Nov 2019 11:41:51 +0800 Subject: [PATCH 1/2] feat: Trigger Node (#1529) * new trigger * add `subtitle` in ConceptLabels to override some title * refine new css of Trigger * fix an unsafe prop visit * fix eslint import path * move styles to 'triggerStyles.ts' * use lodash.get in Trigger.tsx * add subtitle in form editor (#1662) * Final changes * Remove unnecessary guard * enable debug when running tests * fix ci: BreadCrumb * fix ci: CreateNewBot * revert BreadCrumb * delete data.json before each e2e test * More labeling * Fix e2e test * fix visual designer spec * use findAllBy for breadcrumb spec sometimes more than one breadcrumb is found. this will use the last one found * use debug logger * pipe server output to log file and print after tests are run * fix up e2e script * run headless * pipe server logs to file * only invoke cleanup function once * fix visual designer spec again * display server logs * add more debug logging * always print e2e logs * Base leileizh's e2e fixes * update the node env --- .../cypress/integration/Breadcrumb.spec.ts | 13 +-- .../cypress/integration/CreateNewBot.spec.ts | 4 +- .../integration/NotificationPage.spec.ts | 1 + .../integration/VisualDesigner.spec.ts | 9 +- Composer/cypress/support/commands.ts | 6 ++ Composer/package.json | 2 +- .../ProjectTree/TriggerCreationModal.tsx | 2 +- .../packages/client/src/utils/dialogUtil.ts | 12 ++- .../src/Form/fields/RootField.tsx | 5 ++ .../obiformeditor/src/Form/fields/styles.css | 10 ++- .../src/components/nodes/Trigger.tsx | 64 ++++++------- .../src/components/nodes/triggerStyles.ts | 43 +++++++++ Composer/packages/lib/shared/src/labelMap.ts | 38 ++++++-- Composer/packages/lib/shared/src/viewUtils.ts | 8 +- .../packages/server/schemas/editor.schema | 63 +++++++++---- .../server/src/controllers/project.ts | 4 + .../server/src/models/asset/assetManager.ts | 2 + Composer/packages/server/src/server.ts | 3 +- .../packages/server/src/services/project.ts | 3 +- Composer/packages/server/src/store/store.ts | 1 + Composer/scripts/e2e.sh | 13 ++- azure-pipelines.yml | 11 ++- docs/concept-dialog.md | 89 +++++++++++-------- docs/concept-events-and-triggers.md | 2 +- 24 files changed, 280 insertions(+), 128 deletions(-) create mode 100644 Composer/packages/extensions/visual-designer/src/components/nodes/triggerStyles.ts diff --git a/Composer/cypress/integration/Breadcrumb.spec.ts b/Composer/cypress/integration/Breadcrumb.spec.ts index 8c647ee409..63adecd879 100644 --- a/Composer/cypress/integration/Breadcrumb.spec.ts +++ b/Composer/cypress/integration/Breadcrumb.spec.ts @@ -13,7 +13,8 @@ context('breadcrumb', () => { }); function hasBreadcrumbItems(cy: Cypress.cy, items: (string | RegExp)[]) { - cy.findByTestId('Breadcrumb') + cy.get('[data-testid="Breadcrumb"]') + .last() .get('li') .should($li => { items.forEach((item, idx) => { @@ -37,21 +38,21 @@ context('breadcrumb', () => { cy.findByText('__TestTodoSample.Main').click(); }); - hasBreadcrumbItems(cy, ['__TestTodoSample']); + hasBreadcrumbItems(cy, ['__TestTodoSample.Main']); }); it('can show event name in breadcrumb', () => { cy.findByTestId('ProjectTree').within(() => { cy.findByText('AddToDo').click(); - cy.findByText('Dialog started (BeginDialog)').click(); + cy.findByText('Dialog started').click(); }); - hasBreadcrumbItems(cy, ['AddToDo', 'Dialog started (BeginDialog)']); + hasBreadcrumbItems(cy, ['AddToDo', 'Dialog started']); }); it('can show action name in breadcrumb', () => { cy.findByTestId('ProjectTree').within(() => { - cy.findByText('Greeting (ConversationUpdate)').click(); + cy.findByText('Greeting').click(); }); // Click on an action @@ -61,6 +62,6 @@ context('breadcrumb', () => { }); }); - hasBreadcrumbItems(cy, ['__TestTodoSample.Main', 'Greeting (ConversationUpdate)', 'Send a response']); + hasBreadcrumbItems(cy, ['__TestTodoSample.Main', 'Greeting', 'Send a response']); }); }); diff --git a/Composer/cypress/integration/CreateNewBot.spec.ts b/Composer/cypress/integration/CreateNewBot.spec.ts index a02d28cc9a..4841f3cde7 100644 --- a/Composer/cypress/integration/CreateNewBot.spec.ts +++ b/Composer/cypress/integration/CreateNewBot.spec.ts @@ -23,9 +23,9 @@ context('Creating a new bot', () => { cy.findByTestId('Create from template').click(); cy.findByTestId('TodoSample').click(); cy.findByTestId('NextStepButton').click(); - cy.findByTestId('NewDialogName').type('{selectall}__TestNewProject{enter}'); + cy.findByTestId('NewDialogName').type('{selectall}__TestNewProject2{enter}'); cy.findByTestId('ProjectTree').within(() => { - cy.findByText('__TestNewProject.Main').should('exist'); + cy.findByText('__TestNewProject2.Main').should('exist'); cy.findByText('AddToDo').should('exist'); cy.findByText('ClearToDos').should('exist'); cy.findByText('DeleteToDo').should('exist'); diff --git a/Composer/cypress/integration/NotificationPage.spec.ts b/Composer/cypress/integration/NotificationPage.spec.ts index 7a3d9fe657..0f98c58149 100644 --- a/Composer/cypress/integration/NotificationPage.spec.ts +++ b/Composer/cypress/integration/NotificationPage.spec.ts @@ -5,6 +5,7 @@ context('Notification Page', () => { beforeEach(() => { cy.visit(Cypress.env('COMPOSER_URL')); cy.createBot('TodoSample'); + cy.visitPage('Notifications'); }); it('can show lg syntax error ', () => { diff --git a/Composer/cypress/integration/VisualDesigner.spec.ts b/Composer/cypress/integration/VisualDesigner.spec.ts index 7ecdabfaa2..eb6ee33041 100644 --- a/Composer/cypress/integration/VisualDesigner.spec.ts +++ b/Composer/cypress/integration/VisualDesigner.spec.ts @@ -2,12 +2,9 @@ // Licensed under the MIT License. context('Visual Designer', () => { - before(() => { + beforeEach(() => { cy.visit(Cypress.env('COMPOSER_URL')); cy.createBot('TodoSample'); - }); - - beforeEach(() => { // Return to Main.dialog cy.findByTestId('ProjectTree').within(() => { cy.findByText('__TestTodoSample.Main').click(); @@ -16,11 +13,11 @@ context('Visual Designer', () => { it('can find Visual Designer default trigger in container', () => { cy.findByTestId('ProjectTree').within(() => { - cy.findByText('Greeting (ConversationUpdate)').click(); + cy.findByText('Greeting').click(); }); cy.withinEditor('VisualEditor', () => { - cy.findByText('Trigger').should('exist'); + cy.findByText('ConversationUpdate activity').should('exist'); }); }); }); diff --git a/Composer/cypress/support/commands.ts b/Composer/cypress/support/commands.ts index de5939e41f..60b0e246d9 100644 --- a/Composer/cypress/support/commands.ts +++ b/Composer/cypress/support/commands.ts @@ -23,4 +23,10 @@ Cypress.Commands.add('withinEditor', (editorName, cb) => { Cypress.Commands.add('visitPage', page => { cy.findByTestId(`LeftNav-CommandBarButton${page}`).click(); + cy.wait(3000); +}); + +Cypress.on('uncaught:exception', (err, runnable) => { + console.log('uncaught exception', err); + return false; }); diff --git a/Composer/package.json b/Composer/package.json index 742387b7b8..21b3b3df5e 100644 --- a/Composer/package.json +++ b/Composer/package.json @@ -34,7 +34,7 @@ "test:coverage": "yarn test --coverage --no-cache --reporters=default", "test:integration": "cypress run --browser chrome", "test:integration:open": "cypress open", - "test:integration:clean": "rimraf TestBots/*", + "test:integration:clean": "rimraf ../MyBots/__Test*", "lint": "wsrun --exclude-missing --collect-logs --report lint", "lint:fix": "wsrun --exclude-missing --collect-logs --report lint:fix", "typecheck": "concurrently --kill-others-on-fail \"npm:typecheck:*\"", diff --git a/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx b/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx index 6a6988b3cc..71c9075202 100644 --- a/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx @@ -180,7 +180,7 @@ export const TriggerCreationModal: React.FC = props = )} {showIntentDropDown && ( = props => { return designerName || dialogName || sdkOverrides.title || title || schema.title || startCase(name); }; + const getSubTitle = (): string => { + return sdkOverrides.subtitle || sdkOverrides.title || formData.$type; + }; + const getDescription = (): string => { return sdkOverrides.description || description || schema.description || ''; }; @@ -65,6 +69,7 @@ export const RootField: React.FC = props => { styleOverrides={{ field: { fontWeight: FontWeights.semibold } }} fontSize={FontSizes.size20} /> +

{getSubTitle()}

{sdkOverrides.description !== false && (description || schema.description) && (

{getDescription()} diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/styles.css b/Composer/packages/extensions/obiformeditor/src/Form/fields/styles.css index a73d8e3d8c..67b5509d34 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/styles.css +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/styles.css @@ -13,7 +13,15 @@ .RootFieldTitle { border-bottom: 1px solid #c8c6c4; padding: 0 18px; - margin-bottom: 16px; + margin-bottom: 0px; +} +.RootFieldSubtitle { + height: 15px; + line-height: 15px; + font-size: 12px; + font-weight: 600; + color: #4F4F4F; + margin: -7px 0 7px; } .RootFieldDescription { margin-top: 0; diff --git a/Composer/packages/extensions/visual-designer/src/components/nodes/Trigger.tsx b/Composer/packages/extensions/visual-designer/src/components/nodes/Trigger.tsx index 4827126ce7..82435f8dbd 100644 --- a/Composer/packages/extensions/visual-designer/src/components/nodes/Trigger.tsx +++ b/Composer/packages/extensions/visual-designer/src/components/nodes/Trigger.tsx @@ -5,42 +5,44 @@ import { ConceptLabels } from '@bfc/shared'; import { jsx } from '@emotion/core'; +import { Icon } from 'office-ui-fabric-react/lib/Icon'; +import get from 'lodash/get'; -import { ElementIcon } from '../../utils/obiPropertyResolver'; -import { TriggerSize } from '../../constants/ElementSizes'; - -import { FormCard } from './templates/FormCard'; +import { + triggerContainerStyle, + triggerContentStyle, + titleStyle, + subtitleStyle, + triggerIconStyle, +} from './triggerStyles'; function getLabel(data: any): string { - if (data.intent) { - return data.intent; - } - const labelOverrides = ConceptLabels[data.$type]; - - if (labelOverrides.title) { - return labelOverrides.title; + if (labelOverrides) { + return labelOverrides.subtitle || labelOverrides.title; } - return data.$type; } -export const Trigger = ({ data, onClick = () => {} }): JSX.Element => ( -

- -
-); +function getName(data: any): string { + return ( + data.intent || get(data, '$designer.name', ConceptLabels[data.$type] ? ConceptLabels[data.$type].title : data.$type) + ); +} + +export const Trigger = ({ data, onClick = () => {} }): JSX.Element => { + const name = getName(data); + const label = getLabel(data); + + return ( +
+
+
+ + {name} +
+
{label}
+
+
+ ); +}; diff --git a/Composer/packages/extensions/visual-designer/src/components/nodes/triggerStyles.ts b/Composer/packages/extensions/visual-designer/src/components/nodes/triggerStyles.ts new file mode 100644 index 0000000000..fb85ba5f65 --- /dev/null +++ b/Composer/packages/extensions/visual-designer/src/components/nodes/triggerStyles.ts @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License + +import { TriggerSize } from '../../constants/ElementSizes'; + +export const triggerContainerStyle: any = { + ...TriggerSize, + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'flex-end', + paddingBottom: '5px', + boxSizing: 'border-box', +}; + +export const triggerContentStyle: any = { + wordBreak: 'break-all', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', +}; + +export const titleStyle: any = { + whiteSpace: 'nowrap', + color: '#333333', + fontFamily: 'Segoe UI', + fontSize: '18px', + lineHeight: '24px', + fontWeight: 600, +}; + +export const subtitleStyle: any = { + whiteSpace: 'nowrap', + color: '#4f4f4f', + fontFamily: 'Segoe UI', + fontSize: '12px', + lineHeight: '14px', +}; + +export const triggerIconStyle = { + lineHeight: '24px', + marginRight: '5px', +}; diff --git a/Composer/packages/lib/shared/src/labelMap.ts b/Composer/packages/lib/shared/src/labelMap.ts index 889fccdd8f..dec4ca588d 100644 --- a/Composer/packages/lib/shared/src/labelMap.ts +++ b/Composer/packages/lib/shared/src/labelMap.ts @@ -11,6 +11,7 @@ formatMessage.setup({ interface LabelOverride { title?: string | false; + subtitle?: string | false; description?: string | false; } @@ -118,66 +119,85 @@ export const ConceptLabels: { [key in ConceptLabelKey]?: LabelOverride } = { title: formatMessage('OAuth login'), }, [SDKTypes.OnActivity]: { - title: formatMessage('Activity trigger'), + title: formatMessage('Activities'), + subtitle: formatMessage('Activity recieved'), }, [SDKTypes.OnBeginDialog]: { - title: formatMessage('Dialog started (BeginDialog)'), + title: formatMessage('Dialog started'), + subtitle: formatMessage('Begin dialog event'), }, [SDKTypes.OnCancelDialog]: { - title: formatMessage('Dialog cancelled (CancelDialog)'), + title: formatMessage('Dialog cancelled'), + subtitle: formatMessage('Cancel dialog event'), }, [SDKTypes.OnCondition]: { title: formatMessage('Handle a Condition'), }, [SDKTypes.OnConversationUpdateActivity]: { - title: formatMessage('Greeting (ConversationUpdate)'), + title: formatMessage('Greeting'), + subtitle: formatMessage('ConversationUpdate activity'), description: 'Handle the events fired when a user begins a new conversation with the bot. Learn more', }, [SDKTypes.OnCustomEvent]: { - title: formatMessage('Custom trigger'), + title: formatMessage('Custom event'), + subtitle: formatMessage('Custom event'), }, [SDKTypes.OnDialogEvent]: { - title: formatMessage('Dialog trigger'), + title: formatMessage('Dialog events'), + subtitle: formatMessage('Dialog events'), }, [SDKTypes.OnEndOfConversationActivity]: { title: formatMessage('Conversation ended'), + subtitle: formatMessage('EndOfConversation activity'), }, [SDKTypes.OnError]: { title: formatMessage('Error occurred'), + subtitle: formatMessage('Error event'), }, [SDKTypes.OnEventActivity]: { title: formatMessage('Event received'), + subtitle: formatMessage('Event activity'), }, [SDKTypes.OnHandoffActivity]: { title: formatMessage('Handover to human'), + subtitle: formatMessage('Handoff activity'), }, [SDKTypes.OnIntent]: { - title: formatMessage('Intent'), + title: formatMessage('Intent recognized'), + subtitle: formatMessage('Intent recognized'), }, [SDKTypes.OnInvokeActivity]: { title: formatMessage('Conversation invoked'), + subtitle: formatMessage('Invoke activity'), }, [SDKTypes.OnMessageActivity]: { - title: formatMessage('Message activity trigger'), + title: formatMessage('Message events'), + subtitle: formatMessage('Message recieved activity'), }, [SDKTypes.OnMessageDeleteActivity]: { title: formatMessage('Message deleted'), + subtitle: formatMessage('Message deleted activity'), }, [SDKTypes.OnMessageReactionActivity]: { title: formatMessage('Message reaction'), + subtitle: formatMessage('Message reaction activity'), }, [SDKTypes.OnMessageUpdateActivity]: { title: formatMessage('Message updated'), + subtitle: formatMessage('Message updated activity'), }, [SDKTypes.OnRepromptDialog]: { title: formatMessage('Re-prompt for input'), + subtitle: formatMessage('Reprompt dialog event'), }, [SDKTypes.OnTypingActivity]: { title: formatMessage('User is typing'), + subtitle: formatMessage('Typing activity'), }, [SDKTypes.OnUnknownIntent]: { - title: formatMessage('Unrecognized intent'), + title: formatMessage('Unknown intent'), + subtitle: formatMessage('Unknown intent recognized'), }, [SDKTypes.QnAMakerDialog]: { title: formatMessage('Connect to QnA Knowledgebase'), diff --git a/Composer/packages/lib/shared/src/viewUtils.ts b/Composer/packages/lib/shared/src/viewUtils.ts index 332383dc6c..5a59ce4c15 100644 --- a/Composer/packages/lib/shared/src/viewUtils.ts +++ b/Composer/packages/lib/shared/src/viewUtils.ts @@ -110,6 +110,7 @@ export const dialogGroups: DialogGroupsMap = { [DialogGroup.ADVANCED_EVENTS]: { label: 'Advanced Events', types: [ + SDKTypes.OnActivity, SDKTypes.OnConversationUpdateActivity, SDKTypes.OnEndOfConversationActivity, SDKTypes.OnEventActivity, @@ -120,7 +121,12 @@ export const dialogGroups: DialogGroupsMap = { }, [DialogGroup.MESSAGE_EVENTS]: { label: 'Message events', - types: [SDKTypes.OnMessageDeleteActivity, SDKTypes.OnMessageReactionActivity, SDKTypes.OnMessageUpdateActivity], + types: [ + SDKTypes.OnMessageActivity, + SDKTypes.OnMessageDeleteActivity, + SDKTypes.OnMessageReactionActivity, + SDKTypes.OnMessageUpdateActivity, + ], }, [DialogGroup.RECOGNIZER]: { label: 'Recognizers', diff --git a/Composer/packages/server/schemas/editor.schema b/Composer/packages/server/schemas/editor.schema index c4fc174b0e..b6bb740327 100644 --- a/Composer/packages/server/schemas/editor.schema +++ b/Composer/packages/server/schemas/editor.schema @@ -29,6 +29,10 @@ "helpLink": "https://aka.ms/bfc-understanding-dialogs", "helpLinkText": "Learn more" }, + "Microsoft.OnCancelDialog": { + "title": "Dialog cancelled", + "subtitle": "Cancel dialog event" + }, "Microsoft.CancelAllDialogs": { "title": "Cancel All Dialogs", "helpLink": "https://aka.ms/bfc-understanding-dialogs", @@ -144,58 +148,83 @@ "helpLink": "https://aka.ms/bfc-using-oauth" }, "Microsoft.OnActivity": { - "title": "Activity trigger" + "title": "Activities", + "subtitle": "Activity recieved" }, "Microsoft.OnBeginDialog": { - "title": "Dialog started (BeginDialog)" + "title": "Dialog started", + "subtitle": "Begin dialog event" }, "Microsoft.OnConversationUpdateActivity": { - "title": "Greeting (ConversationUpdate)", + "title": "Greeting", + "subtitle": "ConversationUpdate activity", "description": "Handle the events fired when a user begins a new conversation with the bot.", "helpLink": "https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-conversations?view=azure-bot-service-4.0#conversation-lifetime", "helpLinkText": "Learn more" }, "Microsoft.OnDialogEvent": { - "title": "Dialog event" + "title": "Dialog events", + "subtitle": "Dialog event" }, "Microsoft.OnEndOfConversationActivity": { - "title": "Conversation ended" + "title": "Conversation ended", + "subtitle": "EndOfConversation activity" }, "Microsoft.OnCondition": { - "title": "Handle an condition" + "title": "Handle a condition", + "subtitle": "Condition" }, "Microsoft.OnCustomEvent": { - "title": "Handle an Event" + "title": "Handle an Event", + "subtitle": "Custom event" }, "Microsoft.OnEventActivity": { - "title": "Event received" + "title": "Event received", + "subtitle": "Event activity" + }, + "Microsoft.OnError": { + "title": "Error occurred", + "subtitle": "Error event" }, "Microsoft.OnHandoffActivity": { - "title": "Handover to human" + "title": "Handover to human", + "subtitle": "Handoff activity" }, "Microsoft.OnIntent": { - "title": "Intent" + "title": "Intent recognized", + "subtitle": "Intent recognized" }, "Microsoft.OnInvokeActivity": { - "title": "Conversation invoked" + "title": "Conversation invoked", + "subtitle": "Invoke activity" }, "Microsoft.OnMessageActivity": { - "title": "Message received" + "title": "Message events", + "subtitle": "Message recieved activity" }, "Microsoft.OnMessageDeleteActivity": { - "title": "Message deleted" + "title": "Message deleted", + "subtitle": "Message deleted activity" }, "Microsoft.OnMessageReactionActivity": { - "title": "Message reaction" + "title": "Message reaction", + "subtitle": "Message reaction activity" }, "Microsoft.OnMessageUpdateActivity": { - "title": "Message updated" + "title": "Message updated", + "subtitle": "Message updated activity" }, "Microsoft.OnTypingActivity": { - "title": "User is typing" + "title": "User is typing", + "subtitle": "Typing activity" + }, + "Microsoft.OnRepromptDialog": { + "title": "Re-prompt for input", + "subtitle": "Reprompt dialog event" }, "Microsoft.OnUnknownIntent": { - "title": "Unrecognized intent" + "title": "Unknown intent", + "subtitle": "Unknown intent recognized" }, "Microsoft.QnAMakerDialog": { "title": "QnAMakerDialog", diff --git a/Composer/packages/server/src/controllers/project.ts b/Composer/packages/server/src/controllers/project.ts index 7753fbdc84..9a5b391a86 100644 --- a/Composer/packages/server/src/controllers/project.ts +++ b/Composer/packages/server/src/controllers/project.ts @@ -5,6 +5,7 @@ import * as fs from 'fs'; import { Request, Response } from 'express'; +import log from '../logger'; import { BotProjectService } from '../services/project'; import AssectService from '../services/asset'; import { LocationRef } from '../models/bot/interface'; @@ -37,6 +38,8 @@ async function createProject(req: Request, res: Response) { path: Path.resolve(path, name), }; + log('Attempting to create project at %s', path); + try { const newProjRef = await AssectService.manager.copyProjectTemplateTo(templateId, locationRef); await BotProjectService.openProject(newProjRef); @@ -45,6 +48,7 @@ async function createProject(req: Request, res: Response) { await currentProject.updateBotInfo(name, description); await currentProject.index(); const project = currentProject.getIndexes(); + log('Project created successfully.'); res.status(200).json({ ...project, }); diff --git a/Composer/packages/server/src/models/asset/assetManager.ts b/Composer/packages/server/src/models/asset/assetManager.ts index 4ecedd844d..440b5b4ace 100644 --- a/Composer/packages/server/src/models/asset/assetManager.ts +++ b/Composer/packages/server/src/models/asset/assetManager.ts @@ -4,6 +4,7 @@ import find from 'lodash/find'; import { ProjectTemplate } from '@bfc/shared'; +import log from '../../logger'; import { LocalDiskStorage } from '../storage/localDiskStorage'; import { LocationRef } from '../bot/interface'; import { Path } from '../../utility/path'; @@ -171,6 +172,7 @@ export class AssetManager { const dstStorage = StorageService.getStorageClient(ref.storageId); const dstDir = Path.resolve(ref.path); if (await dstStorage.exists(dstDir)) { + log('Failed copying template to %s', dstDir); throw new Error('already have this folder, please give another name'); } diff --git a/Composer/packages/server/src/server.ts b/Composer/packages/server/src/server.ts index 0114f67a97..e332b38c4d 100644 --- a/Composer/packages/server/src/server.ts +++ b/Composer/packages/server/src/server.ts @@ -13,6 +13,7 @@ import compression from 'compression'; import { getAuthProvider } from './router/auth'; import { apiRouter } from './router/api'; import { BASEURL } from './constants'; +import log from './logger'; const app: Express = express(); app.set('view engine', 'ejs'); @@ -81,7 +82,7 @@ app.use(`${BASEURL}/api`, authorize, apiRouter); app.use(function(err: Error, req: Request, res: Response, _next: NextFunction) { if (err) { - console.log(err); + log(err); res.status(500).json({ message: err.message }); } }); diff --git a/Composer/packages/server/src/services/project.ts b/Composer/packages/server/src/services/project.ts index ef966bba08..8bbbba378e 100644 --- a/Composer/packages/server/src/services/project.ts +++ b/Composer/packages/server/src/services/project.ts @@ -7,6 +7,7 @@ import find from 'lodash/find'; import { BotProject } from '../models/bot/botProject'; import { LocationRef } from '../models/bot/interface'; import { Store } from '../store/store'; +import log from '../logger'; import StorageService from './storage'; import { Path } from './../utility/path'; @@ -44,7 +45,7 @@ export class BotProjectService { dateModified = await StorageService.getBlobDateModified(project.storageId, project.path); dateModifiedDict.push({ dateModified, path: project.path }); } catch (err) { - console.error(err); + log(err); } }); await Promise.all(promises); diff --git a/Composer/packages/server/src/store/store.ts b/Composer/packages/server/src/store/store.ts index b8af7d62ec..ef071fafa1 100644 --- a/Composer/packages/server/src/store/store.ts +++ b/Composer/packages/server/src/store/store.ts @@ -67,6 +67,7 @@ class JsonStore implements KVStore { private ensureStore() { if (!fs.existsSync(this.filePath)) { + log('data.json not found. Re-initializing with initial data.'); this.initializeStore(); } } diff --git a/Composer/scripts/e2e.sh b/Composer/scripts/e2e.sh index 3846b0fedf..6eda9118be 100755 --- a/Composer/scripts/e2e.sh +++ b/Composer/scripts/e2e.sh @@ -1,17 +1,14 @@ #!/usr/bin/env bash -set -e +function cleanup { + kill $SERVER_PID +} -yarn start & +yarn start >> e2e.log 2>&1 & SERVER_PID=$! -npx cypress run --browser chrome +npx cypress run EXIT_CODE=$? -cleanup - -function cleanup { - kill $SERVER_PID -} # kill server process trap cleanup EXIT diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f0b2fa4364..96892f72f6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -5,7 +5,7 @@ pr: autoCancel: true branches: include: - - '*' + - "*" variables: YARN_CACHE_FOLDER: $(Pipeline.Workspace)/.yarn @@ -52,10 +52,17 @@ jobs: CYPRESS_SCREENSHOTS_FOLDER: $(Build.ArtifactStagingDirectory)/cypress/screenshots CYPRESS_VIDEOS_FOLDER: $(Build.ArtifactStagingDirectory)/cypress/videos TERM: xterm - COMPOSER_BOTS_FOLDER: $(Pipeline.Workspace)/Composer/TestBots + COMPOSER_BOTS_FOLDER: $(System.DefaultWorkingDirectory)/MyBots + DEBUG: composer - task: PublishPipelineArtifact@1 + displayName: Publish Cypress Artifacts condition: failed() continueOnError: true inputs: targetPath: $(Build.ArtifactStagingDirectory)/cypress artifactName: e2e + - script: cat e2e.log + displayName: Server Logs + condition: always() + continueOnError: true + workingDirectory: Composer diff --git a/docs/concept-dialog.md b/docs/concept-dialog.md index 7ea074c3d8..2bfdd048b6 100644 --- a/docs/concept-dialog.md +++ b/docs/concept-dialog.md @@ -1,102 +1,113 @@ -# Dialogs +# Dialogs + Modern conversational software is comprised of many components, including programming code, custom business logic, cloud APIs, training data for language processing systems and perhaps most importantly, the actual content used in conversations with the bot's end users. With Composer, all of these pieces are integrated with one another into a single interface for constructing blocks of bot functionality called **Dialogs**. ([SDK Docs: Bot Framework Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0)) -Each dialog represents a piece of the bot's functionality. They contain instructions for how the bot will react to input. Simple bots will have a few dialogs. Complex bots may have dozens or hundreds of individual dialogs. +Each dialog represents a piece of the bot's functionality. They contain instructions for how the bot will react to input. Simple bots will have a few dialogs. Complex bots may have dozens or hundreds of individual dialogs. In Composer, dialogs are functional components offered in a visual interface and do not require you to write code. The dialog system supports building a pluggable and extensible model that integrates building blocks of bot functionality. Dialogs help users focus on conversation modeling rather than the mechanics of dialog management. -## Types of dialogs -There are two types of dialogs in Composer: main dialog and child dialog. Below is a screenshot of a main dialog named `MyBot.Main` and two child dialogs named `Weather` and `Greeting`. +## Types of dialogs + +There are two types of dialogs in Composer: main dialog and child dialog. Below is a screenshot of a main dialog named `MyBot.Main` and two child dialogs named `Weather` and `Greeting`. ![main_child_dialog](./media/dialog/main_child_dialog.png) -You create a dialog in Composer to manage a conversation objective. Main dialog is initialized by default when you create a new bot and it has a **.Main** file extension. Each bot has one main dialog and can have multiple child dialogs or no child dialogs. Read the [Add a dialog](./tutorial/bot-tutorial-add-dialog.md) section to create a dialog in Composer. +You create a dialog in Composer to manage a conversation objective. Main dialog is initialized by default when you create a new bot and it has a **.Main** file extension. Each bot has one main dialog and can have multiple child dialogs or no child dialogs. Read the [Add a dialog](./tutorial/bot-tutorial-add-dialog.md) section to create a dialog in Composer. -At runtime, the main dialog is called into action and becomes the active dialog, triggering event handlers with pre-defined actions. As the conversation flows, a child dialog can be called by a main dialog, and vice versa. Different child dialogs can be called with each other as well. +At runtime, the main dialog is called into action and becomes the active dialog, triggering event handlers with pre-defined actions. As the conversation flows, a child dialog can be called by a main dialog, and vice versa. Different child dialogs can be called with each other as well. -## Anatomy of a dialog -The following diagram shows the anatomy of a dialog in Composer. Note that dialogs in Composer are based on [Adaptive dialogs](https://github.com/Microsoft/BotBuilder-Samples/tree/master/experimental/adaptive-dialog#readme). +## Anatomy of a dialog + +The following diagram shows the anatomy of a dialog in Composer. Note that dialogs in Composer are based on [Adaptive dialogs](https://github.com/Microsoft/BotBuilder-Samples/tree/master/experimental/adaptive-dialog#readme). ![adaptive-dialog-anatomy](./media/dialog/adaptive-dialog-anatomy.png) ### Recognizer -When a dialog is called into action its **recognizer** will start to process the message and try to extract the primary **intent** and any **entity values** the message includes. After processing the message, both the **intent** and **entity values** are passed onto the dialog's triggers. Composer currently supports two recognizers: the LUIS recognizer (default) and the Regular Expression recognizer. You can choose only one recognizer per dialog, and a dialog can have no recognizer choosing the `None` type. Below is a screenshot of recognizers supported in Composer. + +When a dialog is called into action its **recognizer** will start to process the message and try to extract the primary **intent** and any **entity values** the message includes. After processing the message, both the **intent** and **entity values** are passed onto the dialog's triggers. Composer currently supports two recognizers: the LUIS recognizer (default) and the Regular Expression recognizer. You can choose only one recognizer per dialog, and a dialog can have no recognizer choosing the `None` type. Below is a screenshot of recognizers supported in Composer. ![recognizer](./media/dialog/recognizer.png) **Recognizers** provide the functionality of understanding and extracting meaningful pieces of information from a user's input. All recognizers emit events when the recognizer picks up an **intent** (or extracts **entities**) from a given user utterance. A **recognizer** of a dialog is not always called into play when a dialog is called. It depends on how you design the dialog system. ### Trigger -The functionality of a dialog is contained within triggers - rules that tell the bot how to process incoming messages. They are also used to define a wide variety of bot behaviors, from performing the main fulfillment of the user's request, to handling [interruptions](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-handle-user-interrupt?view=azure-bot-service-4.0&tabs=csharp) like requests for help, to handling custom, developer-defined events originating from the app itself. Below is a screenshot of the trigger menu in Composer. + +The functionality of a dialog is contained within triggers - rules that tell the bot how to process incoming messages. They are also used to define a wide variety of bot behaviors, from performing the main fulfillment of the user's request, to handling [interruptions](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-handle-user-interrupt?view=azure-bot-service-4.0&tabs=csharp) like requests for help, to handling custom, developer-defined events originating from the app itself. Below is a screenshot of the trigger menu in Composer. ![trigger_menu](./media/dialog/trigger_menu.gif) -### Action -Triggers contain a series of actions that the bot will undertake to fulfill a user's request. Actions are things like sending messages, making calculations, and performing computational tasks on behalf of the user. The path the bot follows through a dialog can branch and loop. The bot can ask questions, validate input, manipulate and store values in memory, and make decisions. Below is a screenshot of the action menu in Composer. Click the **+** sign below the trigger you can mouse over the action menu. +### Action + +Triggers contain a series of actions that the bot will undertake to fulfill a user's request. Actions are things like sending messages, making calculations, and performing computational tasks on behalf of the user. The path the bot follows through a dialog can branch and loop. The bot can ask questions, validate input, manipulate and store values in memory, and make decisions. Below is a screenshot of the action menu in Composer. Click the **+** sign below the trigger you can mouse over the action menu. ![action_menu](./media/dialog/action_menu.gif) ### Language generator -As the bot takes actions and sends messages, the **language generator** is called into play. This allows messages sent by the bot to be composed from variables and templates. Language generators can be used to create reusable components, variable messages, macros, and dynamic messages that are grammatically correct. + +As the bot takes actions and sends messages, the **language generator** is called into play. This allows messages sent by the bot to be composed from variables and templates. Language generators can be used to create reusable components, variable messages, macros, and dynamic messages that are grammatically correct. -## Create a dialog + +## Create a dialog + When you create a bot in Composer you also create its main dialog by default. Follow the steps to create a bot project and its main dialog: -1. On the left side of the Composer home screen, click **+ New** from the upper left corner (or the big **+ ** sign under "Bot Framework Composer" in the middle part of the home screen). +1. On the left side of the Composer home screen, click **+ New** from the upper left corner (or the big **+ ** sign under "Bot Framework Composer" in the middle part of the home screen). -![create_new_bot](./media/dialog/create_new_bot.png) +![create_new_bot](./media/dialog/create_new_bot.png) -2. After you see the pop-up window, select **Create from scratch** and click **Submit**. +2. After you see the pop-up window, select **Create from scratch** and click **Submit**. -3. In the pop-up window give a name for your bot and optionally fill in a brief description and click **Next**. Leave the **Location** field as is at this time. +3. In the pop-up window give a name for your bot and optionally fill in a brief description and click **Next**. Leave the **Location** field as is at this time. ![new_bot](./media/dialog/new_bot.png) -When your bot is created successfully you will see a **.Main** dialog in the dialog navigation pane. Congratulations! You have created your first bot and its main dialog using Composer. Below is a screenshot of a bot named `Greeting` and its main dialog named `Greeting.Main`: +When your bot is created successfully you will see a **.Main** dialog in the dialog navigation pane. Congratulations! You have created your first bot and its main dialog using Composer. Below is a screenshot of a bot named `Greeting` and its main dialog named `Greeting.Main`: ![main_dialog](./media/dialog/main_dialog.png) > [!NOTE] -> After you create a bot a **ConversationUpdate** trigger will be created by default as well. It is a trigger to handle activities such as sending a welcome message. For details please read [events and triggers](concept-events-and-triggers.md). +> After you create a bot a **ConversationUpdate** trigger will be created by default as well. It is a trigger to handle activities such as sending a welcome message. For details please read [events and triggers](concept-events-and-triggers.md). -## Add a dialog -After you create a bot you are also creating its main dialog by default. The main dialog is like the brain of our bot, controlling and managing the dialog system. Sometimes we find it useful to create a child dialog that contains a chunk of functionality so that our dialog system is organized and easily managed. Let's walk through a very simple example to show how to create a child dialog and wire it up to the main dialog. +## Add a dialog -1. Create a child dialog. Click **New Dialog** on the navigation pane. On the pop-up window give a name for the new dialog and optionally fill in the description and then click **Next**. +After you create a bot you are also creating its main dialog by default. The main dialog is like the brain of our bot, controlling and managing the dialog system. Sometimes we find it useful to create a child dialog that contains a chunk of functionality so that our dialog system is organized and easily managed. Let's walk through a very simple example to show how to create a child dialog and wire it up to the main dialog. + +1. Create a child dialog. Click **New Dialog** on the navigation pane. On the pop-up window give a name for the new dialog and optionally fill in the description and then click **Next**. ![weather_dialog](./media/dialog/weather_dialog.png) -After that, you will see an empty dialog you created on the navigation pane. When creating a child dialog you also create a **Dialog started (BeginDialog)** trigger by default. The new dialog named `Weather` may look like this: +After that, you will see an empty dialog you created on the navigation pane. When creating a child dialog you also create a **Dialog started (Begin dialog event)** trigger by default. The new dialog named `Weather` may look like this: ![new_weather_dialog](./media/dialog/new_weather_dialog.png) -2. Define an action in the **BeginDialog** trigger. Click the `+` sign under **Dialog started (BeginDialog)** in the new dialog and select **Send a response**. In the Language Generation editor put a sentence: "The weather dialog is calle with success!" +2. Define an action in the **BeginDialog** trigger. Click the `+` sign under **Dialog started (Begin dialog event)** in the new dialog and select **Send a response**. In the Language Generation editor put a sentence: "The weather dialog is calle with success!" ![send_response](./media/dialog/send_response.gif) -3. Wire up the new dialog. Click the main dialog in navigation pane and select **ConversationUpdate**. In the authoring canvas, click the **+** sign under **ConversationUpdate** and select **Dialog management** and then **Begin a new dialog** which is a dialog action that begins another dialog. When that dialog is completed, it will return to the caller. +3. Wire up the new dialog. Click the main dialog in navigation pane and select **ConversationUpdate**. In the authoring canvas, click the **+** sign under **ConversationUpdate** and select **Dialog management** and then **Begin a new dialog** which is a dialog action that begins another dialog. When that dialog is completed, it will return to the caller. ![begin_dialog_action](./media/dialog/begin_dialog_action.png) -Now in the properties panel on the right side select the dialog you want to wire up from the drop-down menu. Let's select `Weather` dialog and then you will see the name of the new dialog appear in the **Begin a new dialog** action node. +Now in the properties panel on the right side select the dialog you want to wire up from the drop-down menu. Let's select `Weather` dialog and then you will see the name of the new dialog appear in the **Begin a new dialog** action node. ![wire_up_dialog](./media/dialog/wire_up_dialog.gif) -When the bot runs, the pattern of this simple design is as follows: +When the bot runs, the pattern of this simple design is as follows: - The main dialog `Greeting.Main` is called at bot runtime. -- The **ConversationUpdate** trigger in the main dialog is activated and begins to execute the **Begin a new dialog** action which begins `Weather` dialog. -- When `Weather` dialog becomes active, the **BeginDialog** trigger in the child dialog is fired and send the response "The weather dialog is called with success!" to users. - -You can test the result by clicking **Start** on the upper right corner and then click **Test in Emulator**. You should be able to see the following result in the emulator: +- The **ConversationUpdate** trigger in the main dialog is activated and begins to execute the **Begin a new dialog** action which begins `Weather` dialog. +- When `Weather` dialog becomes active, the **BeginDialog** trigger in the child dialog is fired and send the response "The weather dialog is called with success!" to users. + +You can test the result by clicking **Start** on the upper right corner and then click **Test in Emulator**. You should be able to see the following result in the emulator: ![test_emulator](./media/dialog/test_emulator.png) -## Dialog actions -A bot will have a few dialogs or hundreds of individual dialogs and traditionally it's difficult to manage the dialog system and the conversation with user. In the previous "Add a dialog" section, we cover how to create a child dialog and wire it up to the dialog system using **Begin a new dialog** action. In fact, Composer provides more dialog actions to make it easier to manage the dialog system. You can access the different dialog actions by clicking the **+** node under a trigger and then select **Dialog management**. +## Dialog actions -Below is a list of the dialog actions provided in Composer: +A bot will have a few dialogs or hundreds of individual dialogs and traditionally it's difficult to manage the dialog system and the conversation with user. In the previous "Add a dialog" section, we cover how to create a child dialog and wire it up to the dialog system using **Begin a new dialog** action. In fact, Composer provides more dialog actions to make it easier to manage the dialog system. You can access the different dialog actions by clicking the **+** node under a trigger and then select **Dialog management**. + +Below is a list of the dialog actions provided in Composer: | Dialog Action | Description | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------- | @@ -107,12 +118,14 @@ Below is a list of the dialog actions provided in Composer: | Repeat this Dialog | An action that repeats the current dialog with the same dialog. | | Replace this Dialog | An action that replaces the current dialog with the target dialog. | -With these dialog actions, we can easily build a pluggable and extensible dialog system without worrying about the mechanics of dialog management. +With these dialog actions, we can easily build a pluggable and extensible dialog system without worrying about the mechanics of dialog management. + +## Further reading -## Further reading [Dialogs library](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) [Adaptive dialogs](https://github.com/Microsoft/BotBuilder-Samples/tree/master/experimental/adaptive-dialog#readme) -## Next +## Next + [Events and triggers](./concept-events-and-triggers.md) diff --git a/docs/concept-events-and-triggers.md b/docs/concept-events-and-triggers.md index cb68ebacf0..b0bf02578d 100644 --- a/docs/concept-events-and-triggers.md +++ b/docs/concept-events-and-triggers.md @@ -9,7 +9,7 @@ On the navigation pane, click **New Trigger** and you will see the trigger menu There are different types of triggers. They all work in a similar manner, and in some cases, can be interchanged. This section will cover the different types of triggers and when should we use them. Read more to learn how to [define triggers](howto-defining-triggers.md). ### Dialog trigger -The base type of triggers are dialog triggers. Almost all events start as dialog events which are related to the "lifecycle" of the dialog. Currently there are four different dialog triggers in Composer: **Dialog started (BeginDialog)**, **Dialog cancelled (CancelDialog)**, **Error occurred** and **Re-prompt for input**. Most dialogs will include a trigger configured to respond to the `BeginDialog` event, which fires when the dialog begins and allows the bot to respond immediately. +The base type of triggers are dialog triggers. Almost all events start as dialog events which are related to the "lifecycle" of the dialog. Currently there are four different dialog triggers in Composer: **Dialog started (Begin dialog event)**, **Dialog cancelled (CancelDialog)**, **Error occurred** and **Re-prompt for input**. Most dialogs will include a trigger configured to respond to the `BeginDialog` event, which fires when the dialog begins and allows the bot to respond immediately. Use dialog triggers when you want to: - Take actions immediately when the dialog starts, even before the recognizer is called From ae7fa1dd4f2ba13899cf388a283fefc985504f4f Mon Sep 17 00:00:00 2001 From: Zhixiang Zhan Date: Sat, 30 Nov 2019 01:18:55 +0800 Subject: [PATCH 2/2] feat: LG LSP in Composer (#1504) * Add LSP of LG * change folder name * change folder location * seperate server and client demo * add completion and hover for builtin-functions * change file names * change dependency * refactor the package * update demo readme * make some refine * remove npm lock file * add demos to workspace and run test in 1 command * remove declaration files * change tsc outDir to dist, simplify test command * add syntax highlight in demo and new API * change naming of the project and move the demo * remove package.json in demo * change the content in readme * fix lg-lsp package publish problem * fix build command and redundent d.ts files * integrate LG LSP server to composer server * change api in demo * change the order of commands in build:prod * delete redundent files generated from build * change lg-lsp-server api to attachLSPServer * remove gitignore in lg-lsp-server demo * remove attachLSPServer in server * stash * freeze vscode-languageserver-protocol@3.15.0-next.8 * stash code-editor lsp demo * update * build react sample demo * version 17 * launch lsp server * connect server * calc offset * concat full content * clean demo * update * do not build lglsp editor * update demo * update before build * clean build * inline mode * build * lsp working demo in composer * renaming * eslint and build fix * add utils * monaco editor core component * wrap up editor component * seperate client and server * update wrap * attach language server to composer server * token rules and suggestion context awareness * complete demo * inline template editor in form * inline in all-up view * refactor * check file * update * remove monaco-webpack-plugin * import diectly instead of call register * create language server url * add package * allow suggestion in plaintext state * update integrity hash * exist check * add back monaco-webpack-plugin * add mixed demo * code refactor * merge folder * add dependency * resolve alias in jest * fix type * resolve typing * clean up * clean up * Update Composer/package.json Co-Authored-By: Andy Brown * Update Composer/packages/tools/language-servers/language-generation/src/utils.ts Co-Authored-By: Andy Brown * Update Composer/packages/tools/language-servers/package.json Co-Authored-By: Andy Brown * refactor lsp server * refactor lsp client * resolve eslint * move static syntax setting to code editor * clean up * clean * use latest @types/vscode replace vscode * only path map actual vscode module not including the `$` will make jest replace all modules with *vscode* * disable lint errors * resolve code review warning * clean up * pass less data * fix type * clean up, typo, refine and naming * clean up * extends base * hide source name * add test * eslint resolve * close connection when document close * update * add time stamp for every new connection * pass parameters to inline editor * match lsp server port with env server port * rename /lgServer to /lg-language-server * refactor code * compatible LG Parser, prevent crash * do not close shared connection * send initial diagnostics with delay * update demo * refactor error handling * refine, review * error handling * ignore non-exist references at check template body * update tokenizer * close websocket when editor unmount * all editors use a shared unique ws connection * fix structure lg in inline mode and comment token * fix lint * update structure name token * fix fence_block miss recognize template name * remove plaintext in allowState for LG completion * control initial diagnostics send * fix regExp in getWordAtPosition * optimize data passing * update test * eslint fix * update test --- .vscode/launch.json | 41 +- Composer/jest.config.js | 1 + Composer/package.json | 10 +- .../packages/client/config/webpack.config.js | 6 +- Composer/packages/client/jest.config.js | 2 + .../pages/language-generation/code-editor.tsx | 106 ++- .../src/pages/language-generation/index.tsx | 43 +- Composer/packages/client/src/utils/lgUtil.ts | 12 +- .../obiformeditor/demo/webpack.config.demo.js | 3 + .../extensions/obiformeditor/jest.config.js | 1 + .../extensions/obiformeditor/package.json | 1 + .../src/Form/widgets/LgEditorWidget.tsx | 124 +-- .../lib/code-editor/demo/.eslintrc.js | 7 + .../packages/lib/code-editor/demo/index.html | 36 +- .../lib/code-editor/demo/src/index.tsx | 11 +- .../lib/code-editor/demo/src/inlineEditor.tsx | 49 ++ .../lib/code-editor/demo/src/jsonEditor.tsx | 43 ++ .../lib/code-editor/demo/src/lgEditor.tsx | 89 +++ .../lib/code-editor/demo/src/lgJsonEditor.tsx | 124 +++ .../lib/code-editor/demo/src/luEditor.tsx | 229 ++++++ .../lib/code-editor/demo/src/multiEditors.tsx | 53 ++ .../demo/src/{App.tsx => richEditor.tsx} | 9 +- .../code-editor/demo/webpack.config.demo.js | 16 +- .../packages/lib/code-editor/package.json | 17 +- .../lib/code-editor/src/BaseEditor.tsx | 56 +- .../lib/code-editor/src/JsonEditor.tsx | 1 - .../packages/lib/code-editor/src/LgEditor.tsx | 98 ++- .../lib/code-editor/src/RichEditor.tsx | 7 +- .../lib/code-editor/src/languages/index.ts | 4 + .../lib/code-editor/src/languages/lg.ts | 110 +++ .../lib/code-editor/src/utils/common.ts | 27 + .../lib/code-editor/src/utils/index.ts | 1 + .../lib/code-editor/src/utils/lspUtil.ts | 61 ++ .../lib/code-editor/tsconfig.build.json | 6 +- .../packages/lib/code-editor/tsconfig.json | 8 +- Composer/packages/server/package.json | 6 +- Composer/packages/server/src/server.ts | 31 +- .../packages/server/src/utility/attachLSP.ts | 31 + .../language-generation/.eslintrc.js | 7 + .../language-generation/.gitignore | 3 + .../language-generation/README.md | 21 + .../language-generation/__tests__/app.test.ts | 149 ++++ .../__tests__/helpers/server.ts | 77 ++ .../__tests__/mocks/greeting.lg | 8 + .../__tests__/mocks/initialize-params.json | 238 ++++++ .../language-generation/demo/.eslintrc.js | 7 + .../language-generation/demo/src/attach.ts | 31 + .../language-generation/demo/src/server.ts | 44 ++ .../language-generation/demo/tsconfig.json | 10 + .../language-generation/jest.config.js | 15 + .../language-generation/package.json | 32 + .../language-generation/src/LGServer.ts | 362 +++++++++ .../src/builtinFunctionsMap.ts | 720 ++++++++++++++++++ .../language-generation/src/index.ts | 4 + .../language-generation/src/utils.ts | 126 +++ .../language-generation/tsconfig.build.json | 7 + .../language-generation/tsconfig.json | 10 + .../tools/language-servers/package.json | 12 + Composer/packages/tools/package.json | 12 + Composer/yarn.lock | 495 ++++++++---- 60 files changed, 3474 insertions(+), 396 deletions(-) create mode 100644 Composer/packages/lib/code-editor/demo/.eslintrc.js create mode 100644 Composer/packages/lib/code-editor/demo/src/inlineEditor.tsx create mode 100644 Composer/packages/lib/code-editor/demo/src/jsonEditor.tsx create mode 100644 Composer/packages/lib/code-editor/demo/src/lgEditor.tsx create mode 100644 Composer/packages/lib/code-editor/demo/src/lgJsonEditor.tsx create mode 100644 Composer/packages/lib/code-editor/demo/src/luEditor.tsx create mode 100644 Composer/packages/lib/code-editor/demo/src/multiEditors.tsx rename Composer/packages/lib/code-editor/demo/src/{App.tsx => richEditor.tsx} (91%) create mode 100644 Composer/packages/lib/code-editor/src/languages/index.ts create mode 100644 Composer/packages/lib/code-editor/src/languages/lg.ts create mode 100644 Composer/packages/lib/code-editor/src/utils/common.ts create mode 100644 Composer/packages/lib/code-editor/src/utils/lspUtil.ts create mode 100644 Composer/packages/server/src/utility/attachLSP.ts create mode 100644 Composer/packages/tools/language-servers/language-generation/.eslintrc.js create mode 100644 Composer/packages/tools/language-servers/language-generation/.gitignore create mode 100644 Composer/packages/tools/language-servers/language-generation/README.md create mode 100644 Composer/packages/tools/language-servers/language-generation/__tests__/app.test.ts create mode 100644 Composer/packages/tools/language-servers/language-generation/__tests__/helpers/server.ts create mode 100644 Composer/packages/tools/language-servers/language-generation/__tests__/mocks/greeting.lg create mode 100644 Composer/packages/tools/language-servers/language-generation/__tests__/mocks/initialize-params.json create mode 100644 Composer/packages/tools/language-servers/language-generation/demo/.eslintrc.js create mode 100644 Composer/packages/tools/language-servers/language-generation/demo/src/attach.ts create mode 100644 Composer/packages/tools/language-servers/language-generation/demo/src/server.ts create mode 100644 Composer/packages/tools/language-servers/language-generation/demo/tsconfig.json create mode 100644 Composer/packages/tools/language-servers/language-generation/jest.config.js create mode 100644 Composer/packages/tools/language-servers/language-generation/package.json create mode 100644 Composer/packages/tools/language-servers/language-generation/src/LGServer.ts create mode 100644 Composer/packages/tools/language-servers/language-generation/src/builtinFunctionsMap.ts create mode 100644 Composer/packages/tools/language-servers/language-generation/src/index.ts create mode 100644 Composer/packages/tools/language-servers/language-generation/src/utils.ts create mode 100644 Composer/packages/tools/language-servers/language-generation/tsconfig.build.json create mode 100644 Composer/packages/tools/language-servers/language-generation/tsconfig.json create mode 100644 Composer/packages/tools/language-servers/package.json create mode 100644 Composer/packages/tools/package.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 2506014c71..669e566005 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,14 +1,41 @@ { "version": "0.2.0", "configurations": [ + { + "type": "chrome", + "request": "attach", + "name": "Attach to Chrome", + "port": 9222, + "webRoot": "${workspaceFolder}" + }, + { + "name": "LSP Server", + "type": "node", + "request": "launch", + "args": [ + "${workspaceFolder}/Composer/packages/tools/language-servers/language-generation/demo/src/server.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "${workspaceFolder}/Composer/node_modules/ts-node/register" + ], + "sourceMaps": true, + "cwd": "${workspaceFolder}/Composer/packages/tools/language-servers/language-generation/demo/src", + "protocol": "inspector", + }, { "type": "node", "request": "launch", "name": "Server: Launch", - "args": ["./build/server.js"], + "args": [ + "./build/server.js" + ], "preLaunchTask": "server: build", "restart": true, - "outFiles": ["./build/*"], + "outFiles": [ + "./build/*" + ], "envFile": "${workspaceFolder}/Composer/packages/server/.env", "outputCapture": "std", "cwd": "${workspaceFolder}/Composer/packages/server" @@ -20,8 +47,14 @@ "name": "Jest Debug", "program": "${workspaceRoot}/Composer/node_modules/jest/bin/jest", "stopOnEntry": false, - "args": ["--runInBand", "--env=jsdom", "--config=jest.config.js"], - "runtimeArgs": ["--inspect-brk"], + "args": [ + "--runInBand", + "--env=jsdom", + "--config=jest.config.js" + ], + "runtimeArgs": [ + "--inspect-brk" + ], "cwd": "${workspaceRoot}/Composer/packages/server", "sourceMaps": true, "console": "integratedTerminal" diff --git a/Composer/jest.config.js b/Composer/jest.config.js index 32b10b6b50..da2165fe69 100644 --- a/Composer/jest.config.js +++ b/Composer/jest.config.js @@ -41,5 +41,6 @@ module.exports = { '/packages/extensions/visual-designer', '/packages/lib/code-editor', '/packages/lib/shared', + '/packages/tools/language-servers/language-generation', ], }; diff --git a/Composer/package.json b/Composer/package.json index 21b3b3df5e..52a879a14e 100644 --- a/Composer/package.json +++ b/Composer/package.json @@ -13,16 +13,20 @@ "packages/extensions", "packages/extensions/*", "packages/lib", - "packages/lib/*" + "packages/lib/*", + "packages/tools", + "packages/tools/language-servers", + "packages/tools/language-servers/*" ], "scripts": { "build": "node scripts/begin.js && yarn build:prod", - "build:prod": "yarn build:lib && yarn build:extensions && yarn build:server && yarn build:client", - "build:dev": "yarn build:lib && yarn build:extensions", + "build:prod": "yarn build:dev && yarn build:server && yarn build:client", + "build:dev": "yarn build:tools && yarn build:lib && yarn build:extensions", "build:lib": "yarn workspace @bfc/libs build:all", "build:extensions": "yarn workspace @bfc/extensions build:all", "build:server": "yarn workspace @bfc/server build", "build:client": "yarn workspace @bfc/client build", + "build:tools": "yarn workspace @bfc/tools build:all", "start": "cross-env NODE_ENV=production PORT=3000 yarn start:server", "startall": "concurrently --kill-others-on-fail \"npm:runtime\" \"npm:start\"", "start:dev": "concurrently \"npm:start:client\" \"npm:start:server:dev\"", diff --git a/Composer/packages/client/config/webpack.config.js b/Composer/packages/client/config/webpack.config.js index 0a98e430f2..f56da750fe 100644 --- a/Composer/packages/client/config/webpack.config.js +++ b/Composer/packages/client/config/webpack.config.js @@ -228,6 +228,10 @@ module.exports = function(webpackEnv) { // `web` extension prefixes have been added for better support // for React Native Web. extensions: paths.moduleFileExtensions.map(ext => `.${ext}`), + alias: { + // Support lsp code editor + vscode: require.resolve('monaco-languageclient/lib/vscode-compatibility'), + }, plugins: [ // Adds support for installing with Plug'n'Play, leading to faster installs and adding // guards against forgotten dependencies and such. @@ -402,7 +406,7 @@ module.exports = function(webpackEnv) { plugins: [ new MonacoWebpackPlugin({ // available options are documented at https://github.com/Microsoft/monaco-editor-webpack-plugin#options - languages: ['markdown', 'botbuilderlg', 'json'], + languages: ['markdown', 'json'], }), // Generates an `index.html` file with the