From f168890c16d8451890248ad2512dddf33a06576b Mon Sep 17 00:00:00 2001 From: Soroush Date: Mon, 9 Nov 2020 12:54:58 -0800 Subject: [PATCH] chore: sync form-dialogs feature branch (#4734) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: form dialog view dialog action * Fix: Prevent dismissing a property card due to click during text selection within the card * feat: update sdk package to 4.11.0-rc2 (#4625) * Update sdk package to 4.11.0-rc2 * Fix tests for ReplaceDialog * Delete the default appsettings that ships with qna (#4650) Signed-off-by: Srinaath Ravichandran Co-authored-by: Srinaath Ravichandran * fix: dialog resources recognizer/qna incorrect creation (#4612) * fix customize imported dialog resource auto creation * add test * avoid touch form generated dialog * update detecting * update test * update detecting at server Co-authored-by: Chris Whitten * larger warning icon (#4665) * fix: Test/Start buttons unable to click. (#4666) * filter hidden card, do not render empty container * update Co-authored-by: Dong Lei * fix: azure publish fail (#4660) Co-authored-by: Dong Lei * always show "create from scratch" button in "from url modal" (#4662) Co-authored-by: Dong Lei * fix: update 'Disable/Enable' buttons state in realtime (#4664) * add dialog content as a dep of useMemo #4629 * remove log Co-authored-by: Dong Lei * does not allow to delete root bot (#4671) * fix: Visual canvas mess up when add new actions in a zoom in/out mode (#4672) * fix: mess when zoom in/out * use scroll Co-authored-by: Dong Lei * use throttle 200 instead of debounce 1000 (#4670) Co-authored-by: Dong Lei Co-authored-by: Chris Whitten * promisify the 'saveData()' api and fix #4616 (#4667) Co-authored-by: Chris Whitten * fix: Form dialog hidden fixed feature flags (#4675) * Form dialog hidden fixed feature flags * remove temp lint directive * Removed debugging vars * don't install package during eject js runtime, install and build when publish (#4668) Co-authored-by: Dong Lei Co-authored-by: Lu Han <32191031+luhan2017@users.noreply.github.com> * fix: misordered using directives in runtime (#4684) * Update Startup.cs * Update runtime/dotnet/azurewebapp/Startup.cs * add region parameter to luis build call options (#4685) * change Microsoft.UpdateActivity title from "Send an activity" to "Update an activity" (#4686) Co-authored-by: Dong Lei * read recognizer intent from dialog content (#4683) Co-authored-by: Dong Lei * fix: router cache bug (#4687) * clean all router cache when switching project * reset routerCache * should check rootProjectId Co-authored-by: Dong Lei * Fix variable name (#4661) Co-authored-by: Dong Lei * feature: Supporting core+package templates stored on NuGet (#4588) * - added hosted template to template list - updated template type to account for hosted templates * initial scaffolding of hosted template grab * external nuget template working e2e as template in creation flow * Removing redundent types * adding support for relative runtime paths. Removing unused dependencies. * adding comments and removing autogenerated yan lock file from root dir * Adding version to template acquasition Generating build files Fixing broken unit test * updating template version to latest stable * hiding remote template feature flag until NuGet packages are public * Making PR changes * simplifying featureflag logic changing feature flag default values * removing bad null check * update naming * Fixing broken test updating locale files with latest added strings * Removing 'del' dependency in favor of rimraf for directory deletions * Reverting feature flag bug fix in favor for bug fix implemented in seperate PR Co-authored-by: Patrick Volum Co-authored-by: Chris Whitten * fix: breadcrumb display for ProjectTree (#4604) * remove old breadcrumbs and start making new ones * Update DesignPage.tsx * Update DesignPage.tsx * update unit tests to remove breadcrumb things * fix duplicate key bug in breadcrumbs * fix e2e test * detect and display action names in breadcrumb * rewrite to make typechecker happy * make new DesignPage unit tests * Update publisher.ts * Update publisher.ts * restore navigation in undo * retrieve breadcrumb from URL on location change * read double-nested $designer fields * navigate to trigger[0] on OpenDialog node events * fix typo and unit tests * Update validateDialogName.test.ts * better error-checking for invalid URLs * make special "beginDialog" trigger * Update en-US.json * Update DesignPage.tsx * initialize onboarding status to complete in non-production * restore WelcomeModalCloseIcon to ToDoBot test Co-authored-by: Chris Whitten * fix: Sorting OneOfField options / better default type (#4673) * Sorting OneOfField options based on type weights * Fix test * fix test Co-authored-by: Geoff Cox (Microsoft) * Updated locale from building (#4694) * when changes on common.lg, reparse all lgFiles (#4669) Co-authored-by: Dong Lei Co-authored-by: Chris Whitten * Update projectTemplates.ts (#4697) * fix: undo/redo (#4688) * Bot proj breadcrumbs (#4) * remove old breadcrumbs and start making new ones * Update DesignPage.tsx * Update DesignPage.tsx * update unit tests to remove breadcrumb things * fix duplicate key bug in breadcrumbs * fix e2e test * detect and display action names in breadcrumb * rewrite to make typechecker happy * make new DesignPage unit tests * Update publisher.ts * Update publisher.ts * restore navigation in undo * retrieve breadcrumb from URL on location change * read double-nested $designer fields * navigate to trigger[0] on OpenDialog node events * fix typo and unit tests * Update validateDialogName.test.ts * better error-checking for invalid URLs * make special "beginDialog" trigger * Update en-US.json * Update DesignPage.tsx Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com> Co-authored-by: Chris Whitten * fix: undo/redo * add location check and add fix unit tests * Revert "Bot proj breadcrumbs (#4)" This reverts commit d558846afb7251a0288a3a47c022ec6620fc52c1. * add selector for undo * move status * fix unit test Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com> Co-authored-by: Chris Whitten Co-authored-by: Dong Lei * fix: Notification deep link (#4700) * notification with multi-bot, fix url * clean up * redirect to dialog root (#4698) Co-authored-by: Dong Lei * fix: Update disabled property to booleanExpression across all the kinds + two uischema update (#4682) * Update disabled property to booleanExpression across all the kinds * Update server sdk schema and include other two uischema update * Revert "Update server sdk schema and include other two uischema update" This reverts commit cfffa74371ec38ac6437850ee7a7aa7318054b8f. * update server sdk schema and include two other uischema update Co-authored-by: Dong Lei * fix: the generated recognizer files is messy when the dialog name has the same prefix (#4708) * move luBuildResolver to shared lib, apply in azurePublish (#4709) Co-authored-by: Dong Lei * replace deprecated azure blob interface (#4702) Co-authored-by: Dong Lei * add useMemo to improve performance (#4701) Co-authored-by: Dong Lei * make name text field disable in edit (#4699) Co-authored-by: Dong Lei * ci: Disable CI validation (#4646) Co-authored-by: Andy Brown * fix: remodel ProjectTree for greater clarity (#4714) * start tree CSS experiments * Update projectTemplates.ts * make ProjectTree save tree state * start possible fix for textwidth * Update ProjectTree.tsx * Update ProjectTree.tsx * no more squishy triangles * make left-right split view thing work * Update treeItem.tsx * Always enable allowClarmsValidator (#4717) Co-authored-by: Dong Lei * fix: undo/redo can't navigate to the correct position (#4722) * fix: undo/redo can't navigate to the correct position * update the type * fix: add skillConfiguration settings for old bot (#4724) * fix: add skillConfiguration settings for old bot * add update file for get Co-authored-by: Dong Lei * fix onResize handler props (#4720) Co-authored-by: Dong Lei * fix: azure publish failed (#4725) * fix: update incompatible Trigger redirect url & icon size (#4719) * fix onResize handler props * increase warning icon font size to 12 * fix incompatible trigger redirect url * Revert "fix onResize handler props" This reverts commit c9ba885ae8ea510f70916f3400e2a0db77037626. Co-authored-by: Dong Lei * feat: Integrate OneAuth library (#4677) * Integrated OneAuth library for Windows * Auth updates * chore: remove orchestrator (#4602) * Added CSRF protection to getAccessToken route. * Build changes to support OneAuth * Shared types and did some renaming. * Consolidated types * Fixed naming issue on babel localization config * Separated out auth service code. * Removed TODO * Updated shim to be at the oneauth service level * Added interactive retry when failing to get token silently * Added documentation for using OneAuth locally * sign mac assets with correct entitlements add mac signing script add signing and verifying steps fix path to bundles copy provision profile into each app bundle copy the provision profile in contents directory sign each dylib and framework version fix logging string sign frameworks before bundles * Made temporary mac auth flow dev-env-only * Fixed bad import * Removed manual oneauth installation steps from AUTH.md * Linting fixes * Setup tests * Added more tests * Added test for token caching. * Added cross-platform testing for oneauth service Co-authored-by: Andy Brown Co-authored-by: Chris Whitten Co-authored-by: Soroush Co-authored-by: Lu Han <32191031+luhan2017@users.noreply.github.com> Co-authored-by: Srinaath Ravichandran Co-authored-by: Srinaath Ravichandran Co-authored-by: Zhixiang Zhan Co-authored-by: Chris Whitten Co-authored-by: zeye Co-authored-by: Dong Lei Co-authored-by: leileizhang Co-authored-by: liweitian Co-authored-by: Long Alan Co-authored-by: Geoff Cox (Microsoft) Co-authored-by: VanyLaw Co-authored-by: zxyanliu <32497439+zxyanliu@users.noreply.github.com> Co-authored-by: Fei Chen <43032123+feich-ms@users.noreply.github.com> Co-authored-by: liweitian Co-authored-by: Péter Bozsó <3806723+peterbozso@users.noreply.github.com> Co-authored-by: pavolum Co-authored-by: Patrick Volum Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com> Co-authored-by: LouisEugeneMSFT <66701106+LouisEugeneMSFT@users.noreply.github.com> Co-authored-by: Qi Kang Co-authored-by: Tony Anziano Co-authored-by: Andy Brown --- .vscode/launch.json | 3 +- Composer/babel.l10n.config.js | 13 + .../cypress/integration/Breadcrumb.spec.ts | 4 +- Composer/cypress/integration/ToDoBot.spec.ts | 2 + Composer/package.json | 2 +- .../stubs/ShellApiStub.ts | 2 +- .../hooks/useEditorEventApi.ts | 47 +- .../components/ElementMeasurer.tsx | 6 +- .../fields/OneOfField/__tests__/utils.test.ts | 10 +- .../src/components/fields/OneOfField/utils.ts | 16 +- .../components/fields/RegexIntentField.tsx | 13 +- .../components/CreationFlow/index.test.tsx | 10 +- .../components/createQnAModal.test.tsx | 3 +- .../client/__tests__/components/home.test.tsx | 4 +- .../__tests__/pages/design/Design.test.tsx | 65 + .../client/__tests__/utils/navigation.test.ts | 50 +- Composer/packages/client/public/index.html | 6 + .../components/CreationFlow/CreateOptions.tsx | 6 +- .../CreationFlow/DefineConversation.tsx | 23 +- .../Notifications/NotificationContainer.tsx | 5 +- .../components/ProjectTree/ExpandableNode.tsx | 21 +- .../components/ProjectTree/ProjectTree.tsx | 194 +- .../ProjectTree/TriggerCreationModal.tsx | 2 +- .../src/components/ProjectTree/constants.ts | 5 + .../src/components/ProjectTree/treeItem.tsx | 58 +- .../components/QnA/CreateQnAFromUrlModal.tsx | 27 +- .../src/components/Split/LeftRightSplit.tsx | 7 + .../client/src/pages/design/DesignPage.tsx | 259 +- .../src/pages/design/PropertyEditor.tsx | 3 - .../src/pages/form-dialog/FormDialogPage.tsx | 6 +- .../client/src/pages/home/ExampleList.tsx | 6 +- .../src/pages/knowledge-base/QnAPage.tsx | 2 +- .../src/pages/knowledge-base/table-view.tsx | 2 +- .../pages/notifications/NotificationList.tsx | 26 + .../src/pages/notifications/Notifications.tsx | 19 +- .../client/src/pages/notifications/types.ts | 31 +- .../pages/notifications/useNotifications.tsx | 113 +- .../src/pages/publish/createPublishTarget.tsx | 2 +- Composer/packages/client/src/plugins/api.ts | 16 +- .../client/src/recoilModel/Recognizers.tsx | 11 +- .../client/src/recoilModel/atoms/appState.ts | 14 +- .../client/src/recoilModel/atoms/botState.ts | 25 +- .../dispatchers/__tests__/navigation.test.tsx | 59 +- .../recoilModel/dispatchers/application.ts | 9 + .../recoilModel/dispatchers/formDialogs.ts | 5 +- .../client/src/recoilModel/dispatchers/lg.ts | 42 +- .../src/recoilModel/dispatchers/navigation.ts | 71 +- .../src/recoilModel/dispatchers/publisher.ts | 5 +- .../client/src/recoilModel/dispatchers/qna.ts | 8 - .../client/src/recoilModel/selectors/index.ts | 1 + .../src/recoilModel/selectors/messagers.ts | 123 + .../recoilModel/selectors/projectTemplates.ts | 10 +- .../client/src/recoilModel/selectors/undo.ts | 14 + .../packages/client/src/recoilModel/types.ts | 9 +- .../undo/__test__/history.test.tsx | 39 +- .../client/src/recoilModel/undo/history.ts | 70 +- .../packages/client/src/shell/useShell.ts | 26 +- .../packages/client/src/types/window.d.ts | 5 + .../packages/client/src/utils/authClient.ts | 36 + .../utils/convertUtils/designerPathEncoder.ts | 7 +- Composer/packages/client/src/utils/hooks.ts | 15 +- .../packages/client/src/utils/navigation.ts | 36 +- .../packages/client/src/utils/oauthClient.ts | 93 - .../client/src/utils/onboardingStorage.ts | 2 +- .../packages/client/src/utils/routerCache.ts | 5 + .../packages/electron-server/.eslintrc.js | 8 + Composer/packages/electron-server/.gitignore | 1 + Composer/packages/electron-server/AUTH.md | 31 + .../__tests__/auth/oneAuthService.test.ts | 150 + .../electron-server/__tests__/setupTests.js | 7 + .../electron-server/babel.l10n.config.js | 8 + .../packages/electron-server/jest.config.js | 5 +- .../packages/electron-server/package.json | 19 +- .../resources/entitlements-keychain.plist | 22 + .../electron-server/scripts/common.js | 15 + .../electron-server/scripts/copy-artifacts.js | 92 + .../scripts/copy-extensions.js | 48 - .../scripts/copy-form-dialog-templates.js | 46 - .../electron-server/scripts/copy-runtime.js | 41 - .../scripts/electronBuilderDist.js | 13 +- .../scripts/electronBuilderPack.js | 15 +- .../electron-server/scripts/installOneAuth.js | 115 + .../electron-server/scripts/sign-mac.js | 158 + .../electron-server/src/auth/oneAuthBase.ts | 12 + .../src/auth/oneAuthService.ts | 227 ++ .../electron-server/src/auth/oneAuthShim.ts | 24 + .../electron-server/src/auth/oneauth.d.ts | 576 +++ Composer/packages/electron-server/src/main.ts | 26 +- .../src/utility/oauthImplicitFlowHelper.ts | 151 - .../electron-server/tsconfig.build.json | 3 +- .../packages/electron-server/tsconfig.json | 7 +- .../extension-client/src/auth/index.ts | 11 +- .../extension-client/src/auth/types.ts | 9 - .../lib/indexers/src/dialogIndexer.ts | 2 + .../dialogUtils/validateDialogName.test.ts | 2 +- .../shared/__tests__/luBuildResolver.test.ts} | 5 +- Composer/packages/lib/shared/package.json | 1 + .../src/dialogUtils/validateDialogName.ts | 2 +- .../lib/shared/src/featureFlagUtils/index.ts | 14 +- .../shared/src/luBuildResolver.ts} | 4 +- Composer/packages/lib/shared/src/path.ts | 35 + Composer/packages/server/package.json | 4 +- .../packages/server/schemas/sdk.cs.schema | 2 +- .../packages/server/schemas/sdk.de.schema | 2 +- .../packages/server/schemas/sdk.en-US.schema | 3560 +++++++++-------- .../server/schemas/sdk.en-US.uischema | 96 +- .../packages/server/schemas/sdk.es.schema | 2 +- .../packages/server/schemas/sdk.fr.schema | 2 +- .../packages/server/schemas/sdk.hu.schema | 2 +- .../packages/server/schemas/sdk.it.schema | 2 +- .../packages/server/schemas/sdk.ja.schema | 2 +- .../packages/server/schemas/sdk.ko.schema | 2 +- .../packages/server/schemas/sdk.nl.schema | 2 +- .../packages/server/schemas/sdk.pl.schema | 2 +- .../packages/server/schemas/sdk.pt-BR.schema | 2 +- .../packages/server/schemas/sdk.pt-PT.schema | 2 +- .../packages/server/schemas/sdk.ru.schema | 2 +- Composer/packages/server/schemas/sdk.schema | 2761 +++++++------ .../packages/server/schemas/sdk.sv.schema | 2 +- .../packages/server/schemas/sdk.tr.schema | 2 +- Composer/packages/server/schemas/sdk.uischema | 227 +- .../server/schemas/sdk.zh-Hans.schema | 2 +- .../server/schemas/sdk.zh-Hant.schema | 2 +- .../src/controllers/__tests__/auth.test.ts | 82 + .../src/controllers/__tests__/eject.test.ts | 1 + .../packages/server/src/controllers/auth.ts | 41 + .../server/src/controllers/formDialog.ts | 16 +- .../server/src/controllers/project.ts | 30 +- .../packages/server/src/locales/en-US.json | 21 +- .../__tests__/csrfProtection.test.ts | 59 + .../server/src/middleware/csrfProtection.ts | 27 + .../server/src/models/asset/assetManager.ts | 110 +- .../models/bot/__tests__/botProject.test.ts | 2 +- .../models/bot/__tests__/botStructure.test.ts | 40 +- .../server/src/models/bot/botProject.ts | 39 +- .../server/src/models/bot/botStructure.ts | 45 +- .../packages/server/src/models/bot/builder.ts | 4 +- .../models/settings/defaultSettingManager.ts | 29 +- Composer/packages/server/src/router/api.ts | 5 + Composer/packages/server/src/server.ts | 12 +- .../src/services/__tests__/auth.test.ts | 44 + .../__tests__/electronAuthProvider.test.ts | 64 + .../packages/server/src/services/auth/auth.ts | 48 + .../server/src/services/auth/authProvider.ts | 12 + .../src/services/auth/electronAuthProvider.ts | 114 + .../src/services/auth/webAuthProvider.ts | 19 + .../server/src/services/featureFlags.ts | 107 +- .../server/src/utility/electronContext.ts | 18 + .../packages/server/src/utility/isElectron.ts | 4 + .../packages/server/src/utility/platform.ts | 14 + Composer/packages/types/src/auth.ts | 32 + Composer/packages/types/src/index.ts | 1 + Composer/packages/types/src/indexers.ts | 1 + Composer/packages/types/src/runtime.ts | 10 +- Composer/packages/types/src/server.ts | 12 - Composer/packages/types/src/shell.ts | 6 +- .../packages/ui-plugins/lg/src/LgField.tsx | 1 + Composer/yarn.lock | 165 +- azure-pipelines-static-analysis.yml | 3 + extensions/azurePublish/package.json | 2 +- extensions/azurePublish/src/index.ts | 2 +- extensions/azurePublish/src/luisAndQnA.ts | 12 +- extensions/azurePublish/yarn.lock | 46 +- extensions/localPublish/src/index.ts | 2 + extensions/runtimes/src/index.ts | 22 +- .../QnASample/settings/appsettings.json | 57 - extensions/samples/package.json | 1 + extensions/samples/src/index.ts | 31 +- extensions/samples/yarn.lock | 73 + ...oft.BotFramework.Composer.Functions.csproj | 24 +- runtime/dotnet/azurefunctions/Startup.cs | 3 +- ...rosoft.BotFramework.Composer.WebApp.csproj | 24 +- runtime/dotnet/azurewebapp/Schemas/sdk.schema | 900 +++-- .../dotnet/azurewebapp/Schemas/sdk.uischema | 248 +- runtime/dotnet/azurewebapp/Startup.cs | 14 +- ...icrosoft.BotFramework.Composer.Core.csproj | 25 +- ....BotFramework.Composer.CustomAction.csproj | 2 +- runtime/dotnet/tests/ActionsTests.cs | 3 + 178 files changed, 8268 insertions(+), 4877 deletions(-) create mode 100644 Composer/babel.l10n.config.js create mode 100644 Composer/packages/client/__tests__/pages/design/Design.test.tsx create mode 100644 Composer/packages/client/src/components/ProjectTree/constants.ts create mode 100644 Composer/packages/client/src/recoilModel/selectors/messagers.ts create mode 100644 Composer/packages/client/src/recoilModel/selectors/undo.ts create mode 100644 Composer/packages/client/src/utils/authClient.ts delete mode 100644 Composer/packages/client/src/utils/oauthClient.ts create mode 100644 Composer/packages/electron-server/AUTH.md create mode 100644 Composer/packages/electron-server/__tests__/auth/oneAuthService.test.ts create mode 100644 Composer/packages/electron-server/__tests__/setupTests.js create mode 100644 Composer/packages/electron-server/babel.l10n.config.js create mode 100644 Composer/packages/electron-server/resources/entitlements-keychain.plist create mode 100644 Composer/packages/electron-server/scripts/copy-artifacts.js delete mode 100644 Composer/packages/electron-server/scripts/copy-extensions.js delete mode 100644 Composer/packages/electron-server/scripts/copy-form-dialog-templates.js delete mode 100644 Composer/packages/electron-server/scripts/copy-runtime.js create mode 100644 Composer/packages/electron-server/scripts/installOneAuth.js create mode 100644 Composer/packages/electron-server/scripts/sign-mac.js create mode 100644 Composer/packages/electron-server/src/auth/oneAuthBase.ts create mode 100644 Composer/packages/electron-server/src/auth/oneAuthService.ts create mode 100644 Composer/packages/electron-server/src/auth/oneAuthShim.ts create mode 100644 Composer/packages/electron-server/src/auth/oneauth.d.ts delete mode 100644 Composer/packages/electron-server/src/utility/oauthImplicitFlowHelper.ts delete mode 100644 Composer/packages/extension-client/src/auth/types.ts rename Composer/packages/{server/src/models/bot/__tests__/luResolver.test.ts => lib/shared/__tests__/luBuildResolver.test.ts} (99%) rename Composer/packages/{server/src/models/bot/luResolver.ts => lib/shared/src/luBuildResolver.ts} (98%) create mode 100644 Composer/packages/lib/shared/src/path.ts create mode 100644 Composer/packages/server/src/controllers/__tests__/auth.test.ts create mode 100644 Composer/packages/server/src/controllers/auth.ts create mode 100644 Composer/packages/server/src/middleware/__tests__/csrfProtection.test.ts create mode 100644 Composer/packages/server/src/middleware/csrfProtection.ts create mode 100644 Composer/packages/server/src/services/__tests__/auth.test.ts create mode 100644 Composer/packages/server/src/services/__tests__/electronAuthProvider.test.ts create mode 100644 Composer/packages/server/src/services/auth/auth.ts create mode 100644 Composer/packages/server/src/services/auth/authProvider.ts create mode 100644 Composer/packages/server/src/services/auth/electronAuthProvider.ts create mode 100644 Composer/packages/server/src/services/auth/webAuthProvider.ts create mode 100644 Composer/packages/server/src/utility/electronContext.ts create mode 100644 Composer/packages/server/src/utility/isElectron.ts create mode 100644 Composer/packages/server/src/utility/platform.ts create mode 100644 Composer/packages/types/src/auth.ts delete mode 100644 extensions/samples/assets/projects/QnASample/settings/appsettings.json diff --git a/.vscode/launch.json b/.vscode/launch.json index a9e017d4c1..e1e676abe3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -85,7 +85,8 @@ "args": ["${workspaceRoot}/Composer/packages/electron-server"], "env": { "NODE_ENV": "development", - "DEBUG": "composer*" + "DEBUG": "composer*", + "COMPOSER_ENABLE_ONEAUTH": "false" }, "outputCapture": "std" }, diff --git a/Composer/babel.l10n.config.js b/Composer/babel.l10n.config.js new file mode 100644 index 0000000000..6bf5abe37a --- /dev/null +++ b/Composer/babel.l10n.config.js @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +module.exports = { + presets: ['@babel/react', ['@babel/typescript', { allowNamespaces: true }]], + plugins: ['@babel/plugin-proposal-class-properties'], + ignore: [ + 'packages/electron-server', + 'packages/**/__tests__', + 'packages/**/node_modules', + 'packages/**/build/**/*.js', + ], +}; diff --git a/Composer/cypress/integration/Breadcrumb.spec.ts b/Composer/cypress/integration/Breadcrumb.spec.ts index 47016c8f3b..e0a0ff7eb4 100644 --- a/Composer/cypress/integration/Breadcrumb.spec.ts +++ b/Composer/cypress/integration/Breadcrumb.spec.ts @@ -35,12 +35,12 @@ context('breadcrumb', () => { hasBreadcrumbItems(cy, ['__TestTodoSample']); }); - it('can show event name in breadcrumb', () => { + it('can show dialog and trigger name in breadcrumb', () => { cy.findByTestId('ProjectTree').within(() => { cy.findByTestId('addtodo_Dialog started').click(); }); - hasBreadcrumbItems(cy, ['__TestTodoSample', 'Dialog started']); + hasBreadcrumbItems(cy, ['addtodo', 'Dialog started']); }); it('can show action name in breadcrumb', () => { diff --git a/Composer/cypress/integration/ToDoBot.spec.ts b/Composer/cypress/integration/ToDoBot.spec.ts index 0cbe207bfe..4625c90421 100644 --- a/Composer/cypress/integration/ToDoBot.spec.ts +++ b/Composer/cypress/integration/ToDoBot.spec.ts @@ -5,6 +5,8 @@ context('ToDo Bot', () => { before(() => { cy.visit('/home'); cy.createBot('TodoSample'); + cy.findByTestId('WelcomeModalCloseIcon').click(); + cy.findByText('Yes').click(); }); it('can open the main dialog', () => { diff --git a/Composer/package.json b/Composer/package.json index 3aabf9c0c2..0d5dd24e03 100644 --- a/Composer/package.json +++ b/Composer/package.json @@ -67,7 +67,7 @@ "l10n:extract": "cross-env NODE_ENV=production format-message extract -g underscored_crc32 -o packages/server/src/locales/en-US.json l10ntemp/**/*.js", "l10n:extractJson": "node scripts/l10n-extractJson.js", "l10n:transform": "node scripts/l10n-transform.js", - "l10n:babel": "babel ./packages --extensions \".ts,.tsx,.jsx,.js\" --out-dir l10ntemp --presets=@babel/react,@babel/typescript --plugins=@babel/plugin-proposal-class-properties --ignore \"packages/electron-server\",\"packages/**/__tests__\",\"packages/**/node_modules\",\"packages/**/build/**/*.js\"", + "l10n:babel": "babel --config-file ./babel.l10n.config.js --extensions \"ts,.tsx,.jsx,.js\" --out-dir l10ntemp ./packages", "l10n": "yarn l10n:babel && yarn l10n:extract && yarn l10n:transform packages/server/src/locales/en-US.json && yarn l10n:extractJson packages/server/schemas" }, "husky": { diff --git a/Composer/packages/adaptive-flow/__tests__/adaptive-flow-editor/stubs/ShellApiStub.ts b/Composer/packages/adaptive-flow/__tests__/adaptive-flow-editor/stubs/ShellApiStub.ts index d2c5583c85..9620c153c1 100644 --- a/Composer/packages/adaptive-flow/__tests__/adaptive-flow-editor/stubs/ShellApiStub.ts +++ b/Composer/packages/adaptive-flow/__tests__/adaptive-flow-editor/stubs/ShellApiStub.ts @@ -10,7 +10,7 @@ const fnPromise = () => Promise.resolve({} as any); export const ShellApiStub: ShellApi = { getDialog: fn, saveDialog: fn, - saveData: fn, + saveData: fnPromise, navTo: fn, onFocusSteps: fn, onFocusEvent: fn, diff --git a/Composer/packages/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts b/Composer/packages/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts index 82d35ce423..b757ab1aaa 100644 --- a/Composer/packages/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts +++ b/Composer/packages/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts @@ -46,7 +46,7 @@ export const useEditorEventApi = ( onFocusSteps, onFocusEvent, onCopy: onClipboardChange, - navTo: onOpen, + navTo, saveData: onChange, undo, redo, @@ -153,16 +153,17 @@ export const useEditorEventApi = ( break; case NodeEventTypes.OpenDialog: handler = ({ callee }) => { - onOpen(callee); + navTo(callee, '"beginDialog"'); announce(ScreenReaderMessage.DialogOpened); }; break; case NodeEventTypes.Delete: trackActionChange(eventData.id); handler = (e) => { - onChange(deleteSelectedAction(path, data, e.id)); - onFocusSteps([]); - announce(ScreenReaderMessage.ActionDeleted); + onChange(deleteSelectedAction(path, data, e.id), undefined, async () => { + await onFocusSteps([]); + announce(ScreenReaderMessage.ActionDeleted); + }); }; break; case NodeEventTypes.Insert: @@ -170,19 +171,20 @@ export const useEditorEventApi = ( if (eventData.$kind === MenuEventTypes.Paste) { handler = (e) => { insertActions(path, data, e.id, e.position, clipboardActions).then((dialog) => { - onChange(dialog); - onFocusSteps([`${e.id}[${e.position || 0}]`]); + return onChange(dialog, undefined, async () => { + await onFocusSteps([`${e.id}[${e.position || 0}]`]); + announce(ScreenReaderMessage.ActionCreated); + }); }); - - announce(ScreenReaderMessage.ActionCreated); }; } else { handler = (e) => { const newAction = dialogFactory.create(e.$kind); insertAction(path, data, e.id, e.position, newAction).then((dialog) => { - onChange(dialog); - onFocusSteps([`${e.id}[${e.position || 0}]`]); - announce(ScreenReaderMessage.ActionCreated); + return onChange(dialog, undefined, async () => { + await onFocusSteps([`${e.id}[${e.position || 0}]`]); + announce(ScreenReaderMessage.ActionCreated); + }); }); }; } @@ -199,9 +201,10 @@ export const useEditorEventApi = ( const actionIds = getClipboardTargetsFromContext(); trackActionListChange(actionIds); cutSelectedActions(path, data, actionIds).then(({ dialog, cutActions }) => { - onChange(dialog); - onFocusSteps([]); - onClipboardChange(cutActions); + onChange(dialog, undefined, async () => { + await onFocusSteps([]); + onClipboardChange(cutActions); + }); }); announce(ScreenReaderMessage.ActionsCut); }; @@ -259,18 +262,20 @@ export const useEditorEventApi = ( placeholderPosition.arrayIndex, placeholderAction ); - onChange(insertResult); - onFocusSteps([]); - announce(ScreenReaderMessage.ActionsMoved); + onChange(insertResult, undefined, async () => { + await onFocusSteps([]); + announce(ScreenReaderMessage.ActionsMoved); + }); }; break; case NodeEventTypes.DeleteSelection: handler = () => { const actionIds = getClipboardTargetsFromContext(); trackActionListChange(actionIds); - onChange(deleteSelectedActions(path, data, actionIds)); - onFocusSteps([]); - announce(ScreenReaderMessage.ActionsDeleted); + onChange(deleteSelectedActions(path, data, actionIds), undefined, async () => { + await onFocusSteps([]); + announce(ScreenReaderMessage.ActionsDeleted); + }); }; break; case NodeEventTypes.DisableSelection: diff --git a/Composer/packages/adaptive-flow/src/adaptive-flow-renderer/components/ElementMeasurer.tsx b/Composer/packages/adaptive-flow/src/adaptive-flow-renderer/components/ElementMeasurer.tsx index 5f076217b0..57073ee758 100644 --- a/Composer/packages/adaptive-flow/src/adaptive-flow-renderer/components/ElementMeasurer.tsx +++ b/Composer/packages/adaptive-flow/src/adaptive-flow-renderer/components/ElementMeasurer.tsx @@ -19,13 +19,13 @@ export interface ElementMeasurerProps { export const ElementMeasurer: React.FC = ({ children, style, onResize }) => { return ( { + scroll + onResize={({ scroll }) => { /** * As a parent node, mounted before children mounted. * Avoid flickering issue by filtering out the first onResize event. */ - const { width, height } = bounds ?? { width: 0, height: 0 }; + const { width, height } = scroll ?? { width: 0, height: 0 }; if (width === 0 && height === 0) return; onResize(new Boundary(width, height)); }} diff --git a/Composer/packages/adaptive-form/src/components/fields/OneOfField/__tests__/utils.test.ts b/Composer/packages/adaptive-form/src/components/fields/OneOfField/__tests__/utils.test.ts index 2a74117b97..7e5491b86a 100644 --- a/Composer/packages/adaptive-form/src/components/fields/OneOfField/__tests__/utils.test.ts +++ b/Composer/packages/adaptive-form/src/components/fields/OneOfField/__tests__/utils.test.ts @@ -22,9 +22,9 @@ describe('getOptions', () => { it('returns all of the types, sorted', () => { const { options } = getOptions(schema, {}); expect(options).toEqual([ - makeOption(schema, 'boolean'), - makeOption(schema, 'number'), makeOption(schema, 'string'), + makeOption(schema, 'number'), + makeOption(schema, 'boolean'), ]); }); }); @@ -71,13 +71,13 @@ describe('getOptions', () => { const { options } = getOptions(schema, definitions); const optionKeys = options.map((o) => o.key); expect(optionKeys).toEqual([ - 'my awesome string', - 'boolean', + 'string', 'number', + 'boolean', + 'my awesome string', 'an enum', 'dropdown', 'another type', - 'string', 'unknown', ]); }); diff --git a/Composer/packages/adaptive-form/src/components/fields/OneOfField/utils.ts b/Composer/packages/adaptive-form/src/components/fields/OneOfField/utils.ts index d93e944464..4a6b6cc37f 100644 --- a/Composer/packages/adaptive-form/src/components/fields/OneOfField/utils.ts +++ b/Composer/packages/adaptive-form/src/components/fields/OneOfField/utils.ts @@ -25,6 +25,18 @@ function getOptionLabel(schema: JSONSchema7): string { return type || 'unknown'; } +const typePriorityWeights = { + string: 5, + number: 4, + boolean: 3, + array: 2, + object: 1, +}; + +const sortOptionsByTypeWeights = ({ key: type1 }, { key: type2 }): number => { + return (typePriorityWeights[type1] || 0) > (typePriorityWeights[type2] || 0) ? -1 : 1; +}; + export function getOptions( schema: JSONSchema7, definitions?: SchemaDefinitions @@ -40,7 +52,7 @@ export function getOptions( data: { schema: { ...schema, type: t }, icon: getFieldIconText(t) }, })); - options.sort(({ text: t1 }, { text: t2 }) => (t1 > t2 ? 1 : -1)); + options.sort(sortOptionsByTypeWeights); return { options, isNested }; } @@ -64,6 +76,8 @@ export function getOptions( }) .filter(Boolean) as IDropdownOption[]; + options.sort(sortOptionsByTypeWeights); + const expression = (resolvedOneOf as JSONSchema7[]).find(({ $role }) => $role === 'expression'); const merged = merge({}, omit(schema, 'oneOf'), expression); diff --git a/Composer/packages/adaptive-form/src/components/fields/RegexIntentField.tsx b/Composer/packages/adaptive-form/src/components/fields/RegexIntentField.tsx index 60396d7bf4..5f80e7de24 100644 --- a/Composer/packages/adaptive-form/src/components/fields/RegexIntentField.tsx +++ b/Composer/packages/adaptive-form/src/components/fields/RegexIntentField.tsx @@ -7,12 +7,10 @@ import React, { useState, useEffect } from 'react'; import { FieldProps, useShellApi, MicrosoftIDialog } from '@bfc/extension-client'; import { RegexRecognizer } from '@bfc/shared'; -import { useFormData } from '../../hooks'; - import { StringField } from './StringField'; -function getRegexIntentPattern(formData: MicrosoftIDialog, intent: string): string { - const recognizer = formData.recognizer as RegexRecognizer; +function getRegexIntentPattern(dialogContent: MicrosoftIDialog, intent: string): string { + const recognizer = dialogContent.recognizer as RegexRecognizer; let pattern = ''; if (!recognizer) { @@ -28,15 +26,14 @@ function getRegexIntentPattern(formData: MicrosoftIDialog, intent: string): stri const RegexIntentField: React.FC = ({ value: intentName, ...rest }) => { const { currentDialog, shellApi } = useShellApi(); - const formData = useFormData(); - const [localValue, setLocalValue] = useState(getRegexIntentPattern(formData, intentName)); + const [localValue, setLocalValue] = useState(getRegexIntentPattern(currentDialog?.content, intentName)); // if the intent name changes or intent names in the regex patterns // we need to reset the local value useEffect(() => { - const pattern = getRegexIntentPattern(formData, intentName); + const pattern = getRegexIntentPattern(currentDialog?.content, intentName); setLocalValue(pattern); - }, [intentName, (formData.recognizer as RegexRecognizer)?.intents.map((i) => i.intent)]); + }, [intentName, currentDialog?.content]); const handleIntentChange = (pattern?: string) => { setLocalValue(pattern ?? ''); diff --git a/Composer/packages/client/__tests__/components/CreationFlow/index.test.tsx b/Composer/packages/client/__tests__/components/CreationFlow/index.test.tsx index 2a1206494e..8117a34751 100644 --- a/Composer/packages/client/__tests__/components/CreationFlow/index.test.tsx +++ b/Composer/packages/client/__tests__/components/CreationFlow/index.test.tsx @@ -5,9 +5,15 @@ import * as React from 'react'; import { render, fireEvent, act } from '@botframework-composer/test-utils'; import { createHistory, createMemorySource, LocationProvider } from '@reach/router'; import { RecoilRoot } from 'recoil'; +import { getDefaultFeatureFlags } from '@bfc/shared'; import CreationFlow from '../../../src/components/CreationFlow/CreationFlow'; -import { focusedStorageFolderState, creationFlowStatusState, dispatcherState } from '../../../src/recoilModel'; +import { + focusedStorageFolderState, + creationFlowStatusState, + dispatcherState, + featureFlagsState, +} from '../../../src/recoilModel'; import { CreationFlowStatus } from '../../../src/constants'; describe('', () => { @@ -26,7 +32,7 @@ describe('', () => { saveTemplateId: jest.fn(), }); set(creationFlowStatusState, CreationFlowStatus.NEW_FROM_TEMPLATE); - + set(featureFlagsState, getDefaultFeatureFlags()); set(focusedStorageFolderState, { name: 'Desktop', parent: '/test-folder', diff --git a/Composer/packages/client/__tests__/components/createQnAModal.test.tsx b/Composer/packages/client/__tests__/components/createQnAModal.test.tsx index c48efaa4fe..c549ce45dc 100644 --- a/Composer/packages/client/__tests__/components/createQnAModal.test.tsx +++ b/Composer/packages/client/__tests__/components/createQnAModal.test.tsx @@ -6,7 +6,7 @@ import { fireEvent } from '@botframework-composer/test-utils'; import { renderWithRecoil } from '../testUtils/renderWithRecoil'; import CreateQnAFromUrlModal from '../../src/components/QnA/CreateQnAFromUrlModal'; -import { showCreateQnAFromUrlDialogState, showCreateQnAFromUrlDialogWithScratchState } from '../../src/recoilModel'; +import { showCreateQnAFromUrlDialogState } from '../../src/recoilModel'; describe('', () => { const onDismiss = jest.fn(() => {}); @@ -24,7 +24,6 @@ describe('', () => { />, ({ set }) => { set(showCreateQnAFromUrlDialogState(projectId), true); - set(showCreateQnAFromUrlDialogWithScratchState(projectId), true); } ); diff --git a/Composer/packages/client/__tests__/components/home.test.tsx b/Composer/packages/client/__tests__/components/home.test.tsx index e379b8959d..3f4d035646 100644 --- a/Composer/packages/client/__tests__/components/home.test.tsx +++ b/Composer/packages/client/__tests__/components/home.test.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { fireEvent, render } from '@botframework-composer/test-utils'; -import { ProjectTemplate } from '@bfc/shared'; +import { BotTemplate } from '@bfc/shared'; import { RecentBotList } from '../../src/pages/home/RecentBotList'; import { ExampleList } from '../../src/pages/home/ExampleList'; @@ -32,7 +32,7 @@ describe('', () => { const templates = [ { description: 'echo bot', id: 'EchoBot', name: 'Echo Bot' }, { description: 'empty bot', id: 'EmptyBot', name: 'Empty Bot' }, - ] as ProjectTemplate[]; + ] as BotTemplate[]; const onClickTemplate = jest.fn((item) => item); const { container, getByText } = render(); expect(container).toHaveTextContent('Echo Bot'); diff --git a/Composer/packages/client/__tests__/pages/design/Design.test.tsx b/Composer/packages/client/__tests__/pages/design/Design.test.tsx new file mode 100644 index 0000000000..a26cbeacc0 --- /dev/null +++ b/Composer/packages/client/__tests__/pages/design/Design.test.tsx @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +import React from 'react'; + +import { renderWithRecoil } from '../../testUtils'; +import { + botProjectIdsState, + currentProjectIdState, + dialogsSelectorFamily, + schemasState, + projectMetaDataState, + botProjectFileState, +} from '../../../src/recoilModel'; +import { undoFunctionState } from '../../../src/recoilModel/undo/history'; +import mockProjectResponse from '../../../src/recoilModel/dispatchers/__tests__/mocks/mockProjectResponse.json'; +import DesignPage from '../../../src/pages/design/DesignPage'; +import { SAMPLE_DIALOG, SAMPLE_DIALOG_2 } from '../../mocks/sampleDialog'; + +const projectId = '12345.6789'; +const skillId = '56789.1234'; +const dialogId = SAMPLE_DIALOG.id; + +const initRecoilState = ({ set }) => { + set(currentProjectIdState, projectId); + set(botProjectIdsState, [projectId]); + set(dialogsSelectorFamily(projectId), [SAMPLE_DIALOG]); + set(schemasState(projectId), mockProjectResponse.schemas); + set(projectMetaDataState(projectId), { isRootBot: true }); + set(botProjectFileState(projectId), { foo: 'bar' }); + set(undoFunctionState(projectId), { canUndo: () => false, canRedo: () => false }); +}; + +const initRecoilStateMulti = ({ set }) => { + set(currentProjectIdState, projectId); + set(botProjectIdsState, [projectId, skillId]); + set(dialogsSelectorFamily(projectId), [SAMPLE_DIALOG]); + set(dialogsSelectorFamily(skillId), [SAMPLE_DIALOG, SAMPLE_DIALOG_2]); + set(schemasState(projectId), mockProjectResponse.schemas); + set(schemasState(skillId), mockProjectResponse.schemas); + set(projectMetaDataState(projectId), { isRootBot: true }); + set(botProjectFileState(projectId), { foo: 'bar' }); + set(undoFunctionState(projectId), { canUndo: () => false, canRedo: () => false }); + set(undoFunctionState(skillId), { canUndo: () => false, canRedo: () => false }); +}; + +describe('publish page', () => { + it('should render the design page (no skill)', () => { + const { getAllByText, getByText } = renderWithRecoil( + , + initRecoilState + ); + getAllByText(SAMPLE_DIALOG.displayName); + getByText('Start Bot'); + }); + + it('should render the design page (with skill)', () => { + const { getAllByText, getByText } = renderWithRecoil( + , + initRecoilStateMulti + ); + getAllByText(SAMPLE_DIALOG.displayName); + getAllByText(SAMPLE_DIALOG_2.displayName); + getByText('Start Bot'); + }); +}); diff --git a/Composer/packages/client/__tests__/utils/navigation.test.ts b/Composer/packages/client/__tests__/utils/navigation.test.ts index 100e4c12d3..0d526988f7 100644 --- a/Composer/packages/client/__tests__/utils/navigation.test.ts +++ b/Composer/packages/client/__tests__/utils/navigation.test.ts @@ -3,15 +3,7 @@ import { PromptTab } from '@bfc/shared'; -import { - BreadcrumbUpdateType, - getUrlSearch, - checkUrl, - getFocusPath, - clearBreadcrumb, - updateBreadcrumb, - convertPathToUrl, -} from './../../src/utils/navigation'; +import { getUrlSearch, checkUrl, getFocusPath, convertPathToUrl } from './../../src/utils/navigation'; const projectId = '123a-sdf123'; const skillId = '98765.4321'; @@ -27,46 +19,6 @@ describe('getFocusPath', () => { }); }); -describe('Breadcrumb Util', () => { - it('return focus path', () => { - const breadcrumb = [ - { dialogId: `1`, selected: `1`, focused: `1` }, - { dialogId: `2`, selected: `2`, focused: `2` }, - { dialogId: `3`, selected: `3`, focused: `3` }, - ]; - const result1 = clearBreadcrumb(breadcrumb); - expect(result1).toEqual([]); - const result2 = clearBreadcrumb(breadcrumb, 0); - expect(result2).toEqual([]); - const result3 = clearBreadcrumb(breadcrumb, 1); - expect(result3.length).toEqual(1); - expect(result3[0].dialogId).toEqual('1'); - const result4 = clearBreadcrumb(breadcrumb, 4); - expect(result4.length).toEqual(3); - }); - - it('update breadcrumb', () => { - const result1 = updateBreadcrumb([], BreadcrumbUpdateType.Selected); - expect(result1).toEqual([]); - let breadcrumb = [ - { dialogId: `1`, selected: `1`, focused: `1` }, - { dialogId: `2`, selected: `2`, focused: `2` }, - { dialogId: `3`, selected: `3`, focused: `3` }, - ]; - const result2 = updateBreadcrumb(breadcrumb, BreadcrumbUpdateType.Selected); - expect(result2.length).toEqual(1); - expect(result2[0].dialogId).toEqual('1'); - breadcrumb = [ - { dialogId: `1`, selected: `1`, focused: `` }, - { dialogId: `2`, selected: `2`, focused: `` }, - { dialogId: `3`, selected: `3`, focused: `3` }, - ]; - const result3 = updateBreadcrumb(breadcrumb, BreadcrumbUpdateType.Focused); - expect(result3.length).toEqual(2); - expect(result3[1].dialogId).toEqual('2'); - }); -}); - describe('composer url util', () => { it('create url', () => { const result1 = getUrlSearch('triggers[0]', 'triggers[0].actions[0]'); diff --git a/Composer/packages/client/public/index.html b/Composer/packages/client/public/index.html index 047330b0d4..51d64d74e3 100644 --- a/Composer/packages/client/public/index.html +++ b/Composer/packages/client/public/index.html @@ -41,6 +41,12 @@ <% } %> + + + + diff --git a/Composer/packages/client/src/components/CreationFlow/CreateOptions.tsx b/Composer/packages/client/src/components/CreationFlow/CreateOptions.tsx index e4c6a586b2..184bc6a9b4 100644 --- a/Composer/packages/client/src/components/CreationFlow/CreateOptions.tsx +++ b/Composer/packages/client/src/components/CreationFlow/CreateOptions.tsx @@ -20,7 +20,7 @@ import { DetailsRow, } from 'office-ui-fabric-react/lib/DetailsList'; import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'; -import { ProjectTemplate } from '@bfc/shared'; +import { BotTemplate } from '@bfc/shared'; import { DialogWrapper, DialogTypes } from '@bfc/ui-shared'; import { NeutralColors } from '@uifabric/fluent-theme'; import { RouteComponentProps } from '@reach/router'; @@ -105,7 +105,7 @@ const optionKeys = { // -------------------- CreateOptions -------------------- // type CreateOptionsProps = { - templates: ProjectTemplate[]; + templates: BotTemplate[]; onDismiss: () => void; onNext: (data: string) => void; } & RouteComponentProps<{}>; @@ -119,7 +119,7 @@ export function CreateOptions(props: CreateOptionsProps) { const selection = useMemo(() => { return new Selection({ onSelectionChanged: () => { - const t = selection.getSelection()[0] as ProjectTemplate; + const t = selection.getSelection()[0] as BotTemplate; if (t) { setCurrentTemplate(t.id); } diff --git a/Composer/packages/client/src/components/CreationFlow/DefineConversation.tsx b/Composer/packages/client/src/components/CreationFlow/DefineConversation.tsx index 4fcf9292fb..4197a28de0 100644 --- a/Composer/packages/client/src/components/CreationFlow/DefineConversation.tsx +++ b/Composer/packages/client/src/components/CreationFlow/DefineConversation.tsx @@ -8,7 +8,7 @@ import { DialogFooter } from 'office-ui-fabric-react/lib/Dialog'; import formatMessage from 'format-message'; import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button'; import { Stack, StackItem } from 'office-ui-fabric-react/lib/Stack'; -import React, { Fragment, useEffect, useCallback } from 'react'; +import React, { Fragment, useEffect, useCallback, useMemo } from 'react'; import { TextField } from 'office-ui-fabric-react/lib/TextField'; import { RouteComponentProps } from '@reach/router'; import querystring from 'query-string'; @@ -212,6 +212,18 @@ const DefineConversation: React.FC = (props) => { updateField('location', location); }, [focusedStorageFolder]); + const locationSelectContent = useMemo(() => { + return ( + + ); + }, [focusedStorageFolder]); + return ( = (props) => { /> - - + {locationSelectContent} { - const notifications = useRecoilValue(notificationsSelector); + const notifications = useRecoilValue(notificationsSelector).filter(({ hidden }) => !hidden); const { deleteNotification, hideNotification } = useRecoilValue(dispatcherState); + if (isEmpty(notifications)) return null; + return (
{notifications.map((item) => { diff --git a/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx b/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx index bd3baefe10..941ad00d06 100644 --- a/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx +++ b/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx @@ -5,11 +5,15 @@ import { jsx, css } from '@emotion/core'; import { useState, MouseEvent, KeyboardEvent } from 'react'; +import { INDENT_PER_LEVEL } from './constants'; + type Props = { children: React.ReactNode; summary: React.ReactNode; depth?: number; detailsRef?: (el: HTMLElement | null) => void; + onToggle?: (newState: boolean) => void; + defaultState?: boolean; }; const summaryStyle = css` @@ -20,7 +24,7 @@ const summaryStyle = css` `; const nodeStyle = (depth: number) => css` - margin-left: ${depth * 16}px; + margin-left: ${depth * INDENT_PER_LEVEL}px; `; const TRIANGLE_SCALE = 0.6; @@ -28,25 +32,32 @@ const TRIANGLE_SCALE = 0.6; const detailsStyle = css` &:not([open]) > summary::-webkit-details-marker { transform: scaleX(${TRIANGLE_SCALE}); + min-width: 10px; } &[open] > summary::-webkit-details-marker { transform: scaleY(${TRIANGLE_SCALE}); + min-width: 10px; } `; -export const ExpandableNode = ({ children, summary, detailsRef, depth = 0 }: Props) => { - const [isExpanded, setExpanded] = useState(true); +export const ExpandableNode = ({ children, summary, detailsRef, depth = 0, onToggle, defaultState = true }: Props) => { + const [isExpanded, setExpanded] = useState(defaultState); + + function setExpandedWithCallback(newState: boolean) { + setExpanded(newState); + onToggle?.(newState); + } function handleClick(ev: MouseEvent) { if ((ev.target as Element)?.tagName.toLowerCase() === 'summary') { - setExpanded(!isExpanded); + setExpandedWithCallback(!isExpanded); } ev.preventDefault(); } function handleKey(ev: KeyboardEvent) { - if (ev.key === 'Enter' || ev.key === 'Space') setExpanded(!isExpanded); + if (ev.key === 'Enter' || ev.key === 'Space') setExpandedWithCallback(!isExpanded); } return ( diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx index 038e2df623..0deffc36a8 100644 --- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx +++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx @@ -2,14 +2,14 @@ // Licensed under the MIT License. /** @jsx jsx */ -import React, { useCallback, useState, useEffect } from 'react'; +import React, { useCallback, useState, useEffect, useRef } from 'react'; import { jsx, css } from '@emotion/core'; import { SearchBox } from 'office-ui-fabric-react/lib/SearchBox'; import { FocusZone, FocusZoneDirection } from 'office-ui-fabric-react/lib/FocusZone'; import cloneDeep from 'lodash/cloneDeep'; import formatMessage from 'format-message'; import { DialogInfo, ITrigger, Diagnostic, DiagnosticSeverity } from '@bfc/shared'; -import debounce from 'lodash/debounce'; +import throttle from 'lodash/throttle'; import { useRecoilValue } from 'recoil'; import { ISearchBoxStyles } from 'office-ui-fabric-react/lib/SearchBox'; import { extractSchemaProperties, groupTriggersByPropertyReference, NoGroupingTriggerGroupName } from '@bfc/indexers'; @@ -20,6 +20,7 @@ import { rootBotProjectIdSelector, botProjectSpaceSelector, jsonSchemaFilesByProjectIdSelector, + pageElementState, } from '../../recoilModel'; import { getFriendlyName } from '../../utils/dialogUtil'; import { triggerNotSupported } from '../../utils/dialogValidator'; @@ -28,6 +29,7 @@ import { LoadingSpinner } from '../LoadingSpinner'; import { TreeItem } from './treeItem'; import { ExpandableNode } from './ExpandableNode'; +import { INDENT_PER_LEVEL } from './constants'; // -------------------- Styles -------------------- // @@ -65,8 +67,6 @@ const tree = css` label: tree; `; -const SUMMARY_ARROW_SPACE = 28; // the rough pixel size of the dropdown arrow to the left of a Details/Summary element - // -------------------- Helper functions -------------------- // const getTriggerIndex = (trigger: ITrigger, dialog: DialogInfo): number => { @@ -95,6 +95,7 @@ export type TreeLink = { skillId?: string; dialogId?: string; trigger?: number; + parentLink?: TreeLink; }; export type TreeMenuItem = { @@ -138,6 +139,8 @@ type Props = { defaultSelected?: Partial; }; +const TREE_PADDING = 100; // the horizontal space taken up by stuff in the tree other than text or indentation + export const ProjectTree: React.FC = ({ onSelectAllLink: onAllSelected = undefined, showTriggers = true, @@ -147,12 +150,21 @@ export const ProjectTree: React.FC = ({ onSelect, defaultSelected, }) => { - const { onboardingAddCoachMarkRef, navigateToFormDialogSchema } = useRecoilValue(dispatcherState); + const { onboardingAddCoachMarkRef, navigateToFormDialogSchema, setPageElementState } = useRecoilValue( + dispatcherState + ); + const treeRef = useRef(null); + + const pageElements = useRecoilValue(pageElementState).design; + const leftSplitWidth = pageElements?.leftSplitWidth ?? treeRef?.current?.clientWidth ?? 0; + const getPageElement = (name: string) => pageElements?.[name]; + const setPageElement = (name: string, value: any) => + setPageElementState('design', { ...pageElements, [name]: value }); const [filter, setFilter] = useState(''); const formDialogComposerFeatureEnabled = useFeatureFlag('FORM_DIALOG'); const [selectedLink, setSelectedLink] = useState | undefined>(defaultSelected); - const delayedSetFilter = debounce((newValue) => setFilter(newValue), 1000); + const delayedSetFilter = throttle((newValue) => setFilter(newValue), 200); const addMainDialogRef = useCallback((mainDialog) => onboardingAddCoachMarkRef({ mainDialog }), []); const projectCollection = useRecoilValue(botProjectSpaceSelector).map((bot) => ({ ...bot, @@ -217,7 +229,7 @@ export const ProjectTree: React.FC = ({ const doesLinkMatch = (linkInTree?: Partial, selectedLink?: Partial) => { if (linkInTree == null || selectedLink == null) return false; return ( - linkInTree.skillId === selectedLink.skillId && + (selectedLink.projectId === selectedLink.skillId || linkInTree.skillId === selectedLink.skillId) && linkInTree.dialogId === selectedLink.dialogId && linkInTree.trigger === selectedLink.trigger ); @@ -253,18 +265,19 @@ export const ProjectTree: React.FC = ({ > {} }]} + textWidth={leftSplitWidth - TREE_PADDING} onSelect={handleOnSelect} /> ); }; - const renderDialogHeader = (skillId: string, dialog: DialogInfo) => { + const renderDialogHeader = (skillId: string, dialog: DialogInfo, depth: number) => { const warningContent = notificationMap[skillId][dialog.id] ?.filter((diag) => diag.severity === DiagnosticSeverity.Warning) .map((diag) => diag.message) @@ -274,7 +287,7 @@ export const ProjectTree: React.FC = ({ .map((diag) => diag.message) .join(','); - const link: TreeLink = { + const dialogLink: TreeLink = { dialogId: dialog.id, displayName: dialog.displayName, isRoot: dialog.isRoot, @@ -287,49 +300,63 @@ export const ProjectTree: React.FC = ({ const isFormDialog = dialogIsFormDialog(dialog); const showEditSchema = formDialogSchemaExists(skillId, dialog); - return ( - - { - onDeleteDialog(link.dialogId ?? ''); - }, - }, - ...(showEditSchema - ? [ - { - label: formatMessage('Edit schema'), - icon: 'Edit', - onClick: (link) => - navigateToFormDialogSchema({ projectId: link.skillId, schemaId: link.dialogName }), - }, - ] - : []), - ]} - onSelect={handleOnSelect} - /> - - ); + return { + summaryElement: ( + + { + onDeleteDialog(link.dialogId ?? ''); + }, + }, + ] + : []), + ...(showEditSchema + ? [ + { + label: formatMessage('Edit schema'), + icon: 'Edit', + onClick: (link) => + navigateToFormDialogSchema({ projectId: link.skillId, schemaId: link.dialogName }), + }, + ] + : []), + ]} + textWidth={leftSplitWidth - TREE_PADDING} + onSelect={handleOnSelect} + /> + + ), + dialogLink, + }; }; - const renderTrigger = (item: any, dialog: DialogInfo, projectId: string): React.ReactNode => { + const renderTrigger = ( + item: any, + dialog: DialogInfo, + projectId: string, + dialogLink: TreeLink, + depth: number + ): React.ReactNode => { const link: TreeLink = { projectId: rootProjectId, skillId: projectId === rootProjectId ? undefined : projectId, @@ -339,13 +366,14 @@ export const ProjectTree: React.FC = ({ warningContent: item.warningContent, errorContent: item.errorContent, isRoot: false, + parentLink: dialogLink, }; return ( = ({ }, }, ]} + textWidth={leftSplitWidth - TREE_PADDING} onSelect={handleOnSelect} /> ); @@ -373,7 +402,13 @@ export const ProjectTree: React.FC = ({ return scope.toLowerCase().includes(filter.toLowerCase()); }; - const renderTriggerList = (triggers: ITrigger[], dialog: DialogInfo, projectId: string) => { + const renderTriggerList = ( + triggers: ITrigger[], + dialog: DialogInfo, + projectId: string, + dialogLink: TreeLink, + depth: number + ) => { return triggers .filter((tr) => filterMatch(dialog.displayName) || filterMatch(getTriggerName(tr))) .map((tr) => { @@ -385,17 +420,19 @@ export const ProjectTree: React.FC = ({ return renderTrigger( { ...tr, index, displayName: getTriggerName(tr), warningContent, errorContent }, dialog, - projectId + projectId, + dialogLink, + depth ); }); }; - const renderTriggerGroupHeader = (displayName: string, dialog: DialogInfo, projectId: string) => { + const renderTriggerGroupHeader = (displayName: string, dialog: DialogInfo, projectId: string, depth: number) => { const link: TreeLink = { dialogId: dialog.id, displayName, isRoot: false, - projectId: projectId, + projectId, }; return ( = ({ `} role="grid" > - + ); }; - // renders a named expandible node with the triggers as items underneath + // renders a named expandable node with the triggers as items underneath const renderTriggerGroup = ( projectId: string, dialog: DialogInfo, @@ -422,14 +459,21 @@ export const ProjectTree: React.FC = ({ const groupDisplayName = groupName === NoGroupingTriggerGroupName ? formatMessage('form-wide operations') : groupName; const key = `${projectId}.${dialog.id}.group-${groupName}`; + const link: TreeLink = { + dialogId: dialog.id, + displayName: groupName, + isRoot: false, + projectId, + }; return ( setPageElement(key, newState)} > -
{renderTriggerList(triggers, dialog, projectId)}
+
{renderTriggerList(triggers, dialog, projectId, link, startDepth + 1)}
); }; @@ -447,10 +491,10 @@ export const ProjectTree: React.FC = ({ }); }; - const renderDialogTriggers = (dialog: DialogInfo, projectId: string, startDepth: number) => { + const renderDialogTriggers = (dialog: DialogInfo, projectId: string, startDepth: number, dialogLink: TreeLink) => { return dialogIsFormDialog(dialog) - ? renderDialogTriggersByProperty(dialog, projectId, startDepth) - : renderTriggerList(dialog.triggers, dialog, projectId); + ? renderDialogTriggersByProperty(dialog, projectId, startDepth + 1) + : renderTriggerList(dialog.triggers, dialog, projectId, dialogLink, startDepth + 1); }; const createDetailsTree = (bot: BotInProject, startDepth: number) => { @@ -467,26 +511,36 @@ export const ProjectTree: React.FC = ({ if (showTriggers) { return filteredDialogs.map((dialog: DialogInfo) => { + const { summaryElement, dialogLink } = renderDialogHeader(projectId, dialog, startDepth); + const key = 'dialog-' + dialog.id; return ( setPageElement(key, newState)} > -
{renderDialogTriggers(dialog, projectId, startDepth + 1)}
+
{renderDialogTriggers(dialog, projectId, startDepth + 1, dialogLink)}
); }); } else { - return filteredDialogs.map((dialog: DialogInfo) => renderDialogHeader(projectId, dialog)); + return filteredDialogs.map((dialog: DialogInfo) => renderDialogHeader(projectId, dialog, startDepth)); } }; const createBotSubtree = (bot: BotInProject & { hasWarnings: boolean }) => { + const key = 'bot-' + bot.projectId; if (showDialogs && !bot.isRemote) { return ( - + setPageElement(key, newState)} + >
{createDetailsTree(bot, 1)}
); @@ -502,6 +556,7 @@ export const ProjectTree: React.FC = ({ return (
= ({
{onAllSelected != null ? ( ) : null} diff --git a/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx b/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx index 73cdf3a95e..ebd38eccce 100644 --- a/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx @@ -95,7 +95,7 @@ const optionRow = { export const warningIcon = { marginLeft: 5, color: '#BE880A', - fontSize: 5, + fontSize: 12, }; // -------------------- Validation Helpers -------------------- // diff --git a/Composer/packages/client/src/components/ProjectTree/constants.ts b/Composer/packages/client/src/components/ProjectTree/constants.ts new file mode 100644 index 0000000000..a447dd3f8e --- /dev/null +++ b/Composer/packages/client/src/components/ProjectTree/constants.ts @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export const SUMMARY_ARROW_SPACE = 28; // the rough pixel size of the dropdown arrow to the left of a Details/Summary element +export const INDENT_PER_LEVEL = 16; diff --git a/Composer/packages/client/src/components/ProjectTree/treeItem.tsx b/Composer/packages/client/src/components/ProjectTree/treeItem.tsx index 400b22932e..3db255b2d6 100644 --- a/Composer/packages/client/src/components/ProjectTree/treeItem.tsx +++ b/Composer/packages/client/src/components/ProjectTree/treeItem.tsx @@ -17,16 +17,16 @@ import { IContextualMenuStyles } from 'office-ui-fabric-react/lib/ContextualMenu import { ICalloutContentStyles } from 'office-ui-fabric-react/lib/Callout'; import { TreeLink, TreeMenuItem } from './ProjectTree'; +import { SUMMARY_ARROW_SPACE } from './constants'; // -------------------- Styles -------------------- // -const indent = 8; -const itemText = css` + +const iconAndText = css` outline: none; :focus { outline: rgb(102, 102, 102) solid 1px; z-index: 1; } - padding-left: ${indent}px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; @@ -41,6 +41,7 @@ const content = css` outline: none; display: flex; align-items: center; + height: 24px; label: ProjectTreeItem; `; @@ -61,7 +62,7 @@ const menuStyle: Partial = { const moreButton = (isActive: boolean): IButtonStyles => { return { root: { - padding: '0 4px', + padding: '4px 4px 0 4px', alignSelf: 'stretch', visibility: isActive ? 'visible' : 'hidden', height: 'auto', @@ -74,15 +75,20 @@ const moreButton = (isActive: boolean): IButtonStyles => { }; }; -const navItem = (isActive: boolean, shift: number) => css` - width: calc(100%-${shift}px); +const navItem = (isActive: boolean) => css` + label: navItem; + min-width: 100%; position: relative; height: 24px; font-size: 12px; - margin-left: ${shift}px; color: ${isActive ? '#ffffff' : '#545454'}; background: ${isActive ? '#0078d4' : 'transparent'}; font-weight: ${isActive ? FontWeights.semibold : FontWeights.regular}; + + display: flex; + flex-direction: row; + align-items: center; + &:hover { color: #545454; background: #f2f2f2; @@ -91,6 +97,7 @@ const navItem = (isActive: boolean, shift: number) => css` visibility: visible; } } + &:focus { outline: none; .ms-Fabric--isFocusVisible &::after { @@ -111,17 +118,17 @@ const navItem = (isActive: boolean, shift: number) => css` export const overflowSet = css` width: 100%; height: 100%; - padding-right: 12px; box-sizing: border-box; line-height: 24px; justify-content: space-between; display: flex; + margin-top: 2px; `; const statusIcon = { - width: '24px', + width: '12px', height: '18px', - fontSize: 16, + fontSize: 11, marginLeft: 6, }; @@ -135,6 +142,13 @@ const errorIcon = { color: '#CC3F3F', }; +const itemName = (nameWidth: number) => css` + max-width: ${nameWidth}px; + overflow: hidden; + text-overflow: ellipsis; + flex-shrink: 1; +`; + // -------------------- TreeItem -------------------- // interface ITreeItemProps { @@ -146,7 +160,9 @@ interface ITreeItemProps { icon?: string; dialogName?: string; showProps?: boolean; - forceIndent?: number; // needed to make an outline look right; should be the size of the "details" reveal arrow + textWidth?: number; + extraSpace?: number; + hasChildren?: boolean; } const renderTreeMenuItem = (link: TreeLink) => (item: TreeMenuItem) => { @@ -167,13 +183,13 @@ const renderTreeMenuItem = (link: TreeLink) => (item: TreeMenuItem) => { }; }; -const onRenderItem = (item: IOverflowSetItemProps) => { +const onRenderItem = (textWidth: number) => (item: IOverflowSetItemProps) => { const { warningContent, errorContent } = item; return (
{ tabIndex={-1} /> )} - {item.displayName} + {item.displayName} {item.errorContent && ( - + )} {item.warningContent && ( - + )}
@@ -240,20 +256,23 @@ export const TreeItem: React.FC = ({ isActive = false, icon, dialogName, - forceIndent, onSelect, + textWidth = 100, + hasChildren = false, menu = [], + extraSpace = 0, }) => { const a11yLabel = `${dialogName ?? '$Root'}_${link.displayName}`; const overflowMenu = menu.map(renderTreeMenuItem(link)); const linkString = `${link.projectId}_DialogTreeItem${link.dialogId}_${link.trigger ?? ''}`; + const spacerWidth = hasChildren ? 0 : SUMMARY_ARROW_SPACE + extraSpace; return (
= ({ } }} > +
= ({ overflowItems={overflowMenu} role="row" styles={{ item: { flex: 1 } }} - onRenderItem={onRenderItem} + onRenderItem={onRenderItem(textWidth - spacerWidth + extraSpace)} onRenderOverflowButton={onRenderOverflowButton(!!isActive)} />
diff --git a/Composer/packages/client/src/components/QnA/CreateQnAFromUrlModal.tsx b/Composer/packages/client/src/components/QnA/CreateQnAFromUrlModal.tsx index 9d2104b43b..04dff75949 100644 --- a/Composer/packages/client/src/components/QnA/CreateQnAFromUrlModal.tsx +++ b/Composer/packages/client/src/components/QnA/CreateQnAFromUrlModal.tsx @@ -14,11 +14,7 @@ import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button' import { Link } from 'office-ui-fabric-react/lib/Link'; import { FieldConfig, useForm } from '../../hooks/useForm'; -import { - dispatcherState, - onCreateQnAFromUrlDialogCompleteState, - showCreateQnAFromUrlDialogWithScratchState, -} from '../../recoilModel'; +import { dispatcherState, onCreateQnAFromUrlDialogCompleteState } from '../../recoilModel'; import { knowledgeBaseSourceUrl, @@ -67,7 +63,6 @@ export const CreateQnAFromUrlModal: React.FC = (props) const { onDismiss, onSubmit, dialogId, projectId, qnaFiles } = props; const actions = useRecoilValue(dispatcherState); const onComplete = useRecoilValue(onCreateQnAFromUrlDialogCompleteState(projectId)); - const showWithScratch = useRecoilValue(showCreateQnAFromUrlDialogWithScratchState(projectId)); formConfig.name.validate = validateName(qnaFiles); const { formData, updateField, hasErrors, formErrors } = useForm(formConfig); @@ -123,17 +118,15 @@ export const CreateQnAFromUrlModal: React.FC = (props)
- {showWithScratch && ( - { - // switch to create from scratch flow, pass onComplete callback. - actions.createQnAFromScratchDialogBegin({ projectId, onComplete: onComplete?.func }); - }} - /> - )} + { + // switch to create from scratch flow, pass onComplete callback. + actions.createQnAFromScratchDialogBegin({ projectId, onComplete: onComplete?.func }); + }} + /> { diff --git a/Composer/packages/client/src/components/Split/LeftRightSplit.tsx b/Composer/packages/client/src/components/Split/LeftRightSplit.tsx index 50f5bb550c..918b777ad8 100644 --- a/Composer/packages/client/src/components/Split/LeftRightSplit.tsx +++ b/Composer/packages/client/src/components/Split/LeftRightSplit.tsx @@ -4,6 +4,9 @@ import * as React from 'react'; import styled from '@emotion/styled'; import { default as Measure, ContentRect } from 'react-measure'; +import { useRecoilValue } from 'recoil'; + +import { dispatcherState, currentModeState } from '../../recoilModel'; const defaultSplitterWidth = 5; @@ -163,6 +166,9 @@ export const LeftRightSplit = (props: React.PropsWithChildren) => { const [leftStart, setLeftStart] = React.useState(0); const [screenStart, setScreenStart] = React.useState(0); + const currentPageMode = useRecoilValue(currentModeState); + const { setPageElementState } = useRecoilValue(dispatcherState); + const constrainLeft = (value: number): number => { return constrainPaneExtent(value, { total: currentContentWidth, @@ -198,6 +204,7 @@ export const LeftRightSplit = (props: React.PropsWithChildren) => { // calculate candidate left const newLeft = constrainLeft(leftStart + (event.screenX - screenStart)); setLeftWidth(newLeft); + setPageElementState(currentPageMode, { leftSplitWidth: newLeft }); } }; diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx index e838c9a3d2..fd19dc4697 100644 --- a/Composer/packages/client/src/pages/design/DesignPage.tsx +++ b/Composer/packages/client/src/pages/design/DesignPage.tsx @@ -18,20 +18,15 @@ import { LeftRightSplit } from '../../components/Split/LeftRightSplit'; import { LoadingSpinner } from '../../components/LoadingSpinner'; import { TestController } from '../../components/TestController/TestController'; import { DialogDeleting } from '../../constants'; -import { - createSelectedPath, - deleteTrigger, - getBreadcrumbLabel, - TriggerFormData, - getDialogData, -} from '../../utils/dialogUtil'; +import { createSelectedPath, deleteTrigger, TriggerFormData, getDialogData } from '../../utils/dialogUtil'; import { Conversation } from '../../components/Conversation'; import { dialogStyle } from '../../components/Modal/dialogStyle'; import { OpenConfirmModal } from '../../components/Modal/ConfirmDialog'; import { ProjectTree, TreeLink } from '../../components/ProjectTree/ProjectTree'; import { Toolbar, IToolbarItem } from '../../components/Toolbar'; -import { clearBreadcrumb, getFocusPath } from '../../utils/navigation'; +import { getFocusPath } from '../../utils/navigation'; import { navigateTo } from '../../utils/navigation'; +import { getFriendlyName } from '../../utils/dialogUtil'; import { useShell } from '../../shell'; import plugins, { mergePluginConfigs } from '../../plugins'; import { useElectronFeatures } from '../../hooks/useElectronFeatures'; @@ -42,7 +37,6 @@ import { schemasState, displaySkillManifestState, validateDialogsSelectorFamily, - breadcrumbState, focusPathState, showCreateDialogModalState, showAddSkillDialogModalState, @@ -55,6 +49,7 @@ import { triggerNotSupported } from '../../utils/dialogValidator'; import { undoFunctionState, undoVersionState } from '../../recoilModel/undo/history'; import { decodeDesignerPathToArrayPath } from '../../utils/convertUtils/designerPathEncoder'; import { useTriggerApi } from '../../shell/triggerApi'; +import { undoStatusSelectorFamily } from '../../recoilModel/selectors/undo'; import { WarningMessage } from './WarningMessage'; import { @@ -69,6 +64,13 @@ import { import { VisualEditor } from './VisualEditor'; import { PropertyEditor } from './PropertyEditor'; +type BreadcrumbItem = { + key: string; + label: string; + link?: Partial; + onClick?: () => void; +}; + const CreateSkillModal = React.lazy(() => import('../../components/CreateSkillModal')); const CreateDialogModal = React.lazy(() => import('./createDialogModal')); const DisplayManifestModal = React.lazy(() => import('../../components/Modal/DisplayManifestModal')); @@ -85,10 +87,6 @@ function onRenderContent(subTitle, style) { ); } -function onRenderBreadcrumbItem(item, render) { - return {render(item)}; -} - function getAllRef(targetId, dialogs) { let refs: string[] = []; dialogs.forEach((dialog) => { @@ -109,6 +107,13 @@ const getTabFromFragment = () => { } }; +const parseTriggerId = (triggerId: string | undefined): number | undefined => { + if (triggerId == null) return undefined; + const indexString = triggerId.match(/\d+/)?.[0]; + if (indexString == null) return undefined; + return parseInt(indexString); +}; + const DesignPage: React.FC> = ( props ) => { @@ -119,7 +124,6 @@ const DesignPage: React.FC(dialogs[0]); + const [currentDialog, setCurrentDialog] = useState(dialogs[0] as DialogInfo); const [exportSkillModalVisible, setExportSkillModalVisible] = useState(false); const [warningIsVisible, setWarningIsVisible] = useState(true); + const [breadcrumbs, setBreadcrumbs] = useState>([]); + const shell = useShell('DesignPage', skillId ?? rootProjectId); const shellForFlowEditor = useShell('FlowEditor', skillId ?? rootProjectId); const shellForPropertyEditor = useShell('PropertyEditor', skillId ?? rootProjectId); @@ -168,7 +174,7 @@ const DesignPage: React.FC { - const currentDialog = dialogs.find(({ id }) => id === dialogId); + const currentDialog = dialogs.find(({ id }) => id === dialogId) as DialogInfo | undefined; if (currentDialog) { setCurrentDialog(currentDialog); } @@ -205,10 +211,49 @@ const DesignPage: React.FC = []; + + breadcrumbArray.push({ + key: 'dialog-' + props.dialogId, + label: dialogMap[props.dialogId]?.$designer?.name ?? dialogMap[props.dialogId]?.$designer?.$designer?.name, + link: { + projectId: props.projectId, + dialogId: props.dialogId, + }, + onClick: () => navTo(projectId, dialogId), + }); + if (triggerIndex != null && trigger != null) { + breadcrumbArray.push({ + key: 'trigger-' + triggerIndex, + label: trigger.$designer.name || getFriendlyName(trigger), + link: { + projectId: props.projectId, + dialogId: props.dialogId, + trigger: triggerIndex, + }, + onClick: () => navTo(projectId, dialogId, `${triggerIndex}`), + }); + } + + // getDialogData returns whatever's at the end of the path, which could be a trigger or an action + const possibleAction = getDialogData(dialogMap, dialogId, focusPath); - if (typeof data === 'undefined') { + if (params.get('focused') != null) { + // we've linked to an action, so put that in too + breadcrumbArray.push({ + key: 'action-' + focusPath, + label: getActionName(possibleAction), + }); + } + + if (typeof possibleAction === 'undefined') { const { id: foundId } = dialogs.find(({ id }) => id === dialogId) || dialogs.find(({ isRoot }) => isRoot) || {}; /** * It's improper to fallback to `dialogId` directly: @@ -226,9 +271,9 @@ const DesignPage: React.FC = []; + if (dialogId != null) { + breadcrumbArray.push({ + key: 'dialog-' + parentLink?.dialogId, + label: parentLink?.displayName ?? link.displayName, + link: { projectId, skillId, dialogId }, + onClick: () => navTo(skillId ?? projectId, dialogId), + }); + } + if (trigger != null) { + breadcrumbArray.push({ + key: 'trigger-' + parentLink?.trigger, + label: link.displayName, + link: { projectId, skillId, dialogId, trigger }, + onClick: () => selectTo(skillId ?? null, dialogId ?? null, `triggers[${trigger}]`), + }); + } + + setBreadcrumbs(breadcrumbArray); + + if (trigger != null) { + selectTo(skillId ?? null, dialogId ?? null, `triggers[${trigger}]`); + } else if (dialogId != null) { + navTo(skillId ?? projectId, dialogId); } else { // with no dialog or ID, we must be looking at a bot link - navTo(link.skillId ?? link.projectId, null, []); + navTo(skillId ?? projectId, null); } } const onCreateDialogComplete = (dialogId) => { if (dialogId) { - navTo(projectId, dialogId, [{ dialogId, selected: '', focused: '' }]); + navTo(projectId, dialogId); } }; + const pluginConfig: PluginConfig = useMemo(() => { + const sdkUISchema = schemas?.ui?.content ?? {}; + const userUISchema = schemas?.uiOverrides?.content ?? {}; + return mergePluginConfigs({ uiSchema: sdkUISchema }, plugins, { uiSchema: userUISchema }); + }, [schemas?.ui?.content, schemas?.uiOverrides?.content]); + + const getActionName = (action) => { + const nameFromAction = action?.$designer?.name as string | undefined; + let detectedActionName: string; + + if (typeof nameFromAction === 'string') { + detectedActionName = nameFromAction; + } else { + const kind: string = action?.$kind as string; + const actionNameFromSchema = pluginConfig?.uiSchema?.[kind]?.form?.label as string | (() => string) | undefined; + if (typeof actionNameFromSchema === 'string') { + detectedActionName = actionNameFromSchema; + } else if (typeof actionNameFromSchema === 'function') { + detectedActionName = actionNameFromSchema(); + } else { + detectedActionName = formatMessage('Unknown'); + } + } + return detectedActionName; + }; + const { actionSelected, showDisableBtn, showEnableBtn } = useMemo(() => { const actionSelected = Array.isArray(visualEditorSelection) && visualEditorSelection.length > 0; if (!actionSelected) { @@ -285,11 +375,18 @@ const DesignPage: React.FC get(currentDialog?.content, id)); const showDisableBtn = selectedActions.some((x) => get(x, 'disabled') !== true); const showEnableBtn = selectedActions.some((x) => get(x, 'disabled') === true); - return { actionSelected, showDisableBtn, showEnableBtn }; - }, [visualEditorSelection]); - const { onFocusFlowEditor, onBlurFlowEditor } = useElectronFeatures(actionSelected, canUndo?.(), canRedo?.()); + if (selectedActions.length === 1 && selectedActions[0] != null) { + const action = selectedActions[0] as any; + const actionName = getActionName(action); + setBreadcrumbs((prev) => [...prev.slice(0, 2), { key: 'action-' + actionName, label: actionName }]); + } + + return { actionSelected, showDisableBtn, showEnableBtn }; + }, [visualEditorSelection, currentDialog?.content]); + + const { onFocusFlowEditor, onBlurFlowEditor } = useElectronFeatures(actionSelected, canUndo, canRedo); const EditorAPI = getEditorAPI(); const toolbarItems: IToolbarItem[] = [ { @@ -329,7 +426,6 @@ const DesignPage: React.FC { createQnAFromUrlDialogBegin({ projectId, - showFromScratch: true, }); }, }, @@ -349,13 +445,13 @@ const DesignPage: React.FC IBreadcrumbItem = (breadcrumb: BreadcrumbItem) => { + return { + key: breadcrumb.key, + text: breadcrumb.label, + onClick: () => breadcrumb.onClick?.(), + }; + }; - const breadcrumbItems = useMemo(() => { - const items = - dialogs.length > 0 - ? breadcrumb.reduce((result, item, index) => { - const { dialogId, selected, focused } = item; - const text = getBreadcrumbLabel(dialogs, dialogId, selected, focused); - if (text) { - result.push({ - // @ts-ignore - index, - isRoot: !selected && !focused, - text, - ...item, - onClick: handleBreadcrumbItemClick, - }); - } - return result; - }, [] as IBreadcrumbItem[]) - : []; - return ( -
- undefined} - onRenderItem={onRenderBreadcrumbItem} - /> -
- { - setDialogJsonVisibility((current) => !current); - }} - > - {dialogJsonVisible ? formatMessage('Hide code') : formatMessage('Show code')} - -
+ const items = breadcrumbs.map(createBreadcrumbItem); + + const breadcrumbItems = ( +
+ undefined} + /> +
+ { + setDialogJsonVisibility((current) => !current); + }} + > + {dialogJsonVisible ? formatMessage('Hide code') : formatMessage('Show code')} +
- ); - }, [dialogs, breadcrumb, dialogJsonVisible]); +
+ ); async function handleCreateDialogSubmit(dialogName, dialogData) { await createDialog({ id: dialogName, content: dialogData, projectId }); @@ -551,7 +629,7 @@ const DesignPage: React.FC { - const sdkUISchema = schemas?.ui?.content ?? {}; - const userUISchema = schemas?.uiOverrides?.content ?? {}; - return mergePluginConfigs({ uiSchema: sdkUISchema }, plugins, { uiSchema: userUISchema }); - }, [schemas?.ui?.content, schemas?.uiOverrides?.content]); - if (!dialogId) { return ; } @@ -588,13 +660,6 @@ const DesignPage: React.FC t.id === selected); const withWarning = triggerNotSupported(currentDialog, selectedTrigger); - const parseTriggerId = (triggerId: string | undefined): number | undefined => { - if (triggerId == null) return undefined; - const indexString = triggerId.match(/\d+/)?.[0]; - if (indexString == null) return undefined; - return parseInt(indexString); - }; - return (
@@ -642,7 +707,7 @@ const DesignPage: React.FC { setWarningIsVisible(false); }} - onOk={() => navigateTo(`/bot/${projectId}/knowledge-base/all`)} + onOk={() => navigateTo(`/bot/${projectId}/dialogs/${dialogId}`)} /> ) ) : ( diff --git a/Composer/packages/client/src/pages/design/PropertyEditor.tsx b/Composer/packages/client/src/pages/design/PropertyEditor.tsx index 73c40582d1..d523df8ca7 100644 --- a/Composer/packages/client/src/pages/design/PropertyEditor.tsx +++ b/Composer/packages/client/src/pages/design/PropertyEditor.tsx @@ -38,7 +38,6 @@ const PropertyEditor: React.FC = () => { }, [currentDialog, focusedSteps[0]]); const [localData, setLocalData] = useState(dialogData as MicrosoftAdaptiveDialog); - const syncData = useRef( // eslint-disable-next-line @typescript-eslint/no-explicit-any debounce((shellData: any, localData: any) => { @@ -103,8 +102,6 @@ const PropertyEditor: React.FC = () => { const id = setTimeout(() => { if (!isEqual(dialogData, localData)) { shellApi.saveData(localData, focusedSteps[0]); - } else { - shellApi.commitChanges(); } }, 300); diff --git a/Composer/packages/client/src/pages/form-dialog/FormDialogPage.tsx b/Composer/packages/client/src/pages/form-dialog/FormDialogPage.tsx index 943f073b14..ef7fb1f8e7 100644 --- a/Composer/packages/client/src/pages/form-dialog/FormDialogPage.tsx +++ b/Composer/packages/client/src/pages/form-dialog/FormDialogPage.tsx @@ -27,10 +27,10 @@ const EmptyView = styled(Stack)({ opacity: 0.5, }); -type Props = RouteComponentProps<{ projectId: string; schemaId: string }>; +type Props = RouteComponentProps<{ projectId: string; skillId: string; schemaId: string }>; const FormDialogPage: React.FC = React.memo((props: Props) => { - const { projectId = '', schemaId = '' } = props; + const { projectId = '', skillId = '', schemaId = '' } = props; const formDialogSchemaIds = useRecoilValue(formDialogSchemaIdsState(projectId)); const formDialogLibraryTemplates = useRecoilValue(formDialogLibraryTemplatesState); const formDialogGenerationProgressing = useRecoilValue(formDialogGenerationProgressingState); @@ -90,7 +90,7 @@ const FormDialogPage: React.FC = React.memo((props: Props) => { const viewDialog = React.useCallback( (schemaId: string) => { if (schemaId) { - navigateToGeneratedDialog({ projectId, schemaId }); + navigateToGeneratedDialog({ projectId, skillId, schemaId }); } }, [navigateToGeneratedDialog, projectId] diff --git a/Composer/packages/client/src/pages/home/ExampleList.tsx b/Composer/packages/client/src/pages/home/ExampleList.tsx index 6054e32c05..5409d82002 100644 --- a/Composer/packages/client/src/pages/home/ExampleList.tsx +++ b/Composer/packages/client/src/pages/home/ExampleList.tsx @@ -6,7 +6,7 @@ import { jsx } from '@emotion/core'; import React from 'react'; import { ScrollablePane, ScrollbarVisibility } from 'office-ui-fabric-react/lib/ScrollablePane'; import { List } from 'office-ui-fabric-react/lib/List'; -import { ProjectTemplate } from '@bfc/shared'; +import { BotTemplate } from '@bfc/shared'; import * as exampleIcons from '../../images/samples'; @@ -20,7 +20,7 @@ import { } from './styles'; interface ExampleListProps { - examples: ProjectTemplate[]; + examples: BotTemplate[]; onClick: (templateId: string) => void; } @@ -35,7 +35,7 @@ const resolveIcon = (exampleId: string): string => { export const ExampleList: React.FC = (props) => { const { onClick, examples } = props; - function onRenderCell(item?: ProjectTemplate): React.ReactNode { + function onRenderCell(item?: BotTemplate): React.ReactNode { if (!item) { return; } diff --git a/Composer/packages/client/src/pages/knowledge-base/QnAPage.tsx b/Composer/packages/client/src/pages/knowledge-base/QnAPage.tsx index 13dfc69ab6..c53392b190 100644 --- a/Composer/packages/client/src/pages/knowledge-base/QnAPage.tsx +++ b/Composer/packages/client/src/pages/knowledge-base/QnAPage.tsx @@ -66,7 +66,7 @@ const QnAPage: React.FC = (props) => { key: 'Create KB from URL or file', onClick: () => { setCreateOnDialogId(dialog.id); - actions.createQnAFromUrlDialogBegin({ projectId, showFromScratch: false }); + actions.createQnAFromUrlDialogBegin({ projectId }); }, }, ], diff --git a/Composer/packages/client/src/pages/knowledge-base/table-view.tsx b/Composer/packages/client/src/pages/knowledge-base/table-view.tsx index e45b633fb1..b2a4684a00 100644 --- a/Composer/packages/client/src/pages/knowledge-base/table-view.tsx +++ b/Composer/packages/client/src/pages/knowledge-base/table-view.tsx @@ -769,7 +769,7 @@ const TableView: React.FC = (props) => { data-testid={'createKnowledgeBase'} text={formatMessage('Create new KB')} onClick={() => { - actions.createQnAFromUrlDialogBegin({ projectId, showFromScratch: true }); + actions.createQnAFromUrlDialogBegin({ projectId }); }} />
diff --git a/Composer/packages/client/src/pages/notifications/NotificationList.tsx b/Composer/packages/client/src/pages/notifications/NotificationList.tsx index a50bf2e88a..9aadd3b14e 100644 --- a/Composer/packages/client/src/pages/notifications/NotificationList.tsx +++ b/Composer/packages/client/src/pages/notifications/NotificationList.tsx @@ -43,6 +43,7 @@ const columns: IColumn[] = [ return ; }, }, + { key: 'NotificationType', name: formatMessage('Type'), @@ -68,6 +69,31 @@ const columns: IColumn[] = [ }, isPadded: true, }, + { + key: 'NotificationBotName', + name: formatMessage('Bot'), + className: notification.columnCell, + fieldName: 'botName', + minWidth: 70, + maxWidth: 90, + isRowHeader: true, + isResizable: true, + data: 'string', + onRender: (item: INotification) => { + return ( +
+
+ {item.botName} +
+
+ ); + }, + isPadded: true, + }, { key: 'NotificationLocation', name: formatMessage('Location'), diff --git a/Composer/packages/client/src/pages/notifications/Notifications.tsx b/Composer/packages/client/src/pages/notifications/Notifications.tsx index 89d0377eed..4057f06db6 100644 --- a/Composer/packages/client/src/pages/notifications/Notifications.tsx +++ b/Composer/packages/client/src/pages/notifications/Notifications.tsx @@ -20,13 +20,18 @@ const Notifications: React.FC> = (pro const { projectId = '' } = props; const [filter, setFilter] = useState(''); const notifications = useNotifications(projectId, filter); + const rootProjectId = projectId; + const navigations = { [NotificationType.LG]: (item: INotification) => { const { projectId, resourceId, diagnostic, dialogPath } = item; let uri = `/bot/${projectId}/language-generation/${resourceId}/edit#L=${diagnostic.range?.start.line || 0}`; //the format of item.id is lgFile#inlineTemplateId if (dialogPath) { - uri = convertPathToUrl(projectId, resourceId, dialogPath); + uri = + rootProjectId === projectId + ? convertPathToUrl(projectId, null, resourceId, dialogPath) + : convertPathToUrl(rootProjectId, projectId, resourceId, dialogPath); } navigateTo(uri); }, @@ -34,7 +39,10 @@ const Notifications: React.FC> = (pro const { projectId, resourceId, diagnostic, dialogPath } = item; let uri = `/bot/${projectId}/language-understanding/${resourceId}/edit#L=${diagnostic.range?.start.line || 0}`; if (dialogPath) { - uri = convertPathToUrl(projectId, resourceId, dialogPath); + uri = + rootProjectId === projectId + ? convertPathToUrl(projectId, null, resourceId, dialogPath) + : convertPathToUrl(rootProjectId, projectId, resourceId, dialogPath); } navigateTo(uri); }, @@ -46,8 +54,11 @@ const Notifications: React.FC> = (pro [NotificationType.DIALOG]: (item: INotification) => { //path is like main.trigers[0].actions[0] //uri = id?selected=triggers[0]&focused=triggers[0].actions[0] - const { projectId, id, dialogPath } = item; - const uri = convertPathToUrl(projectId, id, dialogPath ?? ''); + const { projectId, id, dialogPath = '' } = item; + const uri = + rootProjectId === projectId + ? convertPathToUrl(projectId, null, id, dialogPath) + : convertPathToUrl(rootProjectId, projectId, id, dialogPath); navigateTo(uri); }, [NotificationType.SKILL]: (item: INotification) => { diff --git a/Composer/packages/client/src/pages/notifications/types.ts b/Composer/packages/client/src/pages/notifications/types.ts index 1253df95c2..fbde0a273b 100644 --- a/Composer/packages/client/src/pages/notifications/types.ts +++ b/Composer/packages/client/src/pages/notifications/types.ts @@ -20,6 +20,7 @@ export enum NotificationType { export interface INotification { projectId: string; + botName: string; id: string; severity: string; type: NotificationType; @@ -32,6 +33,7 @@ export interface INotification { export abstract class Notification implements INotification { projectId: string; + botName: string; id: string; severity: string; type = NotificationType.GENERAL; @@ -40,8 +42,9 @@ export abstract class Notification implements INotification { diagnostic: Diagnostic; dialogPath?: string; resourceId: string; - constructor(projectId: string, id: string, location: string, diagnostic: Diagnostic) { + constructor(projectId: string, botName: string, id: string, location: string, diagnostic: Diagnostic) { this.projectId = projectId; + this.botName = botName; this.id = id; this.resourceId = getBaseName(id); this.severity = DiagnosticSeverity[diagnostic.severity] || ''; @@ -52,16 +55,16 @@ export abstract class Notification implements INotification { export class ServerNotification extends Notification { type = NotificationType.GENERAL; - constructor(projectId: string, id: string, location: string, diagnostic: Diagnostic) { - super(projectId, id, location, diagnostic); + constructor(projectId: string, botName: string, id: string, location: string, diagnostic: Diagnostic) { + super(projectId, botName, id, location, diagnostic); this.message = diagnostic.message; } } export class DialogNotification extends Notification { type = NotificationType.DIALOG; - constructor(projectId: string, id: string, location: string, diagnostic: Diagnostic) { - super(projectId, id, location, diagnostic); + constructor(projectId: string, botName: string, id: string, location: string, diagnostic: Diagnostic) { + super(projectId, botName, id, location, diagnostic); this.message = `In ${replaceDialogDiagnosticLabel(diagnostic.path)} ${diagnostic.message}`; this.dialogPath = diagnostic.path; } @@ -69,8 +72,8 @@ export class DialogNotification extends Notification { export class SkillNotification extends Notification { type = NotificationType.SKILL; - constructor(projectId: string, id: string, location: string, diagnostic: Diagnostic) { - super(projectId, id, location, diagnostic); + constructor(projectId: string, botName: string, id: string, location: string, diagnostic: Diagnostic) { + super(projectId, botName, id, location, diagnostic); this.message = `${replaceDialogDiagnosticLabel(diagnostic.path)} ${diagnostic.message}`; this.dialogPath = diagnostic.path; } @@ -78,8 +81,8 @@ export class SkillNotification extends Notification { export class SettingNotification extends Notification { type = NotificationType.SETTING; - constructor(projectId: string, id: string, location: string, diagnostic: Diagnostic) { - super(projectId, id, location, diagnostic); + constructor(projectId: string, botName: string, id: string, location: string, diagnostic: Diagnostic) { + super(projectId, botName, id, location, diagnostic); this.message = `${replaceDialogDiagnosticLabel(diagnostic.path)} ${diagnostic.message}`; this.dialogPath = diagnostic.path; } @@ -89,13 +92,14 @@ export class LgNotification extends Notification { type = NotificationType.LG; constructor( projectId: string, + botName: string, id: string, location: string, diagnostic: Diagnostic, lgFile: LgFile, dialogs: DialogInfo[] ) { - super(projectId, id, location, diagnostic); + super(projectId, botName, id, location, diagnostic); this.message = createSingleMessage(diagnostic); this.dialogPath = this.findDialogPath(lgFile, dialogs, diagnostic); } @@ -120,13 +124,14 @@ export class LuNotification extends Notification { type = NotificationType.LU; constructor( projectId: string, + botName: string, id: string, location: string, diagnostic: Diagnostic, luFile: LuFile, dialogs: DialogInfo[] ) { - super(projectId, id, location, diagnostic); + super(projectId, botName, id, location, diagnostic); this.dialogPath = this.findDialogPath(luFile, dialogs, diagnostic); this.message = createSingleMessage(diagnostic); } @@ -146,8 +151,8 @@ export class LuNotification extends Notification { export class QnANotification extends Notification { type = NotificationType.QNA; - constructor(projectId: string, id: string, location: string, diagnostic: Diagnostic) { - super(projectId, id, location, diagnostic); + constructor(projectId: string, botName: string, id: string, location: string, diagnostic: Diagnostic) { + super(projectId, botName, id, location, diagnostic); this.dialogPath = ''; this.message = createSingleMessage(diagnostic); } diff --git a/Composer/packages/client/src/pages/notifications/useNotifications.tsx b/Composer/packages/client/src/pages/notifications/useNotifications.tsx index 08997f0e14..365502a4e2 100644 --- a/Composer/packages/client/src/pages/notifications/useNotifications.tsx +++ b/Composer/packages/client/src/pages/notifications/useNotifications.tsx @@ -1,116 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { BotIndexer } from '@bfc/indexers'; -import { BotAssets } from '@bfc/shared'; -import get from 'lodash/get'; -import { useMemo } from 'react'; import { useRecoilValue } from 'recoil'; -import { - botDiagnosticsState, - botProjectFileState, - crossTrainConfigState, - dialogSchemasState, - formDialogSchemasSelectorFamily, - jsonSchemaFilesState, - lgFilesState, - luFilesState, - qnaFilesState, - settingsState, - skillManifestsState, - validateDialogsSelectorFamily, -} from '../../recoilModel'; -import { recognizersSelectorFamily } from '../../recoilModel/selectors/recognizers'; +import { messagersSelector } from '../../recoilModel/selectors'; -import { getReferredLuFiles } from './../../utils/luUtil'; -import { - DialogNotification, - LgNotification, - LuNotification, - Notification, - QnANotification, - ServerNotification, - SettingNotification, - SkillNotification, -} from './types'; - -export default function useNotifications(projectId: string, filter?: string) { - const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId)); - const luFiles = useRecoilValue(luFilesState(projectId)); - const lgFiles = useRecoilValue(lgFilesState(projectId)); - const diagnostics = useRecoilValue(botDiagnosticsState(projectId)); - const setting = useRecoilValue(settingsState(projectId)); - const skillManifests = useRecoilValue(skillManifestsState(projectId)); - const dialogSchemas = useRecoilValue(dialogSchemasState(projectId)); - const qnaFiles = useRecoilValue(qnaFilesState(projectId)); - const formDialogSchemas = useRecoilValue(formDialogSchemasSelectorFamily(projectId)); - const botProjectFile = useRecoilValue(botProjectFileState(projectId)); - const jsonSchemaFiles = useRecoilValue(jsonSchemaFilesState(projectId)); - const recognizers = useRecoilValue(recognizersSelectorFamily(projectId)); - const crossTrainConfig = useRecoilValue(crossTrainConfigState(projectId)); - const botAssets: BotAssets = { - projectId, - dialogs, - luFiles, - qnaFiles, - lgFiles, - skillManifests, - setting, - dialogSchemas, - formDialogSchemas, - botProjectFile, - jsonSchemaFiles, - recognizers, - crossTrainConfig, - }; - - const memoized = useMemo(() => { - const notifications: Notification[] = []; - diagnostics.forEach((d) => { - notifications.push(new ServerNotification(projectId, '', d.source, d)); - }); - const skillDiagnostics = BotIndexer.checkSkillSetting(botAssets); - skillDiagnostics.forEach((item) => { - if (item.source.endsWith('.json')) { - notifications.push(new SkillNotification(projectId, item.source, item.source, item)); - } else { - notifications.push(new DialogNotification(projectId, item.source, item.source, item)); - } - }); - const luisLocaleDiagnostics = BotIndexer.checkLUISLocales(botAssets); - - luisLocaleDiagnostics.forEach((item) => { - notifications.push(new SettingNotification(projectId, item.source, item.source, item)); - }); - - dialogs.forEach((dialog) => { - dialog.diagnostics.forEach((diagnostic) => { - const location = `${dialog.id}.dialog`; - notifications.push(new DialogNotification(projectId, dialog.id, location, diagnostic)); - }); - }); - getReferredLuFiles(luFiles, dialogs).forEach((lufile) => { - lufile.diagnostics.forEach((diagnostic) => { - const location = `${lufile.id}.lu`; - notifications.push(new LuNotification(projectId, lufile.id, location, diagnostic, lufile, dialogs)); - }); - }); - lgFiles.forEach((lgFile) => { - lgFile.diagnostics.forEach((diagnostic) => { - const location = `${lgFile.id}.lg`; - notifications.push(new LgNotification(projectId, lgFile.id, location, diagnostic, lgFile, dialogs)); - }); - }); - qnaFiles.forEach((qnaFile) => { - get(qnaFile, 'diagnostics', []).forEach((diagnostic) => { - const location = `${qnaFile.id}.qna`; - notifications.push(new QnANotification(projectId, qnaFile.id, location, diagnostic)); - }); - }); - return notifications; - }, [botAssets, diagnostics]); - - const notifications: Notification[] = filter ? memoized.filter((x) => x.severity === filter) : memoized; - return notifications; +export default function useNotifications(_projectId: string, filter?: string) { + const messagers = useRecoilValue(messagersSelector); + return filter ? messagers.filter((x) => x.severity === filter) : messagers; } diff --git a/Composer/packages/client/src/pages/publish/createPublishTarget.tsx b/Composer/packages/client/src/pages/publish/createPublishTarget.tsx index 57c00bc018..21a1e82ed2 100644 --- a/Composer/packages/client/src/pages/publish/createPublishTarget.tsx +++ b/Composer/packages/client/src/pages/publish/createPublishTarget.tsx @@ -145,10 +145,10 @@ const CreatePublishTarget: React.FC = (props) => {
Promise; // returns an id token - getAccessToken: (options: OAuthOptions) => Promise; // returns an access token + getAccessToken: (options: AuthParameters) => Promise; // returns an access token } interface PublishAPI { @@ -31,13 +32,8 @@ class API implements IAPI { constructor() { this.auth = { - login: (options: OAuthOptions) => { - const client = new OAuthClient(options); - return client.login(); - }, - getAccessToken: (options: OAuthOptions) => { - const client = new OAuthClient(options); - return client.getTokenSilently(); + getAccessToken: (params: AuthParameters) => { + return AuthClient.getAccessToken(params); }, }; this.publish = { diff --git a/Composer/packages/client/src/recoilModel/Recognizers.tsx b/Composer/packages/client/src/recoilModel/Recognizers.tsx index 051958dcb3..de9cb0a9aa 100644 --- a/Composer/packages/client/src/recoilModel/Recognizers.tsx +++ b/Composer/packages/client/src/recoilModel/Recognizers.tsx @@ -7,7 +7,7 @@ import { useRecoilState, useSetRecoilState } from 'recoil'; import { useRecoilValue } from 'recoil'; import isEqual from 'lodash/isEqual'; -import { getExtension } from '../utils/fileUtil'; +import { getBaseName, getExtension } from '../utils/fileUtil'; import * as luUtil from './../utils/luUtil'; import * as buildUtil from './../utils/buildUtil'; @@ -55,7 +55,7 @@ export const getMultiLanguagueRecognizerDialog = ( const multiLanguageRecognizer = MultiLanguageRecognizerTemplate(target, fileType); files.forEach((item) => { - if (item.empty || !item.id.startsWith(target)) return; + if (item.empty || getBaseName(item.id) !== target) return; const locale = getExtension(item.id); const fileName = `${item.id}.${fileType}`; multiLanguageRecognizer.recognizers[locale] = fileName; @@ -69,7 +69,7 @@ export const getMultiLanguagueRecognizerDialog = ( export const getLuisRecognizerDialogs = (target: string, luFiles: LuFile[]) => { return luFiles - .filter((item) => !item.empty && item.id.startsWith(target)) + .filter((item) => !item.empty && getBaseName(item.id) === target) .map((item) => ({ id: `${item.id}.lu.dialog`, content: LuisRecognizerTemplate(target, item.id) })); }; @@ -144,10 +144,11 @@ export const Recognizer = React.memo((props: { projectId: string }) => { useEffect(() => { let recognizers: RecognizerFile[] = []; dialogs + .filter((dialog) => !dialog.isFormDialog) .filter((dialog) => isCrossTrainedRecognizerSet(dialog) || isLuisRecognizer(dialog)) .forEach((dialog) => { - const filtedLus = luFiles.filter((item) => item.id.startsWith(dialog.id)); - const filtedQnas = qnaFiles.filter((item) => item.id.startsWith(dialog.id)); + const filtedLus = luFiles.filter((item) => getBaseName(item.id) === dialog.id); + const filtedQnas = qnaFiles.filter((item) => getBaseName(item.id) === dialog.id); const { isCrossTrain, luisRecognizers, diff --git a/Composer/packages/client/src/recoilModel/atoms/appState.ts b/Composer/packages/client/src/recoilModel/atoms/appState.ts index a409ccbd7b..6760acd45a 100644 --- a/Composer/packages/client/src/recoilModel/atoms/appState.ts +++ b/Composer/packages/client/src/recoilModel/atoms/appState.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import { atom, atomFamily } from 'recoil'; -import { FormDialogSchemaTemplate, FeatureFlagMap, ProjectTemplate, UserSettings } from '@bfc/shared'; +import { FormDialogSchemaTemplate, FeatureFlagMap, BotTemplate, UserSettings } from '@bfc/shared'; import { ExtensionMetadata } from '@bfc/extension-client'; import { @@ -52,7 +52,7 @@ export const recentProjectsState = atom({ default: [], }); -export const templateProjectsState = atom({ +export const templateProjectsState = atom({ key: getFullyQualifiedKey('templateProjects'), default: [], }); @@ -221,3 +221,13 @@ export const formDialogGenerationProgressingState = atom({ key: getFullyQualifiedKey('formDialogGenerationProgressing'), default: false, }); + +export const pageElementState = atom<{ [page in PageMode]?: { [key: string]: any } }>({ + key: getFullyQualifiedKey('pageElement'), + default: { + design: {}, + lg: {}, + lu: {}, + qna: {}, + }, +}); diff --git a/Composer/packages/client/src/recoilModel/atoms/botState.ts b/Composer/packages/client/src/recoilModel/atoms/botState.ts index cfb2c95555..0df22daf4d 100644 --- a/Composer/packages/client/src/recoilModel/atoms/botState.ts +++ b/Composer/packages/client/src/recoilModel/atoms/botState.ts @@ -24,7 +24,7 @@ import { BotLoadError, DesignPageLocation } from '../../recoilModel/types'; import FilePersistence from '../persistence/FilePersistence'; import { BotStatus } from './../../constants'; -import { BreadcrumbItem, PublishType } from './../../recoilModel/types'; +import { PublishType } from './../../recoilModel/types'; const getFullyQualifiedKey = (value: string) => { return `Bot_${value}_State`; @@ -45,6 +45,7 @@ const emptyDialog: DialogInfo = { triggers: [], intentTriggers: [], skills: [], + isFormDialog: false, }; type dialogStateParams = { projectId: string; dialogId: string }; export const dialogState = atomFamily({ @@ -179,13 +180,6 @@ export const skillManifestsState = atomFamily({ }, }); -export const breadcrumbState = atomFamily({ - key: getFullyQualifiedKey('breadcrumb'), - default: (id) => { - return []; - }, -}); - export const showCreateDialogModalState = atomFamily({ key: getFullyQualifiedKey('showCreateDialogModal'), default: (id) => { @@ -294,11 +288,6 @@ export const showCreateQnAFromUrlDialogState = atomFamily({ default: false, }); -export const showCreateQnAFromUrlDialogWithScratchState = atomFamily({ - key: getFullyQualifiedKey('showCreateQnAFromUrlDialogWithScratch'), - default: false, -}); - export const showCreateQnAFromScratchDialogState = atomFamily({ key: getFullyQualifiedKey('showCreateQnAFromScratchDialog'), default: false, @@ -365,3 +354,13 @@ export const botNameIdentifierState = atomFamily({ key: getFullyQualifiedKey('botNameIdentifier'), default: '', }); + +export const canUndoState = atomFamily({ + key: getFullyQualifiedKey('canUndoState'), + default: false, +}); + +export const canRedoState = atomFamily({ + key: getFullyQualifiedKey('canRedoState'), + default: false, +}); diff --git a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx index 9661ff113f..1ae9e0ca06 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx +++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx @@ -7,20 +7,12 @@ import { SDKKinds } from '@bfc/shared'; import { navigationDispatcher } from '../navigation'; import { renderRecoilHook } from '../../../../__tests__/testUtils'; -import { focusPathState, breadcrumbState, designPageLocationState } from '../../atoms/botState'; +import { focusPathState, designPageLocationState } from '../../atoms/botState'; import { dialogsSelectorFamily } from '../../selectors'; import { dispatcherState } from '../../../recoilModel/DispatcherWrapper'; import { Dispatcher } from '../../../recoilModel/dispatchers'; -import { - convertPathToUrl, - navigateTo, - checkUrl, - updateBreadcrumb, - getUrlSearch, - BreadcrumbUpdateType, -} from '../../../utils/navigation'; +import { convertPathToUrl, navigateTo, checkUrl, getUrlSearch } from '../../../utils/navigation'; import { createSelectedPath, getSelected } from '../../../utils/dialogUtil'; -import { BreadcrumbItem } from '../../../recoilModel/types'; import { currentProjectIdState, botProjectIdsState, botProjectFileState, projectMetaDataState } from '../../atoms'; jest.mock('../../../utils/navigation'); @@ -29,7 +21,6 @@ jest.mock('../../../utils/dialogUtil'); const mockCheckUrl = checkUrl as jest.Mock; const mockNavigateTo = navigateTo as jest.Mock; const mockGetSelected = getSelected as jest.Mock; -const mockUpdateBreadcrumb = updateBreadcrumb as jest.Mock; const mockGetUrlSearch = getUrlSearch as jest.Mock; const mockConvertPathToUrl = convertPathToUrl as jest.Mock; const mockCreateSelectedPath = createSelectedPath as jest.Mock; @@ -37,8 +28,8 @@ const mockCreateSelectedPath = createSelectedPath as jest.Mock; const projectId = '12345.678'; const skillId = '98765.4321'; -function expectNavTo(location: string, state: {} | null = null) { - expect(mockNavigateTo).toHaveBeenLastCalledWith(location, state == null ? expect.anything() : state); +function expectNavTo(location: string) { + expect(mockNavigateTo).toHaveBeenLastCalledWith(location); } describe('navigation dispatcher', () => { @@ -46,7 +37,6 @@ describe('navigation dispatcher', () => { beforeEach(() => { mockCheckUrl.mockClear(); mockNavigateTo.mockClear(); - mockUpdateBreadcrumb.mockReturnValue([]); mockConvertPathToUrl.mockClear(); mockCreateSelectedPath.mockClear(); @@ -54,7 +44,6 @@ describe('navigation dispatcher', () => { const useRecoilTestHook = () => { const focusPath = useRecoilValue(focusPathState(projectId)); - const breadcrumb = useRecoilValue(breadcrumbState(projectId)); const designPageLocation = useRecoilValue(designPageLocationState(projectId)); const dialogs = useRecoilValue(dialogsSelectorFamily(projectId)); const currentDispatcher = useRecoilValue(dispatcherState); @@ -62,7 +51,6 @@ describe('navigation dispatcher', () => { return { dialogs, focusPath, - breadcrumb, designPageLocation, projectId, currentDispatcher, @@ -72,7 +60,6 @@ describe('navigation dispatcher', () => { const { result } = renderRecoilHook(useRecoilTestHook, { states: [ { recoilState: focusPathState(projectId), initialValue: 'path' }, - { recoilState: breadcrumbState(projectId), initialValue: [{ dialogId: '100', selected: 'a', focused: 'b' }] }, { recoilState: designPageLocationState(projectId), initialValue: { @@ -124,17 +111,11 @@ describe('navigation dispatcher', () => { await act(async () => { await dispatcher.setDesignPageLocation(projectId, { dialogId: 'dialogId', - breadcrumb: [], promptTab: undefined, }); }); expect(renderedComponent.current.focusPath).toEqual('dialogId#'); - expect(renderedComponent.current.breadcrumb).toHaveLength(1); - expect(renderedComponent.current.breadcrumb[0]).toEqual({ - dialogId: 'dialogId', - focused: '', - selected: '', - }); + expect(renderedComponent.current.designPageLocation).toEqual({ dialogId: 'dialogId', promptTab: undefined, @@ -147,18 +128,12 @@ describe('navigation dispatcher', () => { await act(async () => { await dispatcher.setDesignPageLocation(projectId, { dialogId: 'dialogId', - breadcrumb: [], selected: 'select', promptTab: undefined, }); }); expect(renderedComponent.current.focusPath).toEqual('dialogId#.select'); - expect(renderedComponent.current.breadcrumb).toHaveLength(1); - expect(renderedComponent.current.breadcrumb[0]).toEqual({ - dialogId: 'dialogId', - focused: '', - selected: 'select', - }); + expect(renderedComponent.current.designPageLocation).toEqual({ dialogId: 'dialogId', promptTab: undefined, @@ -171,19 +146,13 @@ describe('navigation dispatcher', () => { await act(async () => { await dispatcher.setDesignPageLocation(projectId, { dialogId: 'dialogId', - breadcrumb: [], focused: 'focus', selected: 'select', promptTab: undefined, }); }); expect(renderedComponent.current.focusPath).toEqual('dialogId#.focus'); - expect(renderedComponent.current.breadcrumb).toHaveLength(1); - expect(renderedComponent.current.breadcrumb[0]).toEqual({ - dialogId: 'dialogId', - focused: 'focus', - selected: 'select', - }); + expect(renderedComponent.current.designPageLocation).toEqual({ dialogId: 'dialogId', promptTab: undefined, @@ -197,7 +166,7 @@ describe('navigation dispatcher', () => { it('navigates to a destination', async () => { mockConvertPathToUrl.mockReturnValue(`/bot/${projectId}/dialogs/dialogId`); await act(async () => { - await dispatcher.navTo(projectId, 'dialogId', []); + await dispatcher.navTo(projectId, 'dialogId'); }); expectNavTo(`/bot/${projectId}/dialogs/dialogId`); expect(mockConvertPathToUrl).toBeCalledWith(projectId, projectId, 'dialogId'); @@ -206,7 +175,7 @@ describe('navigation dispatcher', () => { it("doesn't navigate to a destination where we already are", async () => { mockCheckUrl.mockReturnValue(true); await act(async () => { - await dispatcher.navTo(projectId, 'dialogId', []); + await dispatcher.navTo(projectId, 'dialogId'); }); expect(mockNavigateTo).not.toBeCalled(); }); @@ -261,8 +230,6 @@ describe('navigation dispatcher', () => { await dispatcher.focusTo(projectId, null, 'focus', ''); }); expectNavTo(`/bot/${projectId}/dialogs/dialogId?selected=select&focused=focus`); - expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Selected); - expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Focused); }); it('goes to a focused page with skill', async () => { @@ -271,8 +238,6 @@ describe('navigation dispatcher', () => { await dispatcher.focusTo(projectId, skillId, 'focus', ''); }); expectNavTo(`/bot/${projectId}/skill/${skillId}/dialogs/dialogInSkillId?selected=select&focused=focus`); - expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Selected); - expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Focused); }); it('goes to a focused page with fragment', async () => { @@ -281,8 +246,6 @@ describe('navigation dispatcher', () => { await dispatcher.focusTo(projectId, null, 'focus', 'fragment'); }); expectNavTo(`/bot/${projectId}/dialogs/dialogId?selected=select&focused=focus#fragment`); - expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Selected); - expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Focused); }); it('goes to a focused page with skill and fragment', async () => { @@ -291,8 +254,6 @@ describe('navigation dispatcher', () => { await dispatcher.focusTo(projectId, skillId, 'focus', 'fragment'); }); expectNavTo(`/bot/${projectId}/skill/${skillId}/dialogs/dialogInSkillId?selected=select&focused=focus#fragment`); - expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Selected); - expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Focused); }); it('stays on the same page but updates breadcrumbs with a checked URL', async () => { @@ -302,8 +263,6 @@ describe('navigation dispatcher', () => { await dispatcher.focusTo(projectId, null, 'focus', 'fragment'); }); expect(mockNavigateTo).not.toBeCalled(); - expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Selected); - expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Focused); }); }); diff --git a/Composer/packages/client/src/recoilModel/dispatchers/application.ts b/Composer/packages/client/src/recoilModel/dispatchers/application.ts index 9bd22e28f7..aab3bf32be 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/application.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/application.ts @@ -12,6 +12,7 @@ import { creationFlowStatusState, currentModeState, PageMode, + pageElementState, } from '../atoms/appState'; import { AppUpdaterStatus, CreationFlowStatus } from '../../constants'; import OnboardingState from '../../utils/onboardingStorage'; @@ -79,6 +80,13 @@ export const applicationDispatcher = () => { set(currentModeState, mode); }); + const setPageElementState = useRecoilCallback(({ set }: CallbackInterface) => (mode: PageMode, settings: {}) => { + set(pageElementState, (currentElementState) => ({ + ...currentElementState, + [mode]: settings, + })); + }); + const onboardingAddCoachMarkRef = useRecoilCallback( ({ set }: CallbackInterface) => (coachMarkRef: { [key: string]: any }) => { set(onboardingState, (onboardingObj) => ({ @@ -120,5 +128,6 @@ export const applicationDispatcher = () => { setCreationFlowStatus, setApplicationLevelError, setCurrentPageMode, + setPageElementState, }; }; diff --git a/Composer/packages/client/src/recoilModel/dispatchers/formDialogs.ts b/Composer/packages/client/src/recoilModel/dispatchers/formDialogs.ts index 65830803ab..5c6625bd00 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/formDialogs.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/formDialogs.ts @@ -110,8 +110,9 @@ export const formDialogsDispatcher = () => { } ); - const navigateToGeneratedDialog = ({ projectId, schemaId }) => { - navigate(`/bot/${projectId}/dialogs/${schemaId}`); + const navigateToGeneratedDialog = ({ projectId, skillId, schemaId }) => { + skillId = skillId || projectId; + navigate(`/bot/${projectId}/skill/${skillId}/dialogs/${schemaId}`); }; const navigateToFormDialogSchema = ({ projectId, schemaId }) => { diff --git a/Composer/packages/client/src/recoilModel/dispatchers/lg.ts b/Composer/packages/client/src/recoilModel/dispatchers/lg.ts index 95107d37fa..04a0fa7594 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/lg.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/lg.ts @@ -64,10 +64,20 @@ export const updateLgFileState = async (projectId: string, lgFiles: LgFile[], up } } - return lgFiles.map((file) => { + const newLgFiles = lgFiles.map((file) => { const changedFile = changes.find(({ id }) => id === file.id); return changedFile ? changedFile : file; }); + + if (dialogId !== 'common') return newLgFiles; + + // if changes happen on common.lg, re-parse all. + const reparsedNewLgFiles: LgFile[] = []; + for (const file of newLgFiles) { + const reparsedFile = (await LgWorker.parse(projectId, file.id, file.content, newLgFiles)) as LgFile; + reparsedNewLgFiles.push(reparsedFile); + } + return reparsedNewLgFiles; }; // when do create, passed id do not carried with locale @@ -198,6 +208,8 @@ export const lgDispatcher = () => { return; } + let newLgFiles: LgFile[] = []; + try { if (template.name !== templateName) { // name change, need update cross multi locale file. @@ -214,11 +226,9 @@ export const lgDispatcher = () => { changes.push(updatedFile); } - set(lgFilesState(projectId), (lgFiles) => { - return lgFiles.map((file) => { - const changedFile = changes.find(({ id }) => id === file.id); - return changedFile ? changedFile : file; - }); + newLgFiles = lgFiles.map((file) => { + const changedFile = changes.find(({ id }) => id === file.id); + return changedFile ? changedFile : file; }); } else { // body change, only update current locale file @@ -230,15 +240,27 @@ export const lgDispatcher = () => { lgFiles )) as LgFile; - set(lgFilesState(projectId), (lgFiles) => { - return lgFiles.map((file) => { - return file.id === id ? updatedFile : file; - }); + newLgFiles = lgFiles.map((file) => { + return file.id === id ? updatedFile : file; }); } } catch (error) { setError(callbackHelpers, error); + return; + } + + if (getBaseName(lgFile.id) !== 'common') { + set(lgFilesState(projectId), newLgFiles); + return; + } + + // if changes happen on common.lg, re-parse all. + const reparsedNewLgFiles: LgFile[] = []; + for (const file of newLgFiles) { + const reparsedFile = (await LgWorker.parse(projectId, file.id, file.content, newLgFiles)) as LgFile; + reparsedNewLgFiles.push(reparsedFile); } + set(lgFilesState(projectId), reparsedNewLgFiles); } ); diff --git a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts index e18df80e4d..5d4ba44d91 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts @@ -11,23 +11,12 @@ import { encodeArrayPathToDesignerPath } from '../../utils/convertUtils/designer import { dialogsSelectorFamily, rootBotProjectIdSelector } from '../selectors'; import { getSelected } from './../../utils/dialogUtil'; -import { BreadcrumbItem } from './../../recoilModel/types'; -import { breadcrumbState, designPageLocationState, focusPathState } from './../atoms/botState'; -import { - BreadcrumbUpdateType, - checkUrl, - convertPathToUrl, - getUrlSearch, - navigateTo, - updateBreadcrumb, -} from './../../utils/navigation'; +import { designPageLocationState, focusPathState } from './../atoms/botState'; +import { checkUrl, convertPathToUrl, getUrlSearch, navigateTo } from './../../utils/navigation'; export const navigationDispatcher = () => { const setDesignPageLocation = useRecoilCallback( - ({ set }: CallbackInterface) => async ( - projectId: string, - { dialogId = '', selected = '', focused = '', breadcrumb = [], promptTab } - ) => { + ({ set }: CallbackInterface) => (projectId: string, { dialogId = '', selected = '', focused = '', promptTab }) => { let focusPath = dialogId + '#'; if (focused) { focusPath = dialogId + '#.' + focused; @@ -36,8 +25,6 @@ export const navigationDispatcher = () => { } set(currentProjectIdState, projectId); set(focusPathState(projectId), focusPath); - //add current path to the breadcrumb - set(breadcrumbState(projectId), [...breadcrumb, { dialogId, selected, focused }]); set(designPageLocationState(projectId), { dialogId, selected, @@ -51,7 +38,7 @@ export const navigationDispatcher = () => { ({ snapshot, set }: CallbackInterface) => async ( skillId: string | null, dialogId: string | null, - breadcrumb: BreadcrumbItem[] = [] + trigger?: string ) => { const rootBotProjectId = await snapshot.getPromise(rootBotProjectIdSelector); if (rootBotProjectId == null) return; @@ -61,10 +48,17 @@ export const navigationDispatcher = () => { const designPageLocation = await snapshot.getPromise(designPageLocationState(projectId)); set(currentProjectIdState, projectId); - const currentUri = convertPathToUrl(rootBotProjectId, projectId, dialogId); + const currentUri = + trigger == null + ? convertPathToUrl(rootBotProjectId, skillId, dialogId) + : convertPathToUrl(rootBotProjectId, skillId, dialogId, `selected=triggers[${trigger}]`); if (checkUrl(currentUri, rootBotProjectId, projectId, designPageLocation)) return; - - navigateTo(currentUri, { state: { breadcrumb } }); + set(designPageLocationState(projectId), { + dialogId: dialogId ?? '', + selected: trigger ?? '', + focused: '', + }); + navigateTo(currentUri); } ); @@ -82,7 +76,6 @@ export const navigationDispatcher = () => { set(currentProjectIdState, projectId); const designPageLocation = await snapshot.getPromise(designPageLocationState(projectId)); - const breadcrumb = await snapshot.getPromise(breadcrumbState(projectId)); // target dialogId, projectId maybe empty string "" const dialogId = destinationDialogId ?? designPageLocation.dialogId ?? 'Main'; @@ -93,7 +86,12 @@ export const navigationDispatcher = () => { const currentUri = convertPathToUrl(rootBotProjectId, skillId, dialogId, encodedSelectPath); if (checkUrl(currentUri, rootBotProjectId, skillId, designPageLocation)) return; - navigateTo(currentUri, { state: { breadcrumb: updateBreadcrumb(breadcrumb, BreadcrumbUpdateType.Selected) } }); + set(designPageLocationState(projectId), { + dialogId, + selected: selectPath, + focused: '', + }); + navigateTo(currentUri); } ); @@ -106,12 +104,10 @@ export const navigationDispatcher = () => { ) => { set(currentProjectIdState, skillId ?? projectId); const designPageLocation = await snapshot.getPromise(designPageLocationState(skillId ?? projectId)); - const breadcrumb = await snapshot.getPromise(breadcrumbState(skillId ?? projectId)); - let updatedBreadcrumb = [...breadcrumb]; const { dialogId, selected } = designPageLocation; let currentUri = - skillId == null + skillId == null || skillId === projectId ? `/bot/${projectId}/dialogs/${dialogId}` : `/bot/${projectId}/skill/${skillId}/dialogs/${dialogId}`; @@ -121,22 +117,24 @@ export const navigationDispatcher = () => { const encodedFocusPath = encodeArrayPathToDesignerPath(currentDialog?.content, focusPath); const targetSelected = getSelected(encodedFocusPath); - if (targetSelected !== selected) { - updatedBreadcrumb = updateBreadcrumb(breadcrumb, BreadcrumbUpdateType.Selected); - updatedBreadcrumb.push({ dialogId, selected: targetSelected, focused: '' }); - } + currentUri = `${currentUri}?selected=${targetSelected}&focused=${encodedFocusPath}`; - updatedBreadcrumb = updateBreadcrumb(breadcrumb, BreadcrumbUpdateType.Focused); } else { currentUri = `${currentUri}?selected=${selected}`; - updatedBreadcrumb = updateBreadcrumb(breadcrumb, BreadcrumbUpdateType.Selected); } if (fragment && typeof fragment === 'string') { currentUri += `#${fragment}`; } if (checkUrl(currentUri, projectId, skillId, designPageLocation)) return; - navigateTo(currentUri, { state: { breadcrumb: updatedBreadcrumb } }); + + set(designPageLocationState(projectId), { + dialogId, + selected: getSelected(focusPath) || selected, + focused: focusPath ?? '', + promptTab: Object.values(PromptTab).find((value) => fragment === value), + }); + navigateTo(currentUri); } ); @@ -146,8 +144,7 @@ export const navigationDispatcher = () => { skillId: string | null, dialogId: string, selectPath: string, - focusPath: string, - breadcrumb: BreadcrumbItem[] = [] + focusPath: string ) => { set(currentProjectIdState, projectId); @@ -159,14 +156,14 @@ export const navigationDispatcher = () => { const designPageLocation = await snapshot.getPromise(designPageLocationState(projectId)); if (search) { const currentUri = - skillId == null + skillId == null || skillId === projectId ? `/bot/${projectId}/dialogs/${dialogId}${search}` : `/bot/${projectId}/skill/${skillId}/dialogs/${dialogId}${search}`; if (checkUrl(currentUri, projectId, skillId, designPageLocation)) return; - navigateTo(currentUri, { state: { breadcrumb } }); + navigateTo(currentUri); } else { - navTo(skillId ?? projectId, dialogId, breadcrumb); + navTo(skillId ?? projectId, dialogId); } } ); diff --git a/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts b/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts index e4ec8f5a2e..96f342d7c7 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts @@ -68,6 +68,7 @@ export const publisherDispatcher = () => { }; const updatePublishStatus = ({ set }: CallbackInterface, projectId: string, target: any, data: any) => { + if (data == null) return; const { endpointURL, status, id } = data; // 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. @@ -173,9 +174,9 @@ export const publisherDispatcher = () => { (callbackHelpers: CallbackInterface) => async (projectId: string, target: any) => { try { const response = await httpClient.get(`/publish/${projectId}/status/${target.name}`); - updatePublishStatus(callbackHelpers, projectId, target, response.data); + updatePublishStatus(callbackHelpers, projectId, target, response?.data); } catch (err) { - updatePublishStatus(callbackHelpers, projectId, target, err.response.data); + updatePublishStatus(callbackHelpers, projectId, target, err.response?.data); } } ); diff --git a/Composer/packages/client/src/recoilModel/dispatchers/qna.ts b/Composer/packages/client/src/recoilModel/dispatchers/qna.ts index 0204271568..ee42f56b98 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/qna.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/qna.ts @@ -12,7 +12,6 @@ import { settingsState, showCreateQnAFromScratchDialogState, showCreateQnAFromUrlDialogState, - showCreateQnAFromUrlDialogWithScratchState, onCreateQnAFromScratchDialogCompleteState, onCreateQnAFromUrlDialogCompleteState, } from '../atoms/botState'; @@ -203,18 +202,11 @@ export const qnaDispatcher = () => { ({ set }: CallbackInterface) => async ({ onComplete, projectId, - showFromScratch, }: { onComplete?: () => void; - showFromScratch: boolean; projectId: string; }) => { set(showCreateQnAFromUrlDialogState(projectId), true); - if (showFromScratch) { - set(showCreateQnAFromUrlDialogWithScratchState(projectId), true); - } else { - set(showCreateQnAFromUrlDialogWithScratchState(projectId), false); - } set(onCreateQnAFromUrlDialogCompleteState(projectId), { func: onComplete }); } ); diff --git a/Composer/packages/client/src/recoilModel/selectors/index.ts b/Composer/packages/client/src/recoilModel/selectors/index.ts index 062b4fdbe0..37b4503a74 100644 --- a/Composer/packages/client/src/recoilModel/selectors/index.ts +++ b/Composer/packages/client/src/recoilModel/selectors/index.ts @@ -8,3 +8,4 @@ export * from './validatedDialogs'; export * from './dialogs'; export * from './dialogImports'; export * from './projectTemplates'; +export * from './messagers'; diff --git a/Composer/packages/client/src/recoilModel/selectors/messagers.ts b/Composer/packages/client/src/recoilModel/selectors/messagers.ts new file mode 100644 index 0000000000..50a97e00d4 --- /dev/null +++ b/Composer/packages/client/src/recoilModel/selectors/messagers.ts @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +import { selector } from 'recoil'; +import { BotIndexer } from '@bfc/indexers'; +import { BotAssets } from '@bfc/shared'; + +import { + botDiagnosticsState, + botProjectFileState, + botProjectSpaceSelector, + crossTrainConfigState, + dialogSchemasState, + formDialogSchemasSelectorFamily, + jsonSchemaFilesState, + lgFilesState, + luFilesState, + qnaFilesState, + settingsState, + skillManifestsState, + validateDialogsSelectorFamily, +} from '../../recoilModel'; +import { recognizersSelectorFamily } from '../../recoilModel/selectors/recognizers'; +import { + DialogNotification, + LgNotification, + LuNotification, + Notification, + QnANotification, + ServerNotification, + SettingNotification, + SkillNotification, +} from '../../pages/notifications/types'; + +import { getReferredLuFiles } from './../../utils/luUtil'; + +export const messagersSelector = selector({ + key: 'messagersSelector', + get: ({ get }) => { + const botProjectSpace = get(botProjectSpaceSelector); + const allMessage: Notification[] = []; + + for (const project of botProjectSpace) { + const { projectId, name } = project; + const dialogs = get(validateDialogsSelectorFamily(projectId)); + const luFiles = get(luFilesState(projectId)); + const lgFiles = get(lgFilesState(projectId)); + const diagnostics = get(botDiagnosticsState(projectId)); + const setting = get(settingsState(projectId)); + const skillManifests = get(skillManifestsState(projectId)); + const dialogSchemas = get(dialogSchemasState(projectId)); + const qnaFiles = get(qnaFilesState(projectId)); + const formDialogSchemas = get(formDialogSchemasSelectorFamily(projectId)); + const botProjectFile = get(botProjectFileState(projectId)); + const jsonSchemaFiles = get(jsonSchemaFilesState(projectId)); + const recognizers = get(recognizersSelectorFamily(projectId)); + const crossTrainConfig = get(crossTrainConfigState(projectId)); + const botAssets: BotAssets = { + projectId, + dialogs, + luFiles, + qnaFiles, + lgFiles, + skillManifests, + setting, + dialogSchemas, + formDialogSchemas, + botProjectFile, + jsonSchemaFiles, + recognizers, + crossTrainConfig, + }; + + const notifications: Notification[] = []; + diagnostics.forEach((d) => { + notifications.push(new ServerNotification(projectId, name, '', d.source, d)); + }); + const skillDiagnostics = BotIndexer.checkSkillSetting(botAssets); + skillDiagnostics.forEach((item) => { + if (item.source.endsWith('.json')) { + notifications.push(new SkillNotification(projectId, name, item.source, item.source, item)); + } else { + notifications.push(new DialogNotification(projectId, name, item.source, item.source, item)); + } + }); + const luisLocaleDiagnostics = BotIndexer.checkLUISLocales(botAssets); + + luisLocaleDiagnostics.forEach((item) => { + notifications.push(new SettingNotification(projectId, name, item.source, item.source, item)); + }); + + dialogs.forEach((dialog) => { + dialog.diagnostics.forEach((diagnostic) => { + const location = `${dialog.id}.dialog`; + notifications.push(new DialogNotification(projectId, name, dialog.id, location, diagnostic)); + }); + }); + getReferredLuFiles(luFiles, dialogs).forEach((lufile) => { + lufile.diagnostics.forEach((diagnostic) => { + const location = `${lufile.id}.lu`; + notifications.push(new LuNotification(projectId, name, lufile.id, location, diagnostic, lufile, dialogs)); + }); + }); + lgFiles.forEach((lgFile) => { + lgFile.diagnostics.forEach((diagnostic) => { + const location = `${lgFile.id}.lg`; + notifications.push(new LgNotification(projectId, name, lgFile.id, location, diagnostic, lgFile, dialogs)); + }); + }); + qnaFiles.forEach((qnaFile) => { + qnaFile.diagnostics.forEach((diagnostic) => { + const location = `${qnaFile.id}.qna`; + notifications.push(new QnANotification(projectId, name, qnaFile.id, location, diagnostic)); + }); + }); + allMessage.push(...notifications); + } + + return allMessage; + }, +}); diff --git a/Composer/packages/client/src/recoilModel/selectors/projectTemplates.ts b/Composer/packages/client/src/recoilModel/selectors/projectTemplates.ts index d3b8a8124d..c3f7cfffa5 100644 --- a/Composer/packages/client/src/recoilModel/selectors/projectTemplates.ts +++ b/Composer/packages/client/src/recoilModel/selectors/projectTemplates.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { BotTemplate } from '@bfc/shared'; import { selector } from 'recoil'; import { featureFlagsState, templateProjectsState } from '../atoms/appState'; @@ -11,13 +12,20 @@ export const filteredTemplatesSelector = selector({ const templates = get(templateProjectsState); const featureFlags = get(featureFlagsState); - const filteredTemplates = [...templates]; + let filteredTemplates = [...templates]; if (!featureFlags?.VA_CREATION?.enabled) { const vaTemplateIndex = filteredTemplates.findIndex((template) => template.id === 'va-core'); if (vaTemplateIndex !== -1) { filteredTemplates.splice(vaTemplateIndex, 1); } } + if (!featureFlags?.REMOTE_TEMPLATE_CREATION_EXPERIENCE?.enabled) { + filteredTemplates = filteredTemplates.filter((template: BotTemplate) => { + if (template.path) { + return template; + } + }); + } return filteredTemplates; }, }); diff --git a/Composer/packages/client/src/recoilModel/selectors/undo.ts b/Composer/packages/client/src/recoilModel/selectors/undo.ts new file mode 100644 index 0000000000..f031ca1c3e --- /dev/null +++ b/Composer/packages/client/src/recoilModel/selectors/undo.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +import { selectorFamily } from 'recoil'; + +import { canRedoState, canUndoState } from '../atoms/botState'; + +export const undoStatusSelectorFamily = selectorFamily<[boolean, boolean], string>({ + key: 'undoStatus', + get: (projectId: string) => ({ get }) => { + const canUndo = get(canUndoState(projectId)); + const canRedo = get(canRedoState(projectId)); + return [canUndo, canRedo]; + }, +}); diff --git a/Composer/packages/client/src/recoilModel/types.ts b/Composer/packages/client/src/recoilModel/types.ts index 453eca41b0..b22608fec3 100644 --- a/Composer/packages/client/src/recoilModel/types.ts +++ b/Composer/packages/client/src/recoilModel/types.ts @@ -76,13 +76,6 @@ export interface AppUpdateState { version?: string; } -export interface BreadcrumbItem { - skillId?: string; - dialogId: string; - selected: string; - focused: string; -} - export type dialogPayload = { id: string; content: any; @@ -94,7 +87,7 @@ export type DesignPageLocationPayload = { dialogId: string; selected: string; focused: string; - breadcrumb: BreadcrumbItem[]; + breadcrumb: string[]; promptTab?: string; }; diff --git a/Composer/packages/client/src/recoilModel/undo/__test__/history.test.tsx b/Composer/packages/client/src/recoilModel/undo/__test__/history.test.tsx index 5d561b06bf..aee175dc76 100644 --- a/Composer/packages/client/src/recoilModel/undo/__test__/history.test.tsx +++ b/Composer/packages/client/src/recoilModel/undo/__test__/history.test.tsx @@ -13,10 +13,14 @@ import { projectMetaDataState, currentProjectIdState, botProjectIdsState, + designPageLocationState, + canUndoState, + canRedoState, } from '../../atoms'; import { dialogsSelectorFamily } from '../../selectors'; import { renderRecoilHook } from '../../../../__tests__/testUtils/react-recoil-hooks-testing-library'; import UndoHistory from '../undoHistory'; +import { undoStatusSelectorFamily } from '../../selectors/undo'; const projectId = '123-asd'; export const UndoRedoWrapper = () => { @@ -30,11 +34,12 @@ describe('', () => { beforeEach(() => { const useRecoilTestHook = () => { - const { undo, redo, canRedo, canUndo, commitChanges, clearUndo } = useRecoilValue(undoFunctionState(projectId)); + const { undo, redo, commitChanges, clearUndo } = useRecoilValue(undoFunctionState(projectId)); const [dialogs, setDialogs] = useRecoilState(dialogsSelectorFamily(projectId)); const setProjectIdState = useSetRecoilState(currentProjectIdState); + const setDesignPageLocation = useSetRecoilState(designPageLocationState(projectId)); const history = useRecoilValue(undoHistoryState(projectId)); - + const [canUndo, canRedo] = useRecoilValue(undoStatusSelectorFamily(projectId)); return { undo, redo, @@ -46,6 +51,7 @@ describe('', () => { setDialogs, dialogs, history, + setDesignPageLocation, }; }; @@ -60,18 +66,19 @@ describe('', () => { }, states: [ { recoilState: botProjectIdsState, initialValue: [projectId] }, - { recoilState: dialogsSelectorFamily(projectId), initialValue: [{ id: '1' }] }, + { recoilState: dialogsSelectorFamily(projectId), initialValue: [{ id: '1', content: '' }] }, { recoilState: lgFilesState(projectId), initialValue: [{ id: '1.lg' }, { id: '2' }] }, { recoilState: luFilesState(projectId), initialValue: [{ id: '1.lu' }, { id: '2' }] }, { recoilState: currentProjectIdState, initialValue: projectId }, { recoilState: undoHistoryState(projectId), initialValue: new UndoHistory(projectId) }, + { recoilState: canUndoState(projectId), initialValue: false }, + { recoilState: canRedoState(projectId), initialValue: false }, + { recoilState: designPageLocationState(projectId), initialValue: { dialogId: '1', focused: '', selected: '' } }, { recoilState: undoFunctionState(projectId), initialValue: { undo: jest.fn(), redo: jest.fn(), - canUndo: jest.fn(), - canRedo: jest.fn(), commitChanges: jest.fn(), clearUndo: jest.fn(), }, @@ -107,14 +114,14 @@ describe('', () => { renderedComponent.current.commitChanges(); }); - expect(renderedComponent.current.canUndo()).toBeTruthy(); + expect(renderedComponent.current.canUndo).toBeTruthy(); act(() => { renderedComponent.current.undo(); }); expect(renderedComponent.current.history.stack.length).toBe(2); - expect(renderedComponent.current.dialogs).toStrictEqual([{ id: '1' }]); - expect(renderedComponent.current.canRedo()).toBeTruthy(); + expect(renderedComponent.current.dialogs).toStrictEqual([{ id: '1', content: '' }]); + expect(renderedComponent.current.canRedo).toBeTruthy(); }); it('should remove the items from present when commit a new change', () => { @@ -132,24 +139,30 @@ describe('', () => { it('should redo', () => { act(() => { - renderedComponent.current.setDialogs([{ id: '2' }]); + renderedComponent.current.setDialogs([{ id: '2', content: '' }]); + }); + + act(() => { + renderedComponent.current.setDesignPageLocation({ dialogId: '2' }); }); + act(() => { renderedComponent.current.commitChanges(); }); - expect(renderedComponent.current.canRedo()).toBeFalsy(); + expect(renderedComponent.current.canRedo).toBeFalsy(); act(() => { renderedComponent.current.undo(); }); - expect(renderedComponent.current.dialogs).toStrictEqual([{ id: '1' }]); - expect(renderedComponent.current.canRedo()).toBeTruthy(); + + expect(renderedComponent.current.dialogs).toStrictEqual([{ id: '1', content: '' }]); + expect(renderedComponent.current.canRedo).toBeTruthy(); act(() => { renderedComponent.current.redo(); }); expect(renderedComponent.current.history.stack.length).toBe(2); - expect(renderedComponent.current.dialogs).toStrictEqual([{ id: '2' }]); + expect(renderedComponent.current.dialogs).toStrictEqual([{ id: '2', content: '' }]); }); it('should clear undo history', () => { diff --git a/Composer/packages/client/src/recoilModel/undo/history.ts b/Composer/packages/client/src/recoilModel/undo/history.ts index 1a431b8212..3784024bc3 100644 --- a/Composer/packages/client/src/recoilModel/undo/history.ts +++ b/Composer/packages/client/src/recoilModel/undo/history.ts @@ -12,17 +12,17 @@ import uniqueId from 'lodash/uniqueId'; import isEmpty from 'lodash/isEmpty'; import { navigateTo, getUrlSearch } from '../../utils/navigation'; +import { encodeArrayPathToDesignerPath } from '../../utils/convertUtils/designerPathEncoder'; +import { dialogsSelectorFamily } from '../selectors'; -import { breadcrumbState } from './../atoms/botState'; -import { designPageLocationState } from './../atoms'; +import { rootBotProjectIdSelector } from './../selectors/project'; +import { canRedoState, canUndoState, designPageLocationState } from './../atoms'; import { trackedAtoms, AtomAssetsMap } from './trackedAtoms'; import UndoHistory from './undoHistory'; type IUndoRedo = { undo: () => void; redo: () => void; - canUndo: () => boolean; - canRedo: () => boolean; commitChanges: () => void; clearUndo: () => void; }; @@ -55,7 +55,6 @@ const getAtomAssetsMap = (snap: Snapshot, projectId: string): AtomAssetsMap => { //should record the location state atomMap.set(designPageLocationState(projectId), snap.getLoadable(designPageLocationState(projectId)).contents); - atomMap.set(breadcrumbState(projectId), snap.getLoadable(breadcrumbState(projectId)).contents); return atomMap; }; @@ -73,17 +72,25 @@ const checkAtomsChanged = (current: AtomAssetsMap, previous: AtomAssetsMap, atom return atoms.some((atom) => checkAtomChanged(current, previous, atom)); }; -function navigate(next: AtomAssetsMap, projectId: string) { - const location = next.get(designPageLocationState(projectId)); - const breadcrumb = [...next.get(breadcrumbState(projectId))]; - if (location) { +function navigate(next: AtomAssetsMap, skillId: string, projectId: string) { + const location = next.get(designPageLocationState(skillId)); + + if (location && projectId) { const { dialogId, selected, focused, promptTab } = location; - let currentUri = `/bot/${projectId}/dialogs/${dialogId}${getUrlSearch(selected, focused)}`; - if (promptTab) { + const dialog = next.get(dialogsSelectorFamily(skillId)).find((dialog) => dialogId === dialog.id); + const baseUri = + skillId == null || skillId === projectId + ? `/bot/${projectId}/dialogs/${dialogId}` + : `/bot/${projectId}/skill/${skillId}/dialogs/${dialogId}`; + + let currentUri = `${baseUri}${getUrlSearch( + encodeArrayPathToDesignerPath(dialog.content, selected), + encodeArrayPathToDesignerPath(dialog.content, focused) + )}`; + if (promptTab && focused) { currentUri += `#${promptTab}`; } - breadcrumb.pop(); - navigateTo(currentUri, { state: { breadcrumb } }); + navigateTo(currentUri); } } @@ -100,15 +107,21 @@ function mapTrackedAtomsOntoSnapshot( target = target.map(({ set }) => set(atom, next)); } }); + + //add design page location to snapshot + const currentLocation = currentAssets.get(designPageLocationState(projectId)); + const nextLocation = nextAssets.get(designPageLocationState(projectId)); + + if (currentLocation !== nextLocation) { + target = target.map(({ set }) => set(designPageLocationState(projectId), nextLocation)); + } return target; } function setInitialLocation(snapshot: Snapshot, projectId: string, undoHistory: UndoHistory) { const location = snapshot.getLoadable(designPageLocationState(projectId)); - const breadcrumb = snapshot.getLoadable(breadcrumbState(projectId)); if (location.state === 'hasValue') { undoHistory.setInitialValue(designPageLocationState(projectId), location.contents); - undoHistory.setInitialValue(breadcrumbState(projectId), breadcrumb.contents); } } interface UndoRootProps { @@ -118,13 +131,16 @@ interface UndoRootProps { export const UndoRoot = React.memo((props: UndoRootProps) => { const { projectId } = props; const undoHistory = useRecoilValue(undoHistoryState(projectId)); + const rootBotProjectId = useRecoilValue(rootBotProjectIdSelector); const history: UndoHistory = useRef(undoHistory).current; const [initialStateLoaded, setInitialStateLoaded] = useState(false); - + const setCanUndo = useSetRecoilState(canUndoState(projectId)); + const setCanRedo = useSetRecoilState(canRedoState(projectId)); const setUndoFunction = useSetRecoilState(undoFunctionState(projectId)); const [, forceUpdate] = useState([]); const setVersion = useSetRecoilState(undoVersionState(projectId)); - + const rootBotId = useRef(''); + rootBotId.current = rootBotProjectId || ''; //use to record the first time change, this will help to get the init location //init location is used to undo navigate const assetsChanged = useRef(false); @@ -162,7 +178,12 @@ export const UndoRoot = React.memo((props: UndoRootProps) => { ) => { target = mapTrackedAtomsOntoSnapshot(target, current, next, projectId); gotoSnapshot(target); - navigate(next, projectId); + navigate(next, projectId, rootBotId.current); + }; + + const updateUndoResult = () => { + setCanRedo(history.canRedo()); + setCanUndo(history.canUndo()); }; const undo = useRecoilCallback(({ snapshot, gotoSnapshot }: CallbackInterface) => () => { @@ -171,6 +192,7 @@ export const UndoRoot = React.memo((props: UndoRootProps) => { const next = history.undo(); if (present) undoAssets(snapshot, present, next, gotoSnapshot, projectId); setVersion(uniqueId()); + updateUndoResult(); } }); @@ -180,17 +202,10 @@ export const UndoRoot = React.memo((props: UndoRootProps) => { const next = history.redo(); if (present) undoAssets(snapshot, present, next, gotoSnapshot, projectId); setVersion(uniqueId()); + updateUndoResult(); } }); - const canUndo = () => { - return history.canUndo(); - }; - - const canRedo = () => { - return history.canRedo(); - }; - const commit = useRecoilCallback(({ snapshot }) => () => { const currentAssets = getAtomAssetsMap(snapshot, projectId); const previousAssets = history.getPresentAssets(); @@ -198,6 +213,7 @@ export const UndoRoot = React.memo((props: UndoRootProps) => { if (previousAssets && checkAtomsChanged(currentAssets, previousAssets, trackedAtoms(projectId))) { history.add(getAtomAssetsMap(snapshot, projectId)); + updateUndoResult(); } }); @@ -214,7 +230,7 @@ export const UndoRoot = React.memo((props: UndoRootProps) => { }); useEffect(() => { - setUndoFunction({ undo, redo, canRedo, canUndo, commitChanges, clearUndo }); + setUndoFunction({ undo, redo, commitChanges, clearUndo }); }, []); return null; diff --git a/Composer/packages/client/src/shell/useShell.ts b/Composer/packages/client/src/shell/useShell.ts index 3e44f3ee05..38c1f1849a 100644 --- a/Composer/packages/client/src/shell/useShell.ts +++ b/Composer/packages/client/src/shell/useShell.ts @@ -16,7 +16,6 @@ import { clipboardActionsState, schemasState, validateDialogsSelectorFamily, - breadcrumbState, focusPathState, skillsState, localeState, @@ -58,6 +57,7 @@ const stubDialog = (): DialogInfo => ({ triggers: [], intentTriggers: [], skills: [], + isFormDialog: false, }); export function useShell(source: EventSource, projectId: string): Shell { @@ -65,7 +65,6 @@ export function useShell(source: EventSource, projectId: string): Shell { const schemas = useRecoilValue(schemasState(projectId)); const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId)); - const breadcrumb = useRecoilValue(breadcrumbState(projectId)); const focusPath = useRecoilValue(focusPathState(projectId)); const skills = useRecoilValue(skillsState(projectId)); const locale = useRecoilValue(localeState(projectId)); @@ -136,17 +135,17 @@ export function useShell(source: EventSource, projectId: string): Shell { updateDialog({ id, content: newDialog.content, projectId }); } - function navigationTo(path) { + async function navigationTo(path, rest?) { if (rootBotProjectId == null) return; - navTo(projectId, path, breadcrumb); + await navTo(projectId, path, rest); } - function focusEvent(subPath) { + async function focusEvent(subPath) { if (rootBotProjectId == null) return; - selectTo(projectId, dialogId, subPath); + await selectTo(projectId, dialogId, subPath); } - function focusSteps(subPaths: string[] = [], fragment?: string) { + async function focusSteps(subPaths: string[] = [], fragment?: string) { let dataPath: string = subPaths[0]; if (source === FORM_EDITOR) { @@ -157,8 +156,7 @@ export function useShell(source: EventSource, projectId: string): Shell { dataPath = `${focused}.${dataPath}`; } } - - focusTo(rootBotProjectId ?? projectId, projectId, dataPath, fragment ?? ''); + await focusTo(rootBotProjectId ?? projectId, projectId, dataPath, fragment ?? ''); } function updateFlowZoomRate(currentRate) { @@ -179,7 +177,7 @@ export function useShell(source: EventSource, projectId: string): Shell { projectId, }); }, - saveData: (newData, updatePath) => { + saveData: (newData, updatePath, callback) => { let dataPath = ''; if (source === FORM_EDITOR) { dataPath = updatePath || focused || ''; @@ -192,8 +190,12 @@ export function useShell(source: EventSource, projectId: string): Shell { projectId, }; dialogMapRef.current[dialogId] = updatedDialog; - updateDialog(payload); - commitChanges(); + return updateDialog(payload).then(async () => { + if (typeof callback === 'function') { + await callback(); + } + commitChanges(); + }); }, updateRegExIntent: updateRegExIntentHandler, renameRegExIntent: renameRegExIntentHandler, diff --git a/Composer/packages/client/src/types/window.d.ts b/Composer/packages/client/src/types/window.d.ts index dd5892d0c3..003cfa04d9 100644 --- a/Composer/packages/client/src/types/window.d.ts +++ b/Composer/packages/client/src/types/window.d.ts @@ -29,5 +29,10 @@ declare global { ExtensionClient: typeof ExtensionClient; Fabric: typeof Fabric; + + /** + * Token generated by the server, and sent with certain auth-related requests to the server to be verified and prevent CSRF attacks. + */ + __csrf__?: string; } } diff --git a/Composer/packages/client/src/utils/authClient.ts b/Composer/packages/client/src/utils/authClient.ts new file mode 100644 index 0000000000..e3ec7ff180 --- /dev/null +++ b/Composer/packages/client/src/utils/authClient.ts @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { AuthParameters } from '@botframework-composer/types'; + +async function getAccessToken(options: AuthParameters): Promise { + try { + const { clientId = '', targetResource = '', scopes = [] } = options; + const { __csrf__ = '' } = window; + + let url = '/api/auth/getAccessToken?'; + const params = new URLSearchParams(); + if (clientId) { + params.append('clientId', clientId); + } + if (scopes.length) { + params.append('scopes', JSON.stringify(scopes)); + } + if (targetResource) { + params.append('targetResource', targetResource); + } + url += params.toString(); + + const result = await fetch(url, { method: 'GET', headers: { 'X-CSRF-Token': __csrf__ } }); + const { accessToken = '' } = await result.json(); + return accessToken; + } catch (e) { + // error handling + console.error('Did not receive an access token back from the server: ', e); + return ''; + } +} + +export const AuthClient = { + getAccessToken, +}; diff --git a/Composer/packages/client/src/utils/convertUtils/designerPathEncoder.ts b/Composer/packages/client/src/utils/convertUtils/designerPathEncoder.ts index 657a050f33..402edc1874 100644 --- a/Composer/packages/client/src/utils/convertUtils/designerPathEncoder.ts +++ b/Composer/packages/client/src/utils/convertUtils/designerPathEncoder.ts @@ -101,7 +101,12 @@ export const decodeDesignerPathToArrayPath = (dialog, path: string): string => { let arrayIndex = -1; if (designerPathInfo) { const { designerId } = designerPathInfo; - arrayIndex = arrayData.findIndex((x) => get(x, '$designer.id') === designerId); + if (designerId === 'beginDialog') { + // special notation to route to this specific trigger + arrayIndex = arrayData.findIndex((x) => get(x, '$kind') === 'Microsoft.OnBeginDialog'); + } else { + arrayIndex = arrayData.findIndex((x) => get(x, '$designer.id') === designerId); + } } else if (arrayPathInfo) { arrayIndex = arrayPathInfo.index; } diff --git a/Composer/packages/client/src/utils/hooks.ts b/Composer/packages/client/src/utils/hooks.ts index a67abc4f9f..d206829d53 100644 --- a/Composer/packages/client/src/utils/hooks.ts +++ b/Composer/packages/client/src/utils/hooks.ts @@ -8,7 +8,13 @@ import find from 'lodash/find'; import { useRecoilValue } from 'recoil'; import { FeatureFlagKey } from '@bfc/shared'; -import { designPageLocationState, currentProjectIdState, pluginPagesSelector, featureFlagsState } from '../recoilModel'; +import { + designPageLocationState, + currentProjectIdState, + pluginPagesSelector, + featureFlagsState, + rootBotProjectIdSelector, +} from '../recoilModel'; import { bottomLinks, topLinks } from './pageLinks'; import routerCache from './routerCache'; @@ -50,10 +56,17 @@ export const useLinks = () => { }; export const useRouterCache = (to: string) => { + const rootProjectId = useRecoilValue(rootBotProjectIdSelector); const [state, setState] = useState(routerCache.getAll()); const { topLinks, bottomLinks } = useLinks(); const linksRef = useRef(topLinks.concat(bottomLinks)); linksRef.current = topLinks.concat(bottomLinks); + + useEffect(() => { + routerCache.cleanAll(); + setState({}); + }, [rootProjectId]); + useEffect(() => { globalHistory.listen(({ location }) => { const links = linksRef.current; diff --git a/Composer/packages/client/src/utils/navigation.ts b/Composer/packages/client/src/utils/navigation.ts index dcc7b19fe4..7a8432d25e 100644 --- a/Composer/packages/client/src/utils/navigation.ts +++ b/Composer/packages/client/src/utils/navigation.ts @@ -1,20 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import cloneDeep from 'lodash/cloneDeep'; import { navigate, NavigateOptions } from '@reach/router'; -import { BreadcrumbItem, DesignPageLocation } from '../recoilModel/types'; +import { DesignPageLocation } from '../recoilModel/types'; import { BASEPATH } from '../constants'; import { parsePathToFocused } from './convertUtils/parsePathToFocused'; import { parsePathToSelected } from './convertUtils/parsePathToSelected'; import { parseTypeToFragment } from './convertUtils/parseTypeToFragment'; import { resolveToBasePath } from './fileUtil'; -export const BreadcrumbUpdateType = { - Selected: 'selected', - Focused: 'focused', -}; export function getFocusPath(selected: string, focused: string): string { if (selected && focused) return focused; @@ -24,31 +19,6 @@ export function getFocusPath(selected: string, focused: string): string { return ''; } -export function clearBreadcrumb(breadcrumb: BreadcrumbItem[], fromIndex?: number): BreadcrumbItem[] { - let breadcrumbCopy = cloneDeep(breadcrumb); - if (fromIndex) { - breadcrumbCopy.splice(fromIndex, breadcrumbCopy.length - fromIndex); - } else { - breadcrumbCopy = []; - } - return breadcrumbCopy; -} - -export function updateBreadcrumb(breadcrumb: BreadcrumbItem[], type: string): BreadcrumbItem[] { - const breadcrumbCopy = cloneDeep(breadcrumb); - if (breadcrumbCopy.length === 0) { - return breadcrumbCopy; - } - - let lastIndex = breadcrumbCopy.length - 1; - while (lastIndex > 0 && breadcrumbCopy[lastIndex][type]) { - breadcrumbCopy.pop(); - lastIndex--; - } - - return breadcrumbCopy; -} - export function getUrlSearch(selected: string, focused: string): string { const search = new URLSearchParams(); if (selected) { @@ -83,7 +53,7 @@ export function checkUrl( } export interface NavigationState { - breadcrumb?: BreadcrumbItem[]; + breadcrumb?: string[]; qnaKbUrls?: string[]; } @@ -97,7 +67,7 @@ export function convertPathToUrl( //uri = id?selected=triggers[0]&focused=triggers[0].actions[0] let uri = `/bot/${projectId}`; - if (skillId != null) { + if (skillId != null && skillId !== projectId) { uri += `/skill/${skillId}`; } if (dialogId != null) { diff --git a/Composer/packages/client/src/utils/oauthClient.ts b/Composer/packages/client/src/utils/oauthClient.ts deleted file mode 100644 index 370eb1bfee..0000000000 --- a/Composer/packages/client/src/utils/oauthClient.ts +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import { isElectron } from './electronUtil'; - -export interface OAuthOptions { - clientId: string; - scopes: string[]; -} - -interface OAuthConfig extends OAuthOptions { - redirectUri: string; -} - -interface OAuthTokens { - accessToken?: string; - idToken?: string; -} - -export class OAuthClient { - private config: OAuthConfig; - private tokens: OAuthTokens; - private id: number; - private static clientId = 0; - - constructor(config: OAuthOptions) { - this.config = { ...config, redirectUri: 'bfcomposer://oauth' }; - this.tokens = {}; - // assign an id to the client so we can route responses back to the right one from the main process - this.id = OAuthClient.clientId++; - } - - /** Logs in the current user and retrieves an id token from Azure */ - public async login(): Promise { - // we need to perform a login request - if (isElectron()) { - return new Promise((resolve, reject) => { - const { ipcRenderer } = window; - ipcRenderer.on('oauth-login-complete', (_ev, idToken: string, id: number) => { - if (id === this.id) { - // make sure the auth request originated from this client instance - this.tokens.idToken = idToken; - resolve(idToken); - } - }); - ipcRenderer.on('oauth-login-error', (_ev, error, id) => { - if (id === this.id) { - console.error('There was an error while attempting to log the current user in: ', error); - reject(error); - } - }); - ipcRenderer.send('oauth-start-login', this.config, this.id); - // TODO: after some amount of time we should reject - }); - } - return Promise.reject('OAuth flow is currently disabled in the Composer web environment.'); - } - - /** - * Retrieves an Azure access token on behalf of the current signed-in user. - */ - public async getTokenSilently(): Promise { - if (isElectron()) { - if (!this.tokens.idToken) { - // login - await this.login(); - } - - const { ipcRenderer } = window; - return new Promise((resolve, reject) => { - ipcRenderer.on('oauth-get-access-token-complete', (_ev, accessToken: string, id: number) => { - if (id === this.id) { - // make sure the auth request originated from this client instance - this.tokens.accessToken = accessToken; - resolve(accessToken); - } - }); - ipcRenderer.on('oauth-get-access-token-error', (_ev, error, id) => { - if (id === this.id) { - console.error('There was an error while attempting to silently get an access token: ', error); - reject(error); - } - }); - // get an access token using the id token - ipcRenderer.send('oauth-get-access-token', this.config, this.tokens.idToken, this.id); - // TODO: after some amount of time we should reject - }); - } - return Promise.reject('OAuth flow is currently disabled in the Composer web environment.'); - } - - // TODO: add token caching -} diff --git a/Composer/packages/client/src/utils/onboardingStorage.ts b/Composer/packages/client/src/utils/onboardingStorage.ts index 9349feee7f..1391b97685 100644 --- a/Composer/packages/client/src/utils/onboardingStorage.ts +++ b/Composer/packages/client/src/utils/onboardingStorage.ts @@ -4,7 +4,7 @@ import storage from './storage'; const KEY = 'OnboardingState'; -const DEFAULT_STATE = { complete: false }; +const DEFAULT_STATE = { complete: process.env.NODE_ENV !== 'production' }; interface IOnboardingState { complete: boolean; diff --git a/Composer/packages/client/src/utils/routerCache.ts b/Composer/packages/client/src/utils/routerCache.ts index 36a78f9db7..08290d9d20 100644 --- a/Composer/packages/client/src/utils/routerCache.ts +++ b/Composer/packages/client/src/utils/routerCache.ts @@ -25,6 +25,11 @@ class RouterCache { this._all[linkTo] = uri; this.storage.set(KEY, this._all); } + + cleanAll() { + this._all = {}; + this.storage.set(KEY, this._all); + } } export default new RouterCache(); diff --git a/Composer/packages/electron-server/.eslintrc.js b/Composer/packages/electron-server/.eslintrc.js index 8cbe448349..31e7c86f7f 100644 --- a/Composer/packages/electron-server/.eslintrc.js +++ b/Composer/packages/electron-server/.eslintrc.js @@ -7,4 +7,12 @@ module.exports = { rules: { 'security/detect-non-literal-fs-filename': 'off', }, + overrides: [ + { + files: ['scripts/*'], + rules: { + '@typescript-eslint/no-var-requires': 'off', + }, + }, + ], }; diff --git a/Composer/packages/electron-server/.gitignore b/Composer/packages/electron-server/.gitignore index 03a0ab5993..d5bff0464b 100644 --- a/Composer/packages/electron-server/.gitignore +++ b/Composer/packages/electron-server/.gitignore @@ -2,3 +2,4 @@ build/ dist/ locales/en-US-pseudo.json l10ntemp/ +oneauth-temp diff --git a/Composer/packages/electron-server/AUTH.md b/Composer/packages/electron-server/AUTH.md new file mode 100644 index 0000000000..05c048201f --- /dev/null +++ b/Composer/packages/electron-server/AUTH.md @@ -0,0 +1,31 @@ +# Enabling Authentication via OneAuth + +## Summary + +Authentication in Composer is done using the OneAuth native node library. + +This library leverages APIs within the user's OS to store and retrieve credentials in a compliant fashion, and allows Composer to get access tokens on behalf of the user once the user signs in. + +We disable this authentication flow by default in the development environment. To use the flow in a dev environment, please follow the steps below to leverage the OneAuth library. + +## Requirements + +**NOTE:** Authentication on Linux is not (yet) supported. We plan to support this in the future. + +When building Composer from source, in order to leverage the OneAuth library you will need to: + +- Set the `COMPOSER_ENABLE_ONEAUTH` environment variable to `true` in whatever process you use to start the `electron-server` package +- Install the `oneauth-win64` or `oneauth-mac` NodeJS module either manually from the private registry, or by downloading it via script + +## Installing the OneAuth module + +Depending on your OS (Mac vs. Windows), you will need to install the `oneauth-mac` or `oneauth-win64` modules respectively. + +### Using the `installOneAuth.js` script + +1. Set `npm_config_registry` to `https://office.pkgs.visualstudio.com/_packaging/OneAuth/npm/registry/` +1. Set `npm_config_username` to anything other than an empty string +1. Set `npm_config__password` (note the double "_") to a base64-encoded [Personal Access Token you created in Azure DevOps](https://office.visualstudio.com/_usersSettings/tokens) for the Office org that has the Packaging (read) scope enabled +1. Run `node scripts/installOneAuth.js` from `/electron-server/` + +There should now be a `/electron-server/oneauth-temp/` directory containing the contents of the OneAuth module which will be called by Composer assuming you set the `COMPOSER_ENABLE_ONEAUTH` environment variable. diff --git a/Composer/packages/electron-server/__tests__/auth/oneAuthService.test.ts b/Composer/packages/electron-server/__tests__/auth/oneAuthService.test.ts new file mode 100644 index 0000000000..9e896129c0 --- /dev/null +++ b/Composer/packages/electron-server/__tests__/auth/oneAuthService.test.ts @@ -0,0 +1,150 @@ +import { OneAuthInstance } from '../../src/auth/oneAuthService'; + +jest.mock('../../src/electronWindow', () => ({ + getInstance: jest.fn().mockReturnValue({ + browserWindow: { + getNativeWindowHandle: jest.fn(), + }, + }), +})); + +jest.mock('../../src/utility/platform', () => ({ + isLinux: () => process.env.TEST_IS_LINUX === 'true', + isMac: () => false, +})); + +describe('OneAuth Serivce', () => { + const INTERACTION_REQUIRED = 'interactionRequired'; + const mockAccount = { + id: 'myAccount', + realm: 'myTenant', + }; + const mockCredential = { + expiresOn: 9999, + value: 'someToken', + }; + const mockOneAuth = { + acquireCredentialInteractively: jest.fn().mockResolvedValue({ credential: mockCredential }), + acquireCredentialSilently: jest.fn().mockResolvedValue({ credential: mockCredential }), + initialize: jest.fn(), + setLogCallback: jest.fn(), + setLogPiiEnabled: jest.fn(), + signInInteractively: jest.fn().mockResolvedValue({ account: mockAccount }), + shutdown: jest.fn(), + AadConfiguration: class AAD {}, + AppConfiguration: class App {}, + AuthParameters: class AP {}, + Status: { + InteractionRequired: INTERACTION_REQUIRED, + }, + }; + let oneAuthService = new OneAuthInstance(); // bypass the shim logic + let processEnvBackup = { ...process.env }; + + afterEach(() => { + process.env = processEnvBackup; + }); + + beforeEach(() => { + jest.resetModules(); + oneAuthService = new OneAuthInstance(); + (oneAuthService as any)._oneAuth = mockOneAuth; + mockOneAuth.acquireCredentialInteractively.mockClear(); + mockOneAuth.acquireCredentialSilently.mockClear(); + mockOneAuth.initialize.mockClear(); + mockOneAuth.setLogCallback.mockClear(); + mockOneAuth.setLogPiiEnabled.mockClear(); + mockOneAuth.signInInteractively.mockClear(); + mockOneAuth.shutdown.mockClear(); + (oneAuthService as any).initialized = false; + (oneAuthService as any).signedInAccount = undefined; + }); + + it('should sign in and get an access token (happy path)', async () => { + const result = await oneAuthService.getAccessToken({ targetResource: 'someProtectedResource' }); + + // it should have initialized + expect(mockOneAuth.setLogPiiEnabled).toHaveBeenCalled(); + expect(mockOneAuth.setLogCallback).toHaveBeenCalled(); + expect(mockOneAuth.initialize).toHaveBeenCalled(); + + // it should have signed in + expect(mockOneAuth.signInInteractively).toHaveBeenCalled(); + expect((oneAuthService as any).signedInAccount).toEqual(mockAccount); + + // it should have called acquireCredentialSilently + expect(mockOneAuth.acquireCredentialSilently).toHaveBeenCalled(); + + expect(result.accessToken).toBe(mockCredential.value); + expect(result.expiryTime).toBe(mockCredential.expiresOn); + }); + + it('should try to acquire a token interactively if interaction is required', async () => { + mockOneAuth.acquireCredentialSilently.mockReturnValueOnce({ error: { status: INTERACTION_REQUIRED } }); + const result = await oneAuthService.getAccessToken({ targetResource: 'someProtectedResource' }); + + expect(mockOneAuth.acquireCredentialInteractively).toHaveBeenCalled(); + + expect(result.accessToken).toBe(mockCredential.value); + expect(result.expiryTime).toBe(mockCredential.expiresOn); + }); + + it('should throw if there is no targetResource passed as an arg', async () => { + try { + await oneAuthService.getAccessToken({ targetResource: undefined } as any); + throw 'Did not throw expected.'; + } catch (e) { + expect(e).toBe('Target resource required to get access token.'); + } + }); + + it('should throw if the signed in account does not have an id', async () => { + try { + mockOneAuth.signInInteractively.mockReturnValueOnce({ account: { id: undefined } }); + await oneAuthService.getAccessToken({ targetResource: 'someProtectedResource' } as any); + throw 'Did not throw expected.'; + } catch (e) { + expect(e).toBe('Signed in account does not have an id.'); + } + }); + + it('should sign out', async () => { + await oneAuthService.getAccessToken({ targetResource: 'someProtectedResource' }); + + // it should have signed in + expect((oneAuthService as any).signedInAccount).toEqual(mockAccount); + + oneAuthService.signOut(); + + expect((oneAuthService as any).signedInAccount).toBeUndefined(); + }); + + it('should shut down', async () => { + await oneAuthService.shutdown(); + + expect(mockOneAuth.shutdown).toHaveBeenCalled(); + }); + + it('should return the shim on Linux', async () => { + Object.assign(process.env, { ...process.env, COMPOSER_ENABLE_ONEAUTH: 'true', TEST_IS_LINUX: 'true' }); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { OneAuthService: service } = require('../../src/auth/oneAuthService'); + const result = await service.getAccessToken({}); + + expect(result).toEqual({ accessToken: '', acquiredAt: 0, expiryTime: 99999999999 }); + }); + + it('should return the shim in the dev environment without the oneauth env variable set', async () => { + Object.assign(process.env, { + ...process.env, + COMPOSER_ENABLE_ONEAUTH: undefined, + NODE_ENV: 'development', + TEST_IS_LINUX: 'false', + }); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { OneAuthService: service } = require('../../src/auth/oneAuthService'); + const result = await service.getAccessToken({}); + + expect(result).toEqual({ accessToken: '', acquiredAt: 0, expiryTime: 99999999999 }); + }); +}); diff --git a/Composer/packages/electron-server/__tests__/setupTests.js b/Composer/packages/electron-server/__tests__/setupTests.js new file mode 100644 index 0000000000..0c94be19e4 --- /dev/null +++ b/Composer/packages/electron-server/__tests__/setupTests.js @@ -0,0 +1,7 @@ +process.env.DEBUG = 'composer*'; + +jest.mock('electron', () => ({ + app: { + getVersion: jest.fn().mockReturnValue('v1.0.0'), + }, +})); diff --git a/Composer/packages/electron-server/babel.l10n.config.js b/Composer/packages/electron-server/babel.l10n.config.js new file mode 100644 index 0000000000..a3793530d0 --- /dev/null +++ b/Composer/packages/electron-server/babel.l10n.config.js @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +module.exports = { + presets: ['@babel/react', ['@babel/typescript', { allowNamespaces: true }]], + plugins: ['@babel/plugin-proposal-class-properties'], + ignore: ['**/__tests__', 'node_modules/**', 'build/**/*.js', 'dist/**'], +}; diff --git a/Composer/packages/electron-server/jest.config.js b/Composer/packages/electron-server/jest.config.js index 216428d9b5..f4650bdda3 100644 --- a/Composer/packages/electron-server/jest.config.js +++ b/Composer/packages/electron-server/jest.config.js @@ -1,3 +1,6 @@ const { createConfig } = require('@botframework-composer/test-utils'); -module.exports = createConfig('electron-server', 'node'); +module.exports = createConfig('electron-server', 'node', { + setupFilesAfterEnv: ['./__tests__/setupTests.js'], + testPathIgnorePatterns: ['/node_modules/', '/__tests__/setupTests.js'], +}); diff --git a/Composer/packages/electron-server/package.json b/Composer/packages/electron-server/package.json index f3e44e77cb..f2d55d422b 100644 --- a/Composer/packages/electron-server/package.json +++ b/Composer/packages/electron-server/package.json @@ -11,11 +11,9 @@ "scripts": { "build": "tsc -p tsconfig.build.json && ncp src/preload.js build/preload.js", "clean": "rimraf build && rimraf dist && rimraf l10ntemp", - "copy-extensions": "node scripts/copy-extensions.js", - "copy-runtime": "node scripts/copy-runtime.js", - "copy-form-dialog-templates": "node scripts/copy-form-dialog-templates.js", + "copy-artifacts": "node scripts/copy-artifacts.js", "dist": "node scripts/electronBuilderDist.js", - "dist:full": "yarn clean && yarn build && yarn run pack && yarn copy-runtime && yarn copy-extensions && yarn copy-form-dialog-templates && yarn dist", + "dist:full": "yarn clean && yarn build && yarn run pack && yarn copy-artifacts && yarn dist", "lint": "eslint --quiet ./src", "lint:fix": "yarn lint --fix", "pack": "node scripts/electronBuilderPack.js", @@ -27,10 +25,13 @@ "l10n:extract": "cross-env NODE_ENV=production format-message extract -g underscored_crc32 -o locales/en-US.json l10ntemp/**/*.js", "l10n:extractJson": "node ../../scripts/l10n-extractJson.js", "l10n:transform": "node ../../scripts/l10n-transform.js", - "l10n:babel": "babel . --extensions \".ts,.tsx,.jsx,.js\" --out-dir l10ntemp --presets=@babel/typescript --plugins=@babel/plugin-proposal-class-properties --ignore \"**/__tests__\",\"**/node_modules\",\"**/build/**/*.js\"", + "l10n:babel": "babel --config-file ./babel.l10n.config.js --extensions \".ts,.tsx,.jsx,.js\" --out-dir l10ntemp .", "l10n": "yarn l10n:babel && yarn l10n:extract && yarn l10n:transform locales/en-US.json && rimraf l10ntemp" }, "devDependencies": { + "@babel/plugin-proposal-class-properties": "7.8.3", + "@babel/plugin-transform-runtime": "7.9.6", + "@botframework-composer/test-utils": "0.0.1", "@types/archiver": "^3.1.0", "@types/body-parser": "^1.17.0", "@types/compression": "^1.0.1", @@ -40,6 +41,7 @@ "@types/form-data": "^2.2.1", "@types/globby": "^9.1.0", "@types/http-errors": "^1.6.1", + "@types/jest": "^26.0.15", "@types/jsonwebtoken": "^8.3.3", "@types/lodash": "^4.14.146", "@types/lru-cache": "^5.1.0", @@ -49,7 +51,7 @@ "@types/rimraf": "^2.0.2", "electron": "8.2.4", "electron-builder": "^22.6.0", - "fs-extra": "^9.0.0", + "globby": "^11.0.1", "js-yaml": "^3.13.1", "mock-fs": "^4.10.1", "ncp": "2.0.0", @@ -62,6 +64,7 @@ "dependencies": { "@bfc/server": "*", "@bfc/shared": "*", + "@botframework-composer/types": "*", "debug": "4.1.1", "electron-updater": "4.2.5", "fix-path": "^3.0.0", @@ -70,5 +73,9 @@ "fs-extra": "^9.0.0", "lodash": "^4.17.19", "semver": "7.3.2" + }, + "peerDependencies": { + "oneauth-mac": "1.11.0", + "oneauth-win64": "1.11.0" } } diff --git a/Composer/packages/electron-server/resources/entitlements-keychain.plist b/Composer/packages/electron-server/resources/entitlements-keychain.plist new file mode 100644 index 0000000000..f5ec040689 --- /dev/null +++ b/Composer/packages/electron-server/resources/entitlements-keychain.plist @@ -0,0 +1,22 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + com.apple.security.cs.allow-dyld-environment-variables + + keychain-access-groups + + UBF8T346G9.com.microsoft.identity.universalstorage + + com.apple.application-identifier + UBF8T346G9.com.microsoft.botframework.composer + com.apple.developer.team-identifier + UBF8T346G9 + + diff --git a/Composer/packages/electron-server/scripts/common.js b/Composer/packages/electron-server/scripts/common.js index dceceb4e61..2177f3a81d 100644 --- a/Composer/packages/electron-server/scripts/common.js +++ b/Composer/packages/electron-server/scripts/common.js @@ -55,7 +55,22 @@ function hashFileAsync(file, algorithm = 'sha512', encoding = 'base64', options) }); } +const scriptName = path.basename(process.argv[1]); +function logInfo(...args) { + const [formatter, ...rest] = args; + console.log(`[${scriptName}] ${formatter}`, ...rest); +} + +function logError(...args) { + const [formatter, ...rest] = args; + console.error(`[${scriptName}] ${formatter}`, ...rest); +} + module.exports = { hashFileAsync, writeToDist, + log: { + info: logInfo, + error: logError, + }, }; diff --git a/Composer/packages/electron-server/scripts/copy-artifacts.js b/Composer/packages/electron-server/scripts/copy-artifacts.js new file mode 100644 index 0000000000..79bce91246 --- /dev/null +++ b/Composer/packages/electron-server/scripts/copy-artifacts.js @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require('path'); + +const fs = require('fs-extra'); + +const electronBuildConfig = require('../electron-builder-config.json'); + +const { log } = require('./common'); + +const oneauthSource = () => { + const oneauthPath = path.resolve(__dirname, '../oneauth-temp'); + if (['win32', 'darwin'].includes(process.platform) && fs.existsSync(oneauthPath)) { + return { source: oneauthPath, dest: 'oneauth', force: true }; + } else { + log.info('Skipping OneAuth. Either on an unsupported platform or it has not been installed to %s.', oneauthPath); + } +}; + +const sources = [ + // extensions + { source: path.resolve(__dirname, '../../../../extensions'), dest: 'extensions' }, + // runtimes + { source: path.resolve(__dirname, '../../../../runtime'), dest: 'runtime' }, + // form-dialog templates + { + source: path.resolve(__dirname, '../../../node_modules/@microsoft/bf-generate-library/templates'), + dest: 'form-dialog-templates', + }, + // oneauth + oneauthSource(), +].filter(Boolean); + +let destinationDir; +switch (process.platform) { + case 'darwin': { + const productName = electronBuildConfig.productName; + destinationDir = path.resolve(__dirname, `../dist/mac/${productName}.app/Contents/Resources/app.asar.unpacked`); + log.info('Mac detected. Copying artifacts to: ', destinationDir); + break; + } + + case 'linux': + destinationDir = path.resolve(__dirname, '../dist/linux-unpacked/resources/app.asar.unpacked'); + log.info('Linux detected. Copying artifacts to: ', destinationDir); + break; + + case 'win32': + destinationDir = path.resolve(__dirname, '../dist/win-unpacked/resources/app.asar.unpacked'); + log.info(`Windows detected. Copying artifacts to ${destinationDir}`); + break; + + default: + log.error('Detected platform is not Mac / Linux / Windows'); + process.exit(1); +} + +const filterOutTS = (src) => { + // true keeps the file, false omits it + return !src.endsWith('.ts') || !src.endsWith('.ts.map'); +}; + +async function copyArtifacts(source, dest, force = false) { + log.info('-------- %s --------', dest); + log.info('Copying %s from: %s', dest, source); + for (const entry of fs.readdirSync(source, { withFileTypes: true })) { + if (force || entry.isDirectory()) { + const extPath = path.join(source, entry.name); + const output = path.join(destinationDir, dest, entry.name); + log.info('Copying %s', entry.name); + + await fs.copy(extPath, output, { filter: filterOutTS }); + } + } +} + +async function copyAll() { + for (const source of sources) { + await copyArtifacts(source.source, source.dest, source.force); + } +} + +copyAll() + .then(() => { + log.info('Copied artifacts successfully.'); + process.exit(0); + }) + .catch((err) => { + log.error('Error while copying artifacts: ', err); + process.exit(1); + }); diff --git a/Composer/packages/electron-server/scripts/copy-extensions.js b/Composer/packages/electron-server/scripts/copy-extensions.js deleted file mode 100644 index 4d4716ed47..0000000000 --- a/Composer/packages/electron-server/scripts/copy-extensions.js +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -const fs = require('fs-extra'); -const { resolve } = require('path'); -const electronBuildConfig = require('../electron-builder-config.json'); - -const source = resolve(__dirname, '../../../../extensions'); -console.log('[copy-extensions.js] Copying extensions from: ', source); - -let destination; -switch (process.platform) { - case 'darwin': - const productName = electronBuildConfig.productName; - destination = resolve(__dirname, `../dist/mac/${productName}.app/Contents/Resources/app.asar.unpacked/extensions`); - console.log('[copy-extensions.js] Mac detected. Copying extensions to: ', destination); - break; - - case 'linux': - destination = resolve(__dirname, '../dist/linux-unpacked/resources/app.asar.unpacked/extensions'); - console.log('[copy-extensions.js] Linux detected. Copying extensions to: ', destination); - break; - - case 'win32': - destination = resolve(__dirname, '../dist/win-unpacked/resources/app.asar.unpacked/extensions'); - console.log(`[copy-extensions.js] Windows detected. Copying extensions from ${source} to ${destination}`); - break; - - default: - console.error('[copy-extensions.js] Detected platform is not Mac / Linux / Windows'); - process.exit(1); -} - -const filterOutTS = (src) => { - // true keeps the file, false omits it - return !src.endsWith('.ts'); -}; - -// copy extensions from /extensions/ to pre-packaged electron app -fs.copy(source, destination, { filter: filterOutTS }, (err) => { - if (err) { - console.error('[copy-extensions.js] Error while copying extensions: ', err); - process.exit(1); - return; - } - console.log('[copy-extensions.js] Copied extensions successfully.'); - process.exit(0); -}); diff --git a/Composer/packages/electron-server/scripts/copy-form-dialog-templates.js b/Composer/packages/electron-server/scripts/copy-form-dialog-templates.js deleted file mode 100644 index 53788aa043..0000000000 --- a/Composer/packages/electron-server/scripts/copy-form-dialog-templates.js +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -const fs = require('fs-extra'); -const { resolve } = require('path'); -const electronBuildConfig = require('../electron-builder-config.json'); - -const tag = 'copy-form-dialogs-templates.js'; - -const source = resolve(__dirname, '../../../node_modules/@microsoft/bf-generate-library/templates'); -const unpackedDir = 'app.asar.unpacked/form-dialog-templates'; -console.log(`[${tag}] Copying templates from: ${source}`); - -let destination; -switch (process.platform) { - case 'darwin': - const productName = electronBuildConfig.productName; - destination = resolve(__dirname, `../dist/mac/${productName}.app/Contents/Resources`, unpackedDir); - console.log(`[${tag}] Mac detected. Copying templates to: ${destination}`); - break; - - case 'linux': - destination = resolve(__dirname, '../dist/linux-unpacked/resources', unpackedDir); - console.log(`[${tag}] Linux detected. Copying templates to: ${destination}`); - break; - - case 'win32': - destination = resolve(__dirname, '../dist/win-unpacked/resources', unpackedDir); - console.log(`[${tag}] Windows detected. Copying templates to ${destination}`); - break; - - default: - console.error(`[${tag}] Detected platform is not Mac / Linux / Windows`); - process.exit(1); -} - -// copy templates from bf-generate-library/templates to asar unpacked dir under packaged electron app -fs.copy(source, destination, { filter: (src) => !src.endsWith('.md') }, (err) => { - if (err) { - console.error(`[${tag}] Error while copying plugins: `, err); - process.exit(1); - return; - } - console.log(`[${tag}] Copied plugins successfully.`); - process.exit(0); -}); diff --git a/Composer/packages/electron-server/scripts/copy-runtime.js b/Composer/packages/electron-server/scripts/copy-runtime.js deleted file mode 100644 index 6971a2d3d1..0000000000 --- a/Composer/packages/electron-server/scripts/copy-runtime.js +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -const fs = require('fs-extra'); -const { resolve } = require('path'); -const electronBuildConfig = require('../electron-builder-config.json'); - -const source = resolve(__dirname, '../../../../runtime'); - -let destination; -switch (process.platform) { - case 'darwin': - const productName = electronBuildConfig.productName; - destination = resolve(__dirname, `../dist/mac/${productName}.app/Contents/Resources/app.asar.unpacked/runtime`); - console.log(`[copy-runtime.js] Mac detected. Copying runtime from ${source} to ${destination}`); - break; - - case 'linux': - destination = resolve(__dirname, '../dist/linux-unpacked/resources/app.asar.unpacked/runtime'); - console.log(`[copy-runtime.js] Linux detected. Copying runtime from ${source} to ${destination}`); - break; - - case 'win32': - destination = resolve(__dirname, '../dist/win-unpacked/resources/app.asar.unpacked/runtime'); - console.log(`[copy-runtime.js] Windows detected. Copying runtime from ${source} to ${destination}`); - break; - - default: - console.error('[copy-runtime.js] Detected platform is not Mac / Linux / Windows'); - process.exit(1); -} - -// copy bot runtime to build directory to be packaged -fs.copy(source, destination, (err) => { - if (err) { - console.error('[copy-runtime.js] Error while copying runtime:'); - process.exit(1); - return; - } - console.log('[copy-runtime.js] Copied runtime successfully.'); -}); diff --git a/Composer/packages/electron-server/scripts/electronBuilderDist.js b/Composer/packages/electron-server/scripts/electronBuilderDist.js index ff0a911de2..93d93ce2f7 100644 --- a/Composer/packages/electron-server/scripts/electronBuilderDist.js +++ b/Composer/packages/electron-server/scripts/electronBuilderDist.js @@ -2,8 +2,11 @@ // Licensed under the MIT License. const { resolve } = require('path'); +// eslint-disable-next-line security/detect-child-process const { exec } = require('child_process'); +const { log } = require('./common'); + /* * Calls electron-builder to take the pre-packed app contents and turn them into * a packaged, distributable application for the host OS @@ -35,21 +38,21 @@ try { // call electron-builder . --prepackaged --config electron-builder-config.json const cmd = `"${electronBuilderBinary}" "${electronServerDir}" --${platform} --x64 --prepackaged "${unpackedAppDir}" --config electron-builder-config.json`; - console.log('[electronBuilderDist.js] Executing command: ', cmd); + log.info('Executing command: ', cmd); const proc = exec(cmd); - proc.stdout.on('data', data => { + proc.stdout.on('data', (data) => { console.log(data); }); - proc.stderr.on('data', data => { + proc.stderr.on('data', (data) => { console.error(data); }); - proc.on('close', code => { + proc.on('close', (code) => { if (code !== 0) { throw new Error(`[electronBuilderDist.js] electron-builder exited with code ${code}`); } }); } catch (e) { - console.error('[electronBuilderDist.js] Error occurred while using electron-builder --dir: ', e); + log.error('Error occurred while using electron-builder --dir: ', e); process.exit(1); } diff --git a/Composer/packages/electron-server/scripts/electronBuilderPack.js b/Composer/packages/electron-server/scripts/electronBuilderPack.js index 7a86e36687..e58fc5ae8a 100644 --- a/Composer/packages/electron-server/scripts/electronBuilderPack.js +++ b/Composer/packages/electron-server/scripts/electronBuilderPack.js @@ -2,14 +2,17 @@ // Licensed under the MIT License. const { resolve } = require('path'); +// eslint-disable-next-line security/detect-child-process const { exec } = require('child_process'); +const { log } = require('./common'); + /* * Calls electron-builder to pre-pack the app contents into what * will be packaged inside of the OS-specific distributable application */ try { - const electronBuilderBinary = resolve(__dirname, '../../../node_modules/.bin/electron-builder'); + const electronBuilderBinary = resolve(__dirname, '../node_modules/.bin/electron-builder'); const electronServerDir = resolve(__dirname, '..'); let platform; switch (process.platform) { @@ -31,21 +34,21 @@ try { // call electron-builder . --dir --config electron-builder-config.json const cmd = `"${electronBuilderBinary}" "${electronServerDir}" --dir --${platform} --x64 --config electron-builder-config.json`; - console.log('[electronBuilderPack.js] Executing command: ', cmd); + log.info('Executing command: ', cmd); const proc = exec(cmd); - proc.stdout.on('data', data => { + proc.stdout.on('data', (data) => { console.log(data); }); - proc.stderr.on('data', data => { + proc.stderr.on('data', (data) => { console.error(data); }); - proc.on('close', code => { + proc.on('close', (code) => { if (code !== 0) { throw new Error(`[electronBuilderPack.js] electron-builder exited with code ${code}`); } }); } catch (e) { - console.error('[electronBuilderPack.js] Error occurred while using electron-builder --dir: ', e); + log.error('[electronBuilderPack.js] Error occurred while using electron-builder --dir: ', e); process.exit(1); } diff --git a/Composer/packages/electron-server/scripts/installOneAuth.js b/Composer/packages/electron-server/scripts/installOneAuth.js new file mode 100644 index 0000000000..b77be1ffe5 --- /dev/null +++ b/Composer/packages/electron-server/scripts/installOneAuth.js @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require('path'); +// eslint-disable-next-line security/detect-child-process +const { execSync } = require('child_process'); + +const { ensureDir, remove } = require('fs-extra'); +const tar = require('tar'); +const glob = require('globby'); + +const { log } = require('./common'); + +/** + * USAGE: + * Set the following npm config environment variables to install oneauth from the ADO registry. + * Then invoke `node scripts/installOneAuth.js`. + * - npm_config_username + * - npm_config__password (note the double _) + * - npm_config_registry + */ + +let packageName = null; + +switch (process.platform) { + case 'darwin': + packageName = 'oneauth-mac'; + log.info('Mac detected. Using %s package.', packageName); + break; + case 'win32': + packageName = 'oneauth-win64'; + log.info('Windows detected. Using %s package.', packageName); + break; + default: + log.error('Detected platform is not Mac / Windows.'); + process.exit(1); +} + +if (packageName === null) { + process.exit(1); +} + +const outDir = path.resolve(__dirname, '../oneauth-temp'); + +// check for env variables +['username', '_password', 'registry'].forEach((configKey) => { + if (!process.env[`npm_config_${configKey}`]) { + log.error(`Must set npm_config_${configKey} to use.`); + process.exit(1); + } +}); + +async function downloadPackage() { + log.info('Starting download.'); + await ensureDir(outDir); + try { + execSync(`cd ${outDir} && npm pack ${packageName}`, { encoding: 'utf-8' }); + } catch (err) { + process.exit(1); + return; + } + + // find tgz + const files = await glob('oneauth*.tgz', { cwd: outDir }); + + if (files.length !== 1) { + log.error('Did not find exactly 1 archive. Exiting.'); + process.exit(1); + return; + } + + return path.join(outDir, files[0]); +} + +async function extractPackage(archivePath) { + log.info('Extracting tarball.'); + await tar.extract({ + file: archivePath, + strip: 1, + C: outDir, + strict: true, + }); + log.info('Done extracting.'); + return archivePath; +} + +async function postinstall() { + log.info('Running post install tasks.'); + execSync('npm run postinstall', { cwd: outDir }); +} + +async function cleanUp(archivePath) { + log.info('Cleaning up archive.'); + await remove(archivePath); +} + +remove(outDir) + .then(ensureDir(outDir)) + .then(downloadPackage) + .then(extractPackage) + .then(cleanUp) + .then(() => { + // oneauth-mac requires us to relink the files + if (process.platform === 'darwin') { + postinstall(); + } + }) + .then(() => { + log.info('Done installing oneauth.'); + process.exit(0); + }) + .catch((err) => { + log.error('Error downloading oneauth. ', err); + process.exit(1); + }); diff --git a/Composer/packages/electron-server/scripts/sign-mac.js b/Composer/packages/electron-server/scripts/sign-mac.js new file mode 100644 index 0000000000..d7b139abf4 --- /dev/null +++ b/Composer/packages/electron-server/scripts/sign-mac.js @@ -0,0 +1,158 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const fs = require('fs'); +const path = require('path'); +// eslint-disable-next-line security/detect-child-process +const { execSync } = require('child_process'); +const glob = require('globby'); + +const { log } = require('./common'); + +if (process.platform !== 'darwin') { + log.error('Script can only be run on MacOS'); + process.exit(1); +} + +const provisionProfilePath = process.argv[2]; + +if (!provisionProfilePath || !fs.existsSync(provisionProfilePath)) { + log.error('Unable to locate provision profile: %s', provisionProfilePath); + process.exit(1); +} + +if (!process.env.DEV_CERT_ID || !process.env.DEV_CERT || !process.env.DEV_CERT_PASSWORD) { + log.error('Dev certificate not found.'); + process.exit(1); +} + +const tempDir = process.env.AGENT_TEMPDIRECTORY; +if (!tempDir) { + log.error('No temp dir set. (AGENT_TEMPDIRECTORY)'); + process.exit(1); +} + +// sign each app bundle with correct entitlements +const baseBundlePath = path.resolve(__dirname, '../dist/mac/Bot Framework Composer.app'); +const baseEntitlementsPath = path.resolve(__dirname, '../resources/entitlements.plist'); +const keychainEntitlementsPath = path.resolve(__dirname, '../resources/entitlements-keychain.plist'); + +const bundles = [ + { + path: path.join(baseBundlePath, 'Contents/Frameworks/Bot Framework Composer Helper (GPU).app'), + entitlements: baseEntitlementsPath, + }, + { + path: path.join(baseBundlePath, 'Contents/Frameworks/Bot Framework Composer Helper (Plugin).app'), + entitlements: baseEntitlementsPath, + }, + { + path: path.join(baseBundlePath, 'Contents/Frameworks/Bot Framework Composer Helper (Renderer).app'), + entitlements: baseEntitlementsPath, + }, + { + path: path.join(baseBundlePath, 'Contents/Frameworks/Bot Framework Composer Helper.app'), + entitlements: keychainEntitlementsPath, + }, + { + path: baseBundlePath, + entitlements: keychainEntitlementsPath, + }, +]; + +// first copy the provision profile into each app bundle +try { + for (const bundle of bundles) { + fs.copyFileSync(provisionProfilePath, path.join(bundle.path, 'Contents/embedded.provisionprofile')); + } +} catch (err) { + log.error('Error copying provision profile. %O', err); + process.exit(1); +} + +// second step is to setup a dev cert to use to sign +try { + log.info('-------- Setting up keychain. --------\n'); + const keychainPath = `${tempDir}/buildagent.keychain`; + const certPath = `${tempDir}/cert.p12`; + + log.info(`security create-keychain -p pwd ${keychainPath}`); + execSync(`security create-keychain -p pwd ${keychainPath}`, { stdio: 'inherit' }); + + log.info(`security default-keychain -s ${keychainPath}`); + execSync(`security default-keychain -s ${keychainPath}`, { stdio: 'inherit' }); + + log.info(`security unlock-keychain -p pwd ${keychainPath}`); + execSync(`security unlock-keychain -p pwd ${keychainPath}`, { stdio: 'inherit' }); + + log.info(`echo ********* | base64 -D > ${certPath}`); + execSync(`echo ${process.env.DEV_CERT} | base64 -D > ${certPath}`, { stdio: 'inherit' }); + + log.info(`security import ${certPath} -k ${keychainPath} -P "*********" -T /usr/bin/codesign`); + execSync( + `security import ${certPath} -k ${keychainPath} -P "${process.env.DEV_CERT_PASSWORD}" -T /usr/bin/codesign`, + { stdio: 'inherit' } + ); + + log.info(`security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd ${keychainPath}`); + execSync(`security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd ${keychainPath}`, { + stdio: 'inherit', + }); +} catch (err) { + log.error('Error setting up dev certificate and keychain. %O', err); + process.exit(1); +} + +try { + log.info('-------- Signing frameworks. --------\n'); + const fwsPath = path.join(baseBundlePath, 'Contents/Frameworks'); + const frameworks = glob.sync('*.framework', { cwd: fwsPath, onlyFiles: false }); + + for (const fw of frameworks) { + const fwPath = path.join(baseBundlePath, 'Contents/Frameworks', fw, 'Versions/A'); + const dylibs = glob.sync('Libraries/*.dylib', { cwd: fwPath }); + + for (const lib of dylibs) { + log.info(`codesign -s ******* --timestamp=none --force "${path.join(fwPath, lib)}"`); + execSync(`codesign -s ${process.env.DEV_CERT_ID} --timestamp=none --force "${path.join(fwPath, lib)}"`, { + stdio: 'inherit', + }); + } + + log.info(`codesign -s ******* --timestamp=none --force "${fwPath}"`); + execSync(`codesign -s ${process.env.DEV_CERT_ID} --timestamp=none --force "${fwPath}"`, { + stdio: 'inherit', + }); + } +} catch (err) { + log.error('Error signing frameworks. %O', err); + process.exit(1); +} + +try { + log.info('-------- Signing bundles. --------\n'); + for (const bundle of bundles) { + log.info( + `codesign -s ******* --timestamp=none --force --options runtime --entitlements "${bundle.entitlements}" "${bundle.path}"` + ); + execSync( + `codesign -s ${process.env.DEV_CERT_ID} --timestamp=none --force --options runtime --entitlements "${bundle.entitlements}" "${bundle.path}"`, + { stdio: 'inherit' } + ); + } +} catch (err) { + log.error('Error setting signing app bundles. %O', err); + process.exit(1); +} + +// verify codesign +try { + log.info('-------- Verifying codesigning. --------\n'); + for (const bundle of bundles) { + log.info(`codesign -dv --verbose=4 "${bundle.path}"`); + execSync(`codesign -dv --verbose=4 "${bundle.path}"`); + } +} catch (err) { + log.error('Error verifying codesign. %O', err); + process.exit(1); +} diff --git a/Composer/packages/electron-server/src/auth/oneAuthBase.ts b/Composer/packages/electron-server/src/auth/oneAuthBase.ts new file mode 100644 index 0000000000..86dd760ffb --- /dev/null +++ b/Composer/packages/electron-server/src/auth/oneAuthBase.ts @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { ElectronAuthParameters } from '@botframework-composer/types'; + +export abstract class OneAuthBase { + abstract async getAccessToken( + params: ElectronAuthParameters + ): Promise<{ accessToken: string; acquiredAt: number; expiryTime: number }>; + abstract shutdown(); + abstract signOut(); +} diff --git a/Composer/packages/electron-server/src/auth/oneAuthService.ts b/Composer/packages/electron-server/src/auth/oneAuthService.ts new file mode 100644 index 0000000000..cd1ebdb829 --- /dev/null +++ b/Composer/packages/electron-server/src/auth/oneAuthService.ts @@ -0,0 +1,227 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +import path from 'path'; + +import { ElectronAuthParameters } from '@botframework-composer/types'; +import { app } from 'electron'; + +import ElectronWindow from '../electronWindow'; +import { isLinux, isMac } from '../utility/platform'; +import logger from '../utility/logger'; +import { getUnpackedAsarPath } from '../utility/getUnpackedAsarPath'; +import { isDevelopment } from '../utility/env'; + +import { OneAuth } from './oneauth'; +import { OneAuthShim } from './oneAuthShim'; +import { OneAuthBase } from './oneAuthBase'; + +const log = logger.extend('one-auth'); + +const COMPOSER_APP_ID = 'com.microsoft.BotFrameworkComposer'; +const COMPOSER_APP_NAME = 'BotFrameworkComposer'; +const COMPOSER_APP_VERSION = app.getVersion(); +const COMPOSER_CLIENT_ID = 'ce48853e-0605-4f77-8746-d70ac63cc6bc'; +const COMPOSER_REDIRECT_URI = 'ms-appx-web://Microsoft.AAD.BrokerPlugin/ce48853e-0605-4f77-8746-d70ac63cc6bc'; +const GRAPH_RESOURCE = 'https://graph.microsoft.com'; +const DEFAULT_LOCALE = 'en'; // TODO: get this from settings? +const DEFAULT_AUTH_SCHEME = 2; // bearer token +const DEFAULT_AUTH_AUTHORITY = 'https://login.microsoftonline.com/common'; // work and school accounts + +export class OneAuthInstance extends OneAuthBase { + private initialized: boolean; + private _oneAuth: typeof OneAuth | null = null; //eslint-disable-line + private signedInAccount: OneAuth.Account | undefined; + + constructor() { + super(); + log('Using genuine OneAuth.'); + // will wait until called to initialize (so that we're sure we have a browser window) + this.initialized = false; + } + + private initialize() { + const window = ElectronWindow.getInstance().browserWindow; + if (window) { + const isDevelopment = Boolean(process.env.NODE_ENV && process.env.NODE_ENV === 'development'); + log('PII logging enabled: %s', isDevelopment); + this.oneAuth.setLogPiiEnabled(isDevelopment); + this.oneAuth.setLogCallback((logLevel, message) => { + log('%s %s', logLevel, message); + }); + log('Initializing...'); + const appConfig = new this.oneAuth.AppConfiguration( + COMPOSER_APP_ID, + COMPOSER_APP_NAME, + COMPOSER_APP_VERSION, + DEFAULT_LOCALE, + 'Please login', + window.getNativeWindowHandle() + ); + const aadConfig = new this.oneAuth.AadConfiguration( + COMPOSER_CLIENT_ID, + COMPOSER_REDIRECT_URI, + GRAPH_RESOURCE, + false // prefer broker + ); + this.oneAuth.initialize(appConfig, undefined, aadConfig, undefined); + this.initialized = true; + log('Service initialized.'); + } else { + log('Electron window did exist not at time of initialization.'); + } + } + + public async getAccessToken( + params: ElectronAuthParameters + ): Promise<{ accessToken: string; acquiredAt: number; expiryTime: number }> { + try { + if (!this.initialized) { + this.initialize(); + } + log('Getting access token...'); + if (!params.targetResource) { + throw 'Target resource required to get access token.'; + } + + // Temporary until we properly configure local Mac dev experience + if (isMac() && isDevelopment) { + log('Mac development env detected. Getting access token using interactive sign in instead of silently.'); + return this.TEMPORARY_getAccessTokenOnMacDev(params); + } + + if (!this.signedInAccount) { + // we need to sign in + log('No signed in account found. Signing user in before getting access token.'); + await this.signIn(); + } + if (!this.signedInAccount?.id) { + throw 'Signed in account does not have an id.'; + } + // use the signed in account to acquire a token + const reqParams = new this.oneAuth.AuthParameters( + DEFAULT_AUTH_SCHEME, + DEFAULT_AUTH_AUTHORITY, + params.targetResource, + this.signedInAccount.realm, + '' + ); + let result = await this.oneAuth.acquireCredentialSilently(this.signedInAccount?.id, reqParams, ''); + if (result.credential && result.credential.value) { + log('Acquired access token. %s', result.credential.value); + return { + accessToken: result.credential.value, + acquiredAt: Date.now(), + expiryTime: result.credential.expiresOn, + }; + } + if (result.error) { + if (result.error.status === this.oneAuth.Status.InteractionRequired) { + // try again but interactively + log('Interaction required. Trying again interactively to get access token.'); + result = await this.oneAuth.acquireCredentialInteractively(this.signedInAccount?.id, reqParams, ''); + if (result.credential && result.credential.value) { + log('Acquired access token interactively. %s', result.credential.value); + return { + accessToken: result.credential.value, + acquiredAt: Date.now(), + expiryTime: result.credential.expiresOn, + }; + } + } + throw result.error; + } + throw 'Could not acquire an access token.'; + } catch (e) { + log('Error while trying to get an access token: %O', e); + throw e; + } + } + + public shutdown() { + log('Shutting down...'); + this.oneAuth.shutdown(); + log('Shut down.'); + } + + /** + * Clears the account saved in memory. + */ + public signOut() { + log('Signing out user...'); + this.signedInAccount = undefined; + log('Signed out user.'); + } + + /** + * Sign the user in and save the account in memory. + */ + private async signIn(): Promise { + try { + log('Signing in...'); + const result: OneAuth.AuthResult = await this.oneAuth.signInInteractively('', undefined, ''); + this.signedInAccount = result.account; + log('Signed in successfully. Got account: %O', result.account); + } catch (e) { + log('There was an error trying to sign in: %O', e); + throw e; + } + } + + /** Temporary workaround on Mac until we figure out how to enable keychain access on a dev build. */ + // eslint-disable-next-line + private async TEMPORARY_getAccessTokenOnMacDev( + params: ElectronAuthParameters + ): Promise<{ accessToken: string; acquiredAt: number; expiryTime: number }> { + try { + // sign-in every time with auth parameters to get a token + const reqParams = new this.oneAuth.AuthParameters( + DEFAULT_AUTH_SCHEME, + DEFAULT_AUTH_AUTHORITY, + params.targetResource, + '', + '' + ); + const result = await this.oneAuth.signInInteractively('', reqParams, ''); + if (result && result.credential && result.credential.value) { + log('Acquired access token. %s', result.credential.value); + return { + accessToken: result.credential.value, + acquiredAt: Date.now(), + expiryTime: result.credential.expiresOn, + }; + } + throw 'Could not acquire an access token.'; + } catch (e) { + log('There was an error trying to acquire a token on Mac by signing in interactively: %O', e); + throw e; + } + } + + private get oneAuth() { + if (!this._oneAuth) { + log('Attempting to load oneauth module from %s.', this.oneauthPath); + try { + // eslint-disable-next-line security/detect-non-literal-require + this._oneAuth = require(this.oneauthPath) as typeof OneAuth; + } catch (e) { + log('Error loading oneauth module. %O', e); + throw e; + } + } + + return this._oneAuth; + } + + private get oneauthPath() { + if (process.env.NODE_ENV === 'production') { + return path.join(getUnpackedAsarPath(), 'oneauth'); + } else { + return path.resolve(__dirname, '../../oneauth-temp'); + } + } +} + +// only use the shim in Linux, or dev environment without flag enabled +const useShim = (isDevelopment && process.env.COMPOSER_ENABLE_ONEAUTH !== 'true') || isLinux(); + +export const OneAuthService = useShim ? new OneAuthShim() : new OneAuthInstance(); diff --git a/Composer/packages/electron-server/src/auth/oneAuthShim.ts b/Composer/packages/electron-server/src/auth/oneAuthShim.ts new file mode 100644 index 0000000000..4667522b3e --- /dev/null +++ b/Composer/packages/electron-server/src/auth/oneAuthShim.ts @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { ElectronAuthParameters } from '@botframework-composer/types'; + +import logger from '../utility/logger'; + +import { OneAuthBase } from './oneAuthBase'; + +const log = logger.extend('one-auth-shim'); + +export class OneAuthShim extends OneAuthBase { + constructor() { + super(); + log('Using OneAuth shim.'); + log('To use genuine OneAuth library please read AUTH.md'); + } + + public async getAccessToken(params: ElectronAuthParameters) { + return { accessToken: '', acquiredAt: 0, expiryTime: 99999999999 }; + } + public shutdown() {} + public signOut() {} +} diff --git a/Composer/packages/electron-server/src/auth/oneauth.d.ts b/Composer/packages/electron-server/src/auth/oneauth.d.ts new file mode 100644 index 0000000000..5fd78ec6e0 --- /dev/null +++ b/Composer/packages/electron-server/src/auth/oneauth.d.ts @@ -0,0 +1,576 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace OneAuth { + /// Levels of logging. Defines the priority of the logged message + export enum LogLevel { + /// Available to fully disable logging + LogLevelNoLog = 1, + /// Default + LogLevelError = 2, + LogLevelWarning = 3, + LogLevelInfo = 4, + LogLevelVerbose = 5, + } + + /// Flights reflecting the values from OneAuthFlight.hpp + export enum Flight { + UseMsalforMsa = 2, + // Windows Only Flights + UseWamforMSA = 1002, + UseWamforAAD = 1003, + } + + export enum AccountType { + /// Microsoft personal accounts + Msa = 1, + /// Microsoft work or school accounts + Aad = 2, + /// On-premises accounts + OnPremises = 3, + } + + export enum CredentialType { + /// Access token - short-lived, used to access one resource + AccessToken = 1, + /// An opaque reference to account password. Can be used to retrieve cleartext password + PasswordReference = 2, + /// Opaque Kerberos credential reference. This is the indication that Kerberos is being used for auhentication. + KerberosReference = 3, + } + + export enum Status { + /// This indicates a bug in our code, in one of our dependencies, or on the server. + /// It may be caused by an unexpected error, exception, or bad data. + /// This may also indicate an API attempting to return an invalid status, in which case the attempted return + /// will be appended to the context. + /// Retrying this request will have undefined behavior. You can still call other APIs, which may succeed. Do so at + /// your own risk. + Unexpected = 0, + + /// This status is reserved for future use. + Reserved = 1, + + /// Authentication is possible, but it requires direct user interaction. + /// Please find an appropriate time to show a dialogue to the user by calling SignIn() or + /// AcquireTokenInteractively(). Retrying this request may return the same error, unless another application has + /// already done the above. + InteractionRequired = 2, + + /// There is no network available on the host machine. This status indicates that a network request was attempted. + /// Retrying this request may return the same error, if the network state has not been fixed. + /// You can retry this request as soon as you believe the network has changed. + /// Please inform the user that their network is unavailable, and that they cannot be signed in because of it. + NoNetwork = 3, + + /// The network is temporarily unavailable on the host machine. This status indicates that a network request was + /// attempted. Retrying this request may succeed without any additional effort by the user. + NetworkTemporarilyUnavailable = 4, + + /// The authentication server is unavailable. This status indicates that a network request was attempted. + /// Retrying this request must be handled at exponential backoff to avoid further stressing the server. + ServerTemporarilyUnavailable = 5, + + /// This request violates our API contract. This includes invalid parameters, calling a UI method on a non-UI + /// std::thread, or attempting to authenticate with an unregistered application. See the result context for + /// additional information. Retrying this request will return the same error. + ApiContractViolation = 6, + + /// The request was cancelled by the user. + UserCanceled = 7, + + /// The request was cancelled by the application. + ApplicationCanceled = 8, + + /// The application is not able to complete this request. + /// The user should contact their IT administrator for help resolving this issue. + IncorrectConfiguration = 9, + + /// The passed in buffer is too small. + InsufficientBuffer = 10, + + /// The provided authority is not trusted to authenticate against. + AuthorityUntrusted = 11, + } + + export enum AuthScheme { + /// HTTP Basic + Basic = 1, + /// OAuth2 + Bearer = 2, + /// LiveId authentication using RPS tokens + LiveId = 3, + /// SPNEGO + Negotiate = 4, + /// Windows Challenge/Response (NTLM) + Ntlm = 5, + } + + /// AppId for OneDrive and Sharepoint + export const CommonAppIdsODSP = 'com.microsoft.ODSP'; + + /// AppId for Office Apps: Word, Excel, Powerpoint + export const CommonAppIdsOffice = 'com.microsoft.Office'; + + /// AppId for Outlook + export const CommonAppIdsOutlook = 'com.microsoft.Outlook'; + + /// AppId for Bing + export const CommonAppIdsBing = 'com.microsoft.Bing'; + + /// AppId for Edge + export const CommonAppIdsEdge = 'com.microsoft.Edge'; + + /// AppId for Microsoft To-Do + export const CommonAppIdsToDo = 'com.microsoft.to-do'; + + /// AppId for Microsoft Intune Company Portal + export const CommonAppIdsCompanyPortal = 'com.microsoft.CompanyPortal'; + + export interface AuthResult { + readonly account: Account; + readonly credential: Credential; + readonly error: Error; + readonly correlationId: string; + } + + export interface Account { + readonly id: string; + readonly accountType: AccountType; + readonly authority: string; + readonly sovereignty: string; + readonly environment: string; + readonly loginName: string; + readonly displayName: string; + readonly providerId: string; + readonly realm: string; + readonly givenName: string; + readonly middleName: string; + readonly familyName: string; + readonly email: string; + readonly phoneNumber: string; + readonly sid: string; + readonly accountHints: string[]; + readonly hosts: string[]; + } + + export interface Credential { + readonly id: string; + readonly credentialType: CredentialType; + readonly accountId: string; + readonly authority: string; + readonly value: string; + readonly target: string; + readonly expiresOn: number; + } + + export interface Error { + readonly status: Status; + readonly diagnostics: { + readonly errorCode: string | undefined; + readonly tag: string | undefined; + readonly description: string | undefined; + }; + readonly toString: string | undefined; + } + + export class AuthParameters { + constructor(authScheme: AuthScheme, authority: string, target: string, realm: string, accessTokenToRenew: string); + readonly authScheme: AuthScheme; + readonly authority: string; + readonly realm: string; + readonly target: string; + readonly accessTokenToRenew: string; + SetAdditionalParameter(key: string, value: string): void; + } + + export class AppConfiguration { + constructor( + appId: string, + appName: string, + appVersion: string, + languageCode: string, + signInWindowTitle: string | undefined, + parentWindow: any | undefined + ); + readonly appId: string; + readonly appName: string; + readonly appVersion: string; + readonly languageCode: string | undefined; + readonly signInWindowTitle: string | undefined; // Windows only + readonly parentWindow: any | undefined; // Windows only + } + + export class MsaConfiguration { + /// Constructor for MsaConfiguration + /// @param clientId + /// @param redirectUri + /// @param defaultSignInScope + /// @deprecated @param useMsalFlight no longer used and has no effect @see {@link SetFlights} + constructor( + clientId: string, + redirectUri: string, + defaultSignInScope: string, + useMsalFlight: boolean | undefined // deprecated + ); + readonly clientId: string; + readonly redirectUri: string; + readonly defaultSignInScope: string; + } + + export class AadConfiguration { + constructor(clientId: string, redirectUri: string, defaultSignInResource: string, preferBroker: boolean); + readonly clientId: string; + readonly redirectUri: string; + readonly defaultSignInResource: string; + } + + /// Configures the OneAuth module. + /// + /// @param appConfiguration The OneAuth app configuration. + /// @param msaConfiguration The MSA configuration. + /// @param aadConfiguration The AAD configuration. + /// @param telemetryConfiguration The Telemetry configuration to receive telemetry dispatched events. + /// @return false if Startup fails, true otherwise. + export function initialize( + appConfiguration: AppConfiguration, + msaConfiguration: MsaConfiguration | undefined, + aadConfiguration: AadConfiguration | undefined, + telemetryConfiguration: TelemetryConfiguration | undefined + ): boolean; + + /// Cancels all outstanding tasks, closes the authentication UI if any, and shuts down the OneAuth authenticator. + /// Once called, acquiring authenticator instance(s) is only possible after calling the configuration API. + export function shutdown(): void; + + /// Sets the language to be used within dialogs inside of OneAuth so that we may display locale correctly + /// @param code that represents the locale to be used + export function setLanguageCode(code: string): void; + + /// Lists the supported locale codes within OneAuth + export function supportedLanguageCodes(): string[]; + + /// Returns the current language code being used + export function getLanguageCode(): string; + + /// Sets Logging level for OneAuth and ADAL + /// @param level Desired logging level. + /// @return Previous logging level. If unable to return the previous logging value, will return "error" level by default. + export function setLogLevel(level: LogLevel): LogLevel; + + /// The OneAuth Log Callback + /// @param level The level of the log message + /// @param message A log message describing the event that occurred + /// @param containsPii If the message might contain Personally Identifiable Information (PII) this will be true. Log messages possibly containing PII will not be sent to the callback unless piiEnabled is set to YES on the logger. + export type LogCallback = (level: LogLevel, message: string, containsPii: boolean) => void; + + /// OneAuth provides a logging callback to assist diagnostics. If piiEnabled is set to NO, the callback will not be triggered for log messages that contain any user information. By default this is NO for release builds and YES for debug builds. + /// @param enabled PII on/off flag. + /// @return Previous PII on/off flag value. + export function setLogPiiEnabled(enabled: boolean): boolean; + + /// Sets OneAuth Log Callback which will be called on every tracing event that meets log level and PII settings. + /// Callback implementers must ensure its performance and reliability (e.g. do not throw exceptions, do handle concurrent calls, etc). + /// @param callback Callback that is called by OneAuth logging facility. + export function setLogCallback(callback: LogCallback): void; + + /// Present the sign-in UI and signs in a user to the application. + /// + /// A successful sign-in returns an account. A new account is created unless there is already a matching one in the + /// local account store. Additionally, an account profile and image are read from a profile endpoint if one is + /// available and the credentials are sufficient for the operation. + /// + /// **Threading:** calls from a thread other than the main thread, as well as concurrent calls, will result in a + /// {@link Status::ApiContractViolation} error. + /// + /// @param accountHint Initial account hint, if you already know what account the user wants to sign in to. If this + /// argument is empty, the user will be able to supply the account hint via account collection UI. + /// @param authParameters What authentication scheme, authority, etc. should be used for authentication. This can + /// either be created from an HTTP authentication challenge (using the CreateAuthParameters API), or manually. + /// AuthParameters implicitly define the credential type. If this argument is nullptr, then modern + /// authentication is implied (e.g. AAD, MSA), and the authentication parameters are inferred from the account hint + /// provided. + /// @param correlationId An identifier that correlates other telemetry events to this event. + /// @param completion Completion (callback) that will be called once this sign in operation has been completed, for + /// both success and failure. It receives an AuthResult instance that contains either an error or contains + /// an account and a credential. + /// + /// **Error statuses returned via completion** + /// - Status::ApiContractViolation + /// - Status::ApplicationCanceled + /// - Status::IncorrectConfiguration + /// - Status::NetworkTemporarilyUnavailable + /// - Status::NoNetwork + /// - Status::ServerTemporarilyUnavailable + /// - Status::UserCanceled + /// - Status::Unexpected + /// + /// @see {@link AuthCompletion} + /// @see {@link AuthParameters} + /// @see {@link AuthResult} + + export function signInInteractively( + accountHint: string | undefined, + authParameters: AuthParameters | undefined, + correlationId: string + ): Promise; + + /// Sign in a user to the app silently with an account inferred from the underlying OS infrastructure, if such + /// an inference is possible. + /// + /// A successful sign-in produces a new account unless the account already exists in the local account store. + /// Additionally, an account profile and image are read from a profile endpoint if one is available and the + /// credentials are sufficient. + /// + /// At this moment, SignInSilently API is of limited use as it only supports Kerberos scenarios on macOS. Support for + /// other scenarios will be added soon. + /// + /// @param authParameters What authentication scheme, authority, etc. should be used for authentication. This can + /// either be created from an HTTP authentication challenge (using the CreateAuthParameters API), or manually. + /// AuthParameters implicitly define the credential type. If this argument is nullptr, then default parameters will + /// be used based on the information inferred from the account hint provided, unless the sign-in is intended for an + /// on-premises resource. On-premises resources cannot be inferred automatically. The developer must specify + /// authentication parameters explicitly for on-premises sign-in scenarios. + /// @param correlationId An idetifier that correlates other telemetry events to this event. + /// @param completion Completion (callback) that will be called once this sign in operation has been completed, for + /// both success and failure. It receives an AuthResult instance that contains either an error or contains + /// an account and a credential. + /// + /// **Error statuses returned via completion** + /// - Status::ApiContractViolation + /// - Status::ApplicationCanceled + /// - Status::NetworkTemporarilyUnavailable + /// - Status::NoNetwork + /// - Status::ServerTemporarilyUnavailable + /// - Status::Unexpected + /// + /// @see {@link AuthCompletion} + /// @see {@link AuthParameters} + /// @see {@link AuthResult} + export function signInSilently( + authParameters: AuthParameters | undefined, + correlationId: string + ): Promise; + + /// Show a prompt for the given account and parameters. + /// + /// If credential acquisition is successful, an account profile and image are read from a profile endpoint if one + /// is available and the credentials are sufficient. + /// + /// **Threading:** calls from a thread other than the main thread, as well as concurrent calls, will result in a + /// {@link Status::ApiContractViolation} error. + /// + /// @param accountId The account id to acquire credentials for. + /// @param authParameters What authentication scheme, authority, etc. should be used for authentication. This can + /// either be created from an HTTP authentication challenge (using the CreateAuthParameters API), or manually. + /// AuthParameters implicitly define the credential type. If this argument is nullptr, then modern + /// authentication is implied (e.g. AAD, MSA), and the authentication parameters are inferred from the account hint + /// provided. + /// @param correlationId An idetifier that correlates other telemetry events to this event. + /// @param completion Completion (callback) that will be called once this sign in operation has been completed, for + /// both success and failure. It receives an AuthResult instance that contains either an error or contains + /// an account and a credential. + /// + /// **Error statuses returned via completion** + /// - Status::ApiContractViolation + /// - Status::ApplicationCanceled + /// - Status::IncorrectConfiguration + /// - Status::NetworkTemporarilyUnavailable + /// - Status::NoNetwork + /// - Status::ServerTemporarilyUnavailable + /// - Status::UserCanceled + /// - Status::Unexpected + /// + /// @see {@link AuthCompletion} + /// @see {@link AuthParameters} + /// @see {@link AuthResult} + export function acquireCredentialInteractively( + accountId: string, + authParameters: AuthParameters, + correlationId: string + ): Promise; + + /// Acquire a credential silently for the given account and parameters. + /// + /// This method will never prompt, but may fail if it cannot silently acquire a credential. If it does fail, the + /// error status may suggest resorting to interactive credential aquisition (see {@link + /// Status::InteractionRequired}). + /// + /// @param accountId The account id to acquire credentials for. + /// @param authParameters What authentication scheme, authority, etc. should be used for authentication. This can + /// either be created from an HTTP authentication challenge (using the CreateAuthParameters API), or manually. + /// AuthParameters implicitly define the credential type. If this argument is nullptr, then modern + /// authentication is implied (e.g. AAD, MSA), and the authentication parameters are inferred from the account hint + /// provided. + /// @param correlationId An idetifier that correlates other telemetry events to this event. + /// @param completion Completion (callback) that will be called once this sign in operation has been completed, for + /// both success and failure. It receives an AuthResult instance that contains either an error or contains + /// an account and a credential. + /// + /// **Error statuses returned via completion** + /// - Status::ApiContractViolation + /// - Status::ApplicationCanceled + /// - Status::IncorrectConfiguration + /// - Status::InteractionRequired + /// - Status::NetworkTemporarilyUnavailable + /// - Status::NoNetwork + /// - Status::ServerTemporarilyUnavailable + /// - Status::Unexpected + /// + /// @see {@link AuthCompletion} + /// @see {@link AuthParameters} + /// @see {@link AuthResult} + export function acquireCredentialSilently( + accountId: string, + authParameters: AuthParameters, + correlationId: string + ): Promise; + + /// Cancels all ongoing tasks and dismisses the UI (if any). + /// Completions for all ongoing tasks are called synchronously, i.e. before ```CancelAllTasks``` returns. + /// Completions for tasks scheduled after ```CancelAllTasks``` was called will not be executed until + /// CancelAllTasks returns control to the caller. + export function cancelAllTasks(): void; + + /// Get an account for a given account id. + /// + /// This API is likely to result in a blocking read from a local persistent store. + /// + /// @param accountId The account id for the desired account + /// @return A shared pointer to the account. + /// @see {@link Account} + export function readAccountById(accountId: string): Promise; + + /// Get all accounts known to OneAuth from local persistent store(s). + /// + /// This API will get accounts from the OneAuth store and any "external" account stores, e.g. the Office or ODSP + /// identity cache. The accounts from "external" stores are de-duplicated against the OneAuth account store. Multiple + /// blocking reads from local persistent stores are likely to result from this call. + /// + /// @return All accounts that OneAuth knows about. + /// @see {@link Account} + /// @see {@link GetAssociatedAccounts} + export function readAllAccounts(): Promise; + + /// Associate an account with a specified application group. + /// + /// Accounts can be associated with applications via application group identifiers. Application group identifiers are + /// arbitrary strings, each associated with a set of applications that choose to help each other identify accounts + /// that they use. + /// + /// This call may result in multiple blocking local I/O operations. + /// + /// @param accountId The account id belonging to the account to associate. + /// @see {@link Account} + /// @see {@link DisassociateAccount} + export function associateAccount(accountId: string): Promise; + + /// Disassociate an account from a specified application group. + /// + /// This call may result in multiple blocking local I/O operations. + /// + /// @param accountId The account id belonging to the account to disassociate. + /// @see {@link Account} + /// @see {@link AssociateAccount} + export function disassociateAccount(accountId: string): Promise; + + /// Get all accounts associated with the specified application groups. + /// + /// This will read accounts from local OneAuth as well as "external" (e.g. Office or ODSP) stores, if any. The + /// accounts from "external" stores are de-duplicated against the OneAuth account store. This call may result in + /// multiple blocking reads from local persistent stores. + /// + /// @param appGroup A list of application groups identifiers. + /// @return An array of account objects. An empty array is returned if no known accounts associated with the + /// specified app groups could be read. + /// @see {@link Account} + /// @see {@link AssociateAccount} + /// @see {@link DisassociateAccount} + /// @see {@link GetAllAccounts} + export function readAssociatedAccounts(appGroup: string[]): Promise; + + /// Get a profile image associated with the specified account. + /// + /// This API does not perform network calls to retrieve the image; the image, if any, is read from a local cache + /// only. This API may result in multiple blocking local I/O operations. + /// + /// @param accountId The account id belonging to the account to get the profile image for. + /// @return An image blob as it was returned by the profile endpoint. If no image associated with the account is + /// locally available, the returned blob is empty. + /// @see {@link Account} + export function readProfileImage(accountId: string): Promise; + + /// Delete all the accounts that OneAuth knows about + /// + /// This is a TEST API and should not be used in production scenarios + /// This is due to the API deleting data that is not exclusive to the calling application. + /// There are no network calls involved in deleting all accounts and as such should not be blocking, allthough + /// given a lot of accounts, it may hang a few milliseconds before returning. + export function testDeleteAllAccounts(): void; + + /// Set the current flights for OneAuth + /// This API needs to be called before initializing OneAuth + /// Once OneAuth has been initialized this will have no effect on the flights that are set till it is + /// shut down and started again. + /// This will override anything that has already been set. + /// + /// This call should not be blocking and should return instantly + /// @param should be an array containing the corresponding @see {@link Flights} to be activated based on the enum defined here + export function setFlights(flights: Array): void; + + /// Get the current flights that have been set on OneAuth + /// + /// This call should not be blocking and should return instantly + /// @return will be an array of flights corresponding to the correct enum of Flights defined here + export function getFlights(): Promise>; + + export enum AudienceType { + Automation = 0, + Preproduction = 1, + Production = 2, + } + + /// Callback function that receives data from OneAuth library that is meant to be sent using Aria SDK. + /// https://authtelemetry.visualstudio.com/Microsoft%20Auth%20Telemetry%20System/_wiki/wikis/Microsoft%20Auth%20Telemetry%20System.wiki/28/Win32?anchor=setting-up-your-telemetry-dispatcher + /// + /// @param data The data that needs to be sent to the telemetry server. + export type TelemetryDispatcher = (data: Data) => void; + + export interface TelemetryConfigurationConstructor { + new ( + audienceType: AudienceType, + sessionId: string, + dispatcher: TelemetryDispatcher | undefined, + allowedResources: string[] + ): TelemetryConfiguration; + } + + export interface TelemetryConfiguration extends TelemetryConfigurationConstructor { + readonly audienceType: AudienceType; + readonly sessionId: string; + readonly telemetryDispatcher: TelemetryDispatcher | undefined; + readonly allowedResources: string[]; + } + + export interface StringMap { + readonly [key: string]: string; + } + + export interface IntMap { + readonly [property: string]: number; + } + + export interface BooleanMap { + readonly [property: string]: boolean; + } + + export interface Data { + readonly name: string; + readonly isInstrumentationError: boolean; + readonly stringMap: StringMap; + readonly intMap: IntMap; + readonly int64Map: IntMap; + readonly boolMap: BooleanMap; + } +} diff --git a/Composer/packages/electron-server/src/main.ts b/Composer/packages/electron-server/src/main.ts index 8243be9cf6..bf0e36771f 100644 --- a/Composer/packages/electron-server/src/main.ts +++ b/Composer/packages/electron-server/src/main.ts @@ -19,9 +19,9 @@ import { isDevelopment } from './utility/env'; import { getUnpackedAsarPath } from './utility/getUnpackedAsarPath'; import { loadLocale, getAppLocale, updateAppLocale } from './utility/locale'; import log from './utility/logger'; -import { getAccessToken, loginAndGetIdToken, OAuthLoginOptions } from './utility/oauthImplicitFlowHelper'; import { isMac, isWindows } from './utility/platform'; import { parseDeepLinkUrl } from './utility/url'; +import { OneAuthService } from './auth/oneAuthService'; const microsoftLogoPath = join(__dirname, '../resources/ms_logo.svg'); let currentAppLocale = getAppLocale().appLocale; @@ -119,25 +119,6 @@ function initializeAppUpdater(settings: AppUpdaterSettings) { log('App updater initialized.'); } -function initAuthListeners(window: Electron.BrowserWindow) { - ipcMain.on('oauth-start-login', async (_ev, options: OAuthLoginOptions, id: number) => { - try { - const idToken = await loginAndGetIdToken(options); - window.webContents.send('oauth-login-complete', idToken, id); - } catch (e) { - window.webContents.send('oauth-login-error', e, id); - } - }); - ipcMain.on('oauth-get-access-token', async (_ev, options: OAuthLoginOptions, idToken: string, id: number) => { - try { - const accessToken = await getAccessToken({ ...options, idToken }); - window.webContents.send('oauth-get-access-token-complete', accessToken, id); - } catch (e) { - window.webContents.send('oauth-get-access-token-error', e, id); - } - }); -} - async function loadServer() { if (!isDevelopment) { // only change paths if packaged electron app @@ -154,7 +135,9 @@ async function loadServer() { log('Starting server...'); const { start } = await import('@bfc/server'); - serverPort = await start(); + serverPort = await start({ + getAccessToken: OneAuthService.getAccessToken.bind(OneAuthService), + }); log(`Server started at port: ${serverPort}`); } @@ -166,7 +149,6 @@ async function main(show = false) { if (process.env.COMPOSER_DEV_TOOLS) { mainWindow.webContents.openDevTools(); } - initAuthListeners(mainWindow); if (isWindows()) { deeplinkUrl = processArgsForWindows(process.argv); diff --git a/Composer/packages/electron-server/src/utility/oauthImplicitFlowHelper.ts b/Composer/packages/electron-server/src/utility/oauthImplicitFlowHelper.ts deleted file mode 100644 index e8655f868e..0000000000 --- a/Composer/packages/electron-server/src/utility/oauthImplicitFlowHelper.ts +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import { randomBytes } from 'crypto'; - -import { BrowserWindow } from 'electron'; - -const composerRedirectUri = 'bfcomposer://oauth'; -const baseUrl = 'https://login.microsoftonline.com/organizations/'; -const implicitEndpoint = 'oauth2/v2.0/authorize'; - -function parseJwt(token: string) { - const base64Payload = token.split('.')[1]; - const payload = Buffer.from(base64Payload, 'base64'); - return JSON.parse(payload.toString()); -} - -function generateNonce(): string { - return randomBytes(32).toString('base64'); -} - -function generateState(clientId: string) { - const state = { - clientId, - guid: randomBytes(32).toString('base64'), - time: Date.now(), - }; - return JSON.stringify(state); -} - -export interface OAuthLoginOptions { - clientId: string; - redirectUri: string; - scopes?: string[]; -} - -export interface OAuthTokenOptions extends OAuthLoginOptions { - idToken: string; -} - -function getLoginUrl(options: OAuthLoginOptions): string { - const { clientId, redirectUri, scopes = [] } = options; - if (scopes.indexOf('openid') === -1) { - scopes.push('openid'); - } - if (scopes.indexOf('profile') === -1) { - scopes.push('profile'); - } - const params = [ - `client_id=${encodeURIComponent(clientId)}`, - `response_type=id_token`, - `redirect_uri=${encodeURIComponent(redirectUri)}`, - `scope=${encodeURIComponent(scopes.join(' '))}`, - `response_mode=fragment`, - `state=${encodeURIComponent(generateState(clientId))}`, - `nonce=${encodeURIComponent(generateNonce())}`, - ].join('&'); - - const url = `${baseUrl}${implicitEndpoint}?${params}`; - return url; -} - -export function getAccessTokenUrl(options: OAuthTokenOptions): string { - const { clientId, idToken, redirectUri, scopes = [] } = options; - const params = [ - `client_id=${encodeURIComponent(clientId)}`, - `response_type=token`, - `redirect_uri=${encodeURIComponent(redirectUri)}`, - `scope=${encodeURIComponent(scopes.join(' '))}`, - `response_mode=fragment`, - `state=${encodeURIComponent(generateState(clientId))}`, - `nonce=${encodeURIComponent(generateNonce())}`, - `prompt=none`, - ]; - const jwt = parseJwt(idToken); - if (jwt.preferred_username) { - params.push(`login_hint=${encodeURIComponent(jwt.preferred_username)}`); - } - - const url = `${baseUrl}${implicitEndpoint}?${params.join('&')}`; - return url; -} - -async function createAccessTokenWindow(url: string): Promise { - const tokenWindow = new BrowserWindow({ width: 400, height: 600, show: false }); - const waitingForAccessToken = monitorWindowForQueryParam(tokenWindow, 'access_token'); - tokenWindow.loadURL(url); - return waitingForAccessToken; -} - -async function createLoginWindow(url: string): Promise { - const loginWindow = new BrowserWindow({ width: 400, height: 600, show: true }); - const waitingForIdToken = monitorWindowForQueryParam(loginWindow, 'id_token'); - loginWindow.loadURL(url); - return waitingForIdToken; -} - -/** Will wait until the specified window redirects to a URL starting with bfcomposer://oauth, - * and then resolve with the desired parameter value or reject with an error message. - * - * @param window The Electron browser window to monitor for redirect events - * @param queryParam The query string parameter to be ripped off the final URL after all redirects are finished - */ -async function monitorWindowForQueryParam(window: BrowserWindow, queryParam: string): Promise { - return new Promise((resolve, reject) => { - window.webContents.on('will-redirect', (event, redirectUrl) => { - if (redirectUrl.startsWith(composerRedirectUri)) { - // We have reached the end of the oauth flow; don't actually complete the redirect. - // Just rip the desired parameters from the url and close the window. - event.preventDefault(); - const parsedUrl = new URL(redirectUrl.replace('#', '?')); - const param = parsedUrl.searchParams.get(queryParam); - if (param) { - window.close(); - resolve(param); - } - const error = parsedUrl.searchParams.get('error'); - const errorDescription = parsedUrl.searchParams.get('error_description'); - if (error || errorDescription) { - window.close(); - reject({ error, errorDescription }); - } - reject({ error: `Unknown error retrieving ${param} from OAuth window` }); - } - }); - }); -} - -/** - * Logs the user in using the OAuth implicit flow and returns an id token - * - * @param id Internal id used by Composer to route the OAuth response back to the client that it originated from - * @returns An object containing the id token and the id of the OAuth client that originated the request - */ -export async function loginAndGetIdToken(options: OAuthLoginOptions): Promise { - const loginUrl = getLoginUrl(options); - const res = await createLoginWindow(loginUrl); - return res; -} - -/** - * Uses an id token to request an access token on behalf of the user and returns token - * - * @param id Internal id used by Composer to route the OAuth response back to the client that it originated from - * @returns An object containing the access token and the id of the OAuth client that originated the request - */ -export async function getAccessToken(options: OAuthTokenOptions): Promise { - const tokenUrl = getAccessTokenUrl(options); - const res = await createAccessTokenWindow(tokenUrl); - return res; -} diff --git a/Composer/packages/electron-server/tsconfig.build.json b/Composer/packages/electron-server/tsconfig.build.json index 831a29d8a2..399e7db994 100644 --- a/Composer/packages/electron-server/tsconfig.build.json +++ b/Composer/packages/electron-server/tsconfig.build.json @@ -1,4 +1,5 @@ { /* Options used for building production code (tests excluded) */ - "extends": "./tsconfig.json" + "extends": "./tsconfig.json", + "exclude": ["scripts/*", "__tests__*"] } diff --git a/Composer/packages/electron-server/tsconfig.json b/Composer/packages/electron-server/tsconfig.json index 7fdc0ce4f6..e9d9bf617d 100644 --- a/Composer/packages/electron-server/tsconfig.json +++ b/Composer/packages/electron-server/tsconfig.json @@ -9,5 +9,10 @@ "@src/*": ["src/*"] } }, - "include": ["src/main.ts", "src/preload.js"], + "include": [ + "src/**/*.ts", + "scripts/*.js", // include scripts to geting proper linting + "__tests__", // intellisense + "src/preload.js" + ] } diff --git a/Composer/packages/extension-client/src/auth/index.ts b/Composer/packages/extension-client/src/auth/index.ts index 7acb67114c..e6c0f67b4c 100644 --- a/Composer/packages/extension-client/src/auth/index.ts +++ b/Composer/packages/extension-client/src/auth/index.ts @@ -1,18 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { AuthParameters } from '@botframework-composer/types'; + import { ComposerGlobalName } from '../common/constants'; -import { OAuthOptions } from './types'; /** Logs the user into Azure for a given client ID with the provided scopes. Returns an ID token. */ -export function login(options: OAuthOptions): Promise { - return window[ComposerGlobalName].login(options); +export function login(params: AuthParameters): Promise { + return window[ComposerGlobalName].login(params); } /** Requests an access token from Azure for a given client ID with the provided scopes. * Returns an access token that can be used to call APIs on behalf of the user. * */ -export function getAccessToken(options: OAuthOptions): Promise { - return window[ComposerGlobalName].getAccessToken(options); +export function getAccessToken(params: AuthParameters): Promise { + return window[ComposerGlobalName].getAccessToken(params); } diff --git a/Composer/packages/extension-client/src/auth/types.ts b/Composer/packages/extension-client/src/auth/types.ts deleted file mode 100644 index d50d60d864..0000000000 --- a/Composer/packages/extension-client/src/auth/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -export interface OAuthOptions { - /** Client ID of the AAD app that the user is authenticating against. */ - clientId: string; - /** List of OAuth scopes that will be granted once the user has authenticated. */ - scopes: string[]; -} diff --git a/Composer/packages/lib/indexers/src/dialogIndexer.ts b/Composer/packages/lib/indexers/src/dialogIndexer.ts index 2e88ecfb5e..57d92b7bb8 100644 --- a/Composer/packages/lib/indexers/src/dialogIndexer.ts +++ b/Composer/packages/lib/indexers/src/dialogIndexer.ts @@ -180,6 +180,7 @@ function parse(id: string, content: any) { const luFile = typeof content.recognizer === 'string' ? content.recognizer : ''; const qnaFile = typeof content.recognizer === 'string' ? content.recognizer : ''; const lgFile = typeof content.generator === 'string' ? content.generator : ''; + const isFormDialog = has(content, 'schema'); // mark as form generated dialog; const diagnostics: Diagnostic[] = []; return { id, @@ -194,6 +195,7 @@ function parse(id: string, content: any) { triggers: extractTriggers(content), intentTriggers: extractIntentTriggers(content), skills: extractReferredSkills(content), + isFormDialog, }; } diff --git a/Composer/packages/lib/shared/__tests__/dialogUtils/validateDialogName.test.ts b/Composer/packages/lib/shared/__tests__/dialogUtils/validateDialogName.test.ts index 8edcbec0a2..0666c0dbc1 100644 --- a/Composer/packages/lib/shared/__tests__/dialogUtils/validateDialogName.test.ts +++ b/Composer/packages/lib/shared/__tests__/dialogUtils/validateDialogName.test.ts @@ -4,7 +4,7 @@ import { validateDialogName } from '../../src/dialogUtils/validateDialogName'; const error = new Error( - "Spaces and special characters are not allowed. Use letters, numbers, -, or _ and don't use number at the beginning." + 'Spaces and special characters are not allowed. Use letters, numbers, -, or _, and begin the name with a letter.' ); const emptyError = new Error('The file name can not be empty'); diff --git a/Composer/packages/server/src/models/bot/__tests__/luResolver.test.ts b/Composer/packages/lib/shared/__tests__/luBuildResolver.test.ts similarity index 99% rename from Composer/packages/server/src/models/bot/__tests__/luResolver.test.ts rename to Composer/packages/lib/shared/__tests__/luBuildResolver.test.ts index d5447e9487..4be4825568 100644 --- a/Composer/packages/server/src/models/bot/__tests__/luResolver.test.ts +++ b/Composer/packages/lib/shared/__tests__/luBuildResolver.test.ts @@ -1,9 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { FileInfo } from '@bfc/shared'; - -import { luImportResolverGenerator, getLUFiles, getQnAFiles } from '../luResolver'; +import { FileInfo } from '../src'; +import { luImportResolverGenerator, getLUFiles, getQnAFiles } from '../src/luBuildResolver'; const files = [ { diff --git a/Composer/packages/lib/shared/package.json b/Composer/packages/lib/shared/package.json index 2feaab041a..ee3a72f7c8 100644 --- a/Composer/packages/lib/shared/package.json +++ b/Composer/packages/lib/shared/package.json @@ -39,6 +39,7 @@ "@botframework-composer/types": "*", "format-message": "6.2.3", "json-schema": "^0.2.5", + "multimatch": "^5.0.0", "nanoid": "^3.1.3", "nanoid-dictionary": "^3.0.0" } diff --git a/Composer/packages/lib/shared/src/dialogUtils/validateDialogName.ts b/Composer/packages/lib/shared/src/dialogUtils/validateDialogName.ts index cf6d2fad1c..6450599600 100644 --- a/Composer/packages/lib/shared/src/dialogUtils/validateDialogName.ts +++ b/Composer/packages/lib/shared/src/dialogUtils/validateDialogName.ts @@ -13,7 +13,7 @@ export const validateDialogName = (name: string) => { if (!nameRegex.test(name)) { throw new Error( formatMessage( - "Spaces and special characters are not allowed. Use letters, numbers, -, or _ and don't use number at the beginning." + 'Spaces and special characters are not allowed. Use letters, numbers, -, or _, and begin the name with a letter.' ) ); } diff --git a/Composer/packages/lib/shared/src/featureFlagUtils/index.ts b/Composer/packages/lib/shared/src/featureFlagUtils/index.ts index 3f03bf3a67..a624678f6a 100644 --- a/Composer/packages/lib/shared/src/featureFlagUtils/index.ts +++ b/Composer/packages/lib/shared/src/featureFlagUtils/index.ts @@ -14,7 +14,7 @@ export type FeatureFlag = { enabled: boolean; }; -export type FeatureFlagKey = 'VA_CREATION' | 'FORM_DIALOG'; +export type FeatureFlagKey = 'VA_CREATION' | 'FORM_DIALOG' | 'REMOTE_TEMPLATE_CREATION_EXPERIENCE'; export type FeatureFlagMap = Record; @@ -22,13 +22,21 @@ export const getDefaultFeatureFlags = (): FeatureFlagMap => ({ VA_CREATION: { displayName: formatMessage('VA Creation'), description: formatMessage('VA template made available in new bot flow.'), - isHidden: false, + isHidden: true, enabled: false, }, FORM_DIALOG: { displayName: formatMessage('Show Form Dialog'), description: formatMessage('Show form dialog editor in the canvas'), - isHidden: false, + isHidden: true, + enabled: false, + }, + REMOTE_TEMPLATE_CREATION_EXPERIENCE: { + displayName: formatMessage('Remote templates'), + description: formatMessage( + 'If turned on then externally stored templates will be selectable in the new bot flow template list' + ), + isHidden: true, enabled: false, }, }); diff --git a/Composer/packages/server/src/models/bot/luResolver.ts b/Composer/packages/lib/shared/src/luBuildResolver.ts similarity index 98% rename from Composer/packages/server/src/models/bot/luResolver.ts rename to Composer/packages/lib/shared/src/luBuildResolver.ts index 64c5a9590c..2b08d2f058 100644 --- a/Composer/packages/server/src/models/bot/luResolver.ts +++ b/Composer/packages/lib/shared/src/luBuildResolver.ts @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { FileInfo } from '@bfc/shared'; +import { FileInfo } from '@botframework-composer/types'; import multimatch from 'multimatch'; -import { Path } from '../../utility/path'; +import { Path } from './path'; // eslint-disable-next-line @typescript-eslint/no-var-requires const luObject = require('@microsoft/bf-lu/lib/parser/lu/lu.js'); diff --git a/Composer/packages/lib/shared/src/path.ts b/Composer/packages/lib/shared/src/path.ts new file mode 100644 index 0000000000..402ee8e2fd --- /dev/null +++ b/Composer/packages/lib/shared/src/path.ts @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import path from 'path'; + +// handle all the path to unix pattern +class PathHandler { + resolve(...params: string[]) { + const pathToTransform = path.join(...params); + if (path.isAbsolute(pathToTransform)) { + return pathToTransform.replace(/\\/g, '/'); + } else { + return path.resolve(...params).replace(/\\/g, '/'); + } + } + relative(from: string, to: string) { + return path.relative(from, to).replace(/\\/g, '/'); + } + basename(param: string, ext: string | undefined = undefined) { + return path.basename(param, ext).replace(/\\/g, '/'); + } + dirname(param: string) { + return path.dirname(param).replace(/\\/g, '/'); + } + extname(param: string) { + return path.extname(param).replace(/\\/g, '/'); + } + join(...params: string[]) { + return path.join(...params).replace(/\\/g, '/'); + } + isAbsolute(param: string) { + return path.isAbsolute(param); + } +} +export const Path = new PathHandler(); diff --git a/Composer/packages/server/package.json b/Composer/packages/server/package.json index d421c48b75..5a83f7aafd 100644 --- a/Composer/packages/server/package.json +++ b/Composer/packages/server/package.json @@ -62,6 +62,8 @@ "@bfc/lg-languageserver": "*", "@bfc/lu-languageserver": "*", "@bfc/shared": "*", + "@botframework-composer/types": "*", + "@microsoft/bf-dialog": "4.11.0-dev.20201025.69cf2b9", "@microsoft/bf-dispatcher": "^4.11.0-beta.20201016.393c6b2", "@microsoft/bf-generate-library": "^4.10.0-daily.20201026.178799", "@microsoft/bf-lu": "^4.11.0-rc.20201030.a9f9b96", @@ -71,7 +73,6 @@ "body-parser": "^1.18.3", "chalk": "^4.0.0", "compression": "^1.7.4", - "cookie-parser": "^1.4.4", "debug": "^4.1.1", "dotenv": "^8.1.0", "ejs": "^2.7.1", @@ -88,7 +89,6 @@ "luis-apis": "2.5.1", "minimatch": "^3.0.4", "morgan": "^1.9.1", - "multimatch": "^4.0.0", "passport": "^0.4.1", "path-to-regexp": "^6.1.0", "portfinder": "1.0.25", diff --git a/Composer/packages/server/schemas/sdk.cs.schema b/Composer/packages/server/schemas/sdk.cs.schema index 436f1ddb96..1a96946001 100644 --- a/Composer/packages/server/schemas/sdk.cs.schema +++ b/Composer/packages/server/schemas/sdk.cs.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.de.schema b/Composer/packages/server/schemas/sdk.de.schema index c5432b0ef2..33b6459732 100644 --- a/Composer/packages/server/schemas/sdk.de.schema +++ b/Composer/packages/server/schemas/sdk.de.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.en-US.schema b/Composer/packages/server/schemas/sdk.en-US.schema index f76b80d46a..245790cd7a 100644 --- a/Composer/packages/server/schemas/sdk.en-US.schema +++ b/Composer/packages/server/schemas/sdk.en-US.schema @@ -3,7 +3,7 @@ "description": "These are all of the kinds that can be created by the loader.", "definitions": { "Microsoft.ActivityTemplate": { - "title": "Microsoft ActivityTemplate", + "title": "Microsoft activity template", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -26,7 +26,7 @@ } }, "Microsoft.AdaptiveDialog": { - "title": "Adaptive Dialog", + "title": "Adaptive dialog", "description": "Flexible, data driven dialog that can adapt to the conversation.", "patternProperties": { "^\\$": { @@ -52,7 +52,7 @@ "description": "Input recognizer that interprets user input into intent and entities." }, "generator": { - "title": "Language Generator", + "title": "Language generator", "description": "Language generator that generates bot responses." }, "selector": { @@ -71,6 +71,9 @@ "title": "Schema", "description": "Schema to fill in.", "anyOf": { + "0": { + "title": "Core schema meta-schema" + }, "1": { "title": "Reference to JSON schema", "description": "Reference to JSON schema .dialog file." @@ -88,7 +91,7 @@ } }, "Microsoft.AgeEntityRecognizer": { - "title": "Age Entity Recognizer", + "title": "Age entity recognizer", "description": "Recognizer which recognizes age.", "patternProperties": { "^\\$": { @@ -108,7 +111,7 @@ } }, "Microsoft.Ask": { - "title": "Send Activity to Ask a question", + "title": "Send activity to ask a question", "description": "This is an action which sends an activity to the user when a response is expected", "patternProperties": { "^\\$": { @@ -118,7 +121,7 @@ }, "properties": { "expectedProperties": { - "title": "Expected Properties", + "title": "Expected properties", "description": "Properties expected from the user.", "items": { "title": "Name", @@ -126,7 +129,7 @@ } }, "defaultOperation": { - "title": "Default Operation", + "title": "Default operation", "description": "Sets the default operation that will be used when no operation is recognized in the response to this Ask." }, "id": { @@ -285,7 +288,7 @@ } }, "activityProcessed": { - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the dialog that is called can process the current activity." }, "resultProperty": { @@ -321,7 +324,7 @@ "description": "Optional condition which if true will disable this action." }, "activityProcessed": { - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the skill will be started using the activity in the current turn context instead of the activity in the Activity property." }, "resultProperty": { @@ -337,11 +340,11 @@ "description": "The callback Url for the skill host." }, "connectionName": { - "title": "OAuth Connection Name (SSO)", + "title": "OAuth connection name (SSO)", "description": "The OAuth Connection Name, that would be used to perform Single SignOn with a skill." }, "skillAppId": { - "title": "Skill App ID", + "title": "Skill App Id", "description": "The Microsoft App ID for the skill." }, "skillEndpoint": { @@ -353,7 +356,7 @@ "description": "The activity to send to the skill." }, "allowInterruptions": { - "title": "Allow Interruptions", + "title": "Allow interruptions", "description": "A boolean expression that determines whether the parent should be allowed to interrupt the skill." }, "$kind": { @@ -367,7 +370,7 @@ } }, "Microsoft.BreakLoop": { - "title": "Break Loop", + "title": "Break loop", "description": "Stop executing this loop", "patternProperties": { "^\\$": { @@ -413,7 +416,7 @@ "description": "Optional condition which if true will disable this action." }, "activityProcessed": { - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the caller dialog is told it should process the current activity." }, "eventName": { @@ -453,7 +456,7 @@ "description": "Optional condition which if true will disable this action." }, "activityProcessed": { - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the caller dialog is told it should process the current activity." }, "eventName": { @@ -474,6 +477,26 @@ } } }, + "Microsoft.ChannelMentionEntityRecognizer": { + "title": "Channel mention entity recognizer", + "description": "Promotes mention entities passed by a channel via the activity.entities into recognizer result.", + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + }, + "$designer": { + "title": "Designer information", + "description": "Extra information for the Bot Framework Composer." + } + } + }, "Microsoft.ChoiceInput": { "title": "Choice input dialog", "description": "Collect information - Pick from a list of choices", @@ -675,7 +698,7 @@ } }, "Microsoft.ConditionalSelector": { - "title": "Conditional Trigger Selector", + "title": "Conditional trigger selector", "description": "Use a rule selector based on a condition", "patternProperties": { "^\\$": { @@ -727,7 +750,7 @@ } }, "choiceOptions": { - "title": "Choice Options", + "title": "Choice options", "description": "Choice Options or expression which provides Choice Options to control display choices to the user.", "oneOf": { "0": { @@ -865,7 +888,7 @@ } }, "Microsoft.ConfirmationEntityRecognizer": { - "title": "Confirmation Entity Recognizer", + "title": "Confirmation entity recognizer", "description": "Recognizer which recognizes confirmation choices (yes/no).", "patternProperties": { "^\\$": { @@ -884,8 +907,44 @@ } } }, + "Microsoft.ContinueConversationLater": { + "title": "Continue conversation later (Queue)", + "description": "Continue conversation at later time (via Azure Storage Queue).", + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "title": "Disabled", + "description": "Optional condition which if true will disable this action." + }, + "date": { + "title": "Date", + "description": "Date in the future as a ISO string when the conversation should continue." + }, + "value": { + "title": "Value", + "description": "Value to send in the activity.value." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + }, + "$designer": { + "title": "Designer information", + "description": "Extra information for the Bot Framework Composer." + } + } + }, "Microsoft.ContinueLoop": { - "title": "Continue Loop", + "title": "Continue loop", "description": "Stop executing this template and continue with the next iteration of the loop.", "patternProperties": { "^\\$": { @@ -913,7 +972,7 @@ } }, "Microsoft.CrossTrainedRecognizerSet": { - "title": "Cross-trained Recognizer Set", + "title": "Cross-trained recognizer set", "description": "Recognizer for selecting between cross trained recognizers.", "patternProperties": { "^\\$": { @@ -941,7 +1000,7 @@ } }, "Microsoft.CurrencyEntityRecognizer": { - "title": "Currency Entity Recognizer", + "title": "Currency entity recognizer", "description": "Recognizer which recognizes currency.", "patternProperties": { "^\\$": { @@ -961,7 +1020,7 @@ } }, "Microsoft.DateTimeEntityRecognizer": { - "title": "DateTime Entity Recognizer", + "title": "Date and time entity recognizer", "description": "Recognizer which recognizes dates and time fragments.", "patternProperties": { "^\\$": { @@ -995,7 +1054,7 @@ }, "properties": { "defaultValue": { - "title": "Default Date", + "title": "Default date", "description": "'Property' will be set to the value or the result of the expression when max turn count is exceeded." }, "value": { @@ -1161,7 +1220,7 @@ } }, "Microsoft.DeleteProperty": { - "title": "Delete Property", + "title": "Delete property", "description": "Delete a property and any value it holds.", "patternProperties": { "^\\$": { @@ -1193,7 +1252,7 @@ } }, "Microsoft.DimensionEntityRecognizer": { - "title": "Dimension Entity Recognizer", + "title": "Dimension entity recognizer", "description": "Recognizer which recognizes dimension.", "patternProperties": { "^\\$": { @@ -1273,7 +1332,7 @@ "description": "Type of change to the array in memory.", "oneOf": { "0": { - "title": "Enum", + "title": "Change type", "description": "Standard change type." } } @@ -1287,7 +1346,7 @@ "description": "Property that holds the array to update." }, "resultProperty": { - "title": "Result Property", + "title": "Result property", "description": "Property to store the result of this action." }, "value": { @@ -1305,7 +1364,7 @@ } }, "Microsoft.EmailEntityRecognizer": { - "title": "Email Entity Recognizer", + "title": "Email entity recognizer", "description": "Recognizer which recognizes email.", "patternProperties": { "^\\$": { @@ -1435,7 +1494,7 @@ } }, "Microsoft.FirstSelector": { - "title": "First Trigger Selector", + "title": "First trigger selector", "description": "Selector for first true rule", "patternProperties": { "^\\$": { @@ -1547,7 +1606,7 @@ } }, "Microsoft.GetActivityMembers": { - "title": "Get Activity Members", + "title": "Get activity members", "description": "Get the members who are participating in an activity. (BotFrameworkAdapter only)", "patternProperties": { "^\\$": { @@ -1565,7 +1624,7 @@ "description": "Property (named location to store information)." }, "activityId": { - "title": "ActivityId", + "title": "Activity Id", "description": "Activity ID or expression to an activityId to use to get the members. If none is defined then the current activity id will be used." }, "disabled": { @@ -1583,7 +1642,7 @@ } }, "Microsoft.GetConversationMembers": { - "title": "Get Converation Members", + "title": "Get conversation members", "description": "Get the members who are participating in an conversation. (BotFrameworkAdapter only)", "patternProperties": { "^\\$": { @@ -1615,7 +1674,7 @@ } }, "Microsoft.GotoAction": { - "title": "Go to Action", + "title": "Go to action", "description": "Go to an an action by id.", "patternProperties": { "^\\$": { @@ -1647,7 +1706,7 @@ } }, "Microsoft.GuidEntityRecognizer": { - "title": "Guid Entity Recognizer", + "title": "Guid entity recognizer", "description": "Recognizer which recognizes guids.", "patternProperties": { "^\\$": { @@ -1667,7 +1726,7 @@ } }, "Microsoft.HashtagEntityRecognizer": { - "title": "Hashtag Entity Recognizer", + "title": "Hashtag entity recognizer", "description": "Recognizer which recognizes Hashtags.", "patternProperties": { "^\\$": { @@ -1750,397 +1809,425 @@ }, "Microsoft.IActivityTemplate": { "title": "Microsoft ActivityTemplates", - "description": "Components which are ActivityTemplate, which is string template, an activity, or a implementation of ActivityTemplate" - }, - "Microsoft.IDialog": { - "title": "Microsoft Dialogs", - "description": "Components which derive from Dialog" - }, - "Microsoft.IEntityRecognizer": { - "title": "Entity Recognizers", - "description": "Components which derive from EntityRecognizer.", - "oneOf": { - "0": { - "title": "Reference to Microsoft.IEntityRecognizer", - "description": "Reference to Microsoft.IEntityRecognizer .dialog file." - } - } - }, - "Microsoft.ILanguageGenerator": { - "title": "Microsoft LanguageGenerator", - "description": "Components which dervie from the LanguageGenerator class" - }, - "Microsoft.IRecognizer": { - "title": "Microsoft Recognizer", - "description": "Components which derive from Recognizer class" - }, - "Microsoft.ITextTemplate": { - "title": "Microsoft TextTemplate", - "description": "Components which derive from TextTemplate class" - }, - "Microsoft.ITrigger": { - "title": "Microsoft Triggers", - "description": "Components which derive from OnCondition class.", + "description": "Components which are ActivityTemplate, which is string template, an activity, or a implementation of ActivityTemplate", "oneOf": { - "0": { - "title": "Reference to Microsoft.ITrigger", - "description": "Reference to Microsoft.ITrigger .dialog file." - } - } - }, - "Microsoft.ITriggerSelector": { - "title": "Selectors", - "description": "Components which derive from TriggerSelector class.", - "oneOf": { - "0": { - "title": "Reference to Microsoft.ITriggerSelector", - "description": "Reference to Microsoft.ITriggerSelector .dialog file." - } - } - }, - "Microsoft.IfCondition": { - "title": "If condition", - "description": "Two-way branch the conversation flow based on a condition.", - "patternProperties": { - "^\\$": { - "title": "Tooling property", - "description": "Open ended property for tooling." - } - }, - "properties": { - "id": { - "title": "Id", - "description": "Optional id for the dialog" - }, - "condition": { - "title": "Condition", - "description": "Expression to evaluate." - }, - "disabled": { - "title": "Disabled", - "description": "Optional condition which if true will disable this action." - }, - "actions": { - "title": "Actions", - "description": "Actions to execute if condition is true." - }, - "elseActions": { - "title": "Else", - "description": "Actions to execute if condition is false." - }, - "$kind": { - "title": "Kind of dialog object", - "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" - }, - "$designer": { - "title": "Designer information", - "description": "Extra information for the Bot Framework Composer." - } - } - }, - "Microsoft.InputDialog": { - "patternProperties": { - "^\\$": { - "title": "Tooling property", - "description": "Open ended property for tooling." - } - }, - "properties": { - "id": { - "title": "Id", - "description": "Optional id for the dialog" - }, - "disabled": { - "title": "Disabled", - "description": "Optional condition which if true will disable this action." - }, - "prompt": { - "title": "Initial prompt", - "description": "Message to send to collect information." - }, - "unrecognizedPrompt": { - "title": "Unrecognized prompt", - "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value." - }, - "invalidPrompt": { - "title": "Invalid prompt", - "description": "Message to send when the user input does not meet any validation expression." - }, - "defaultValueResponse": { - "title": "Default value response", - "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value." - }, - "maxTurnCount": { - "title": "Max turn count", - "description": "Maximum number of re-prompt attempts to collect information." - }, - "validations": { - "title": "Validation expressions", - "description": "Expression to validate user input.", - "items": { - "title": "Condition", - "description": "Expression which needs to met for the input to be considered valid" - } - }, - "property": { - "title": "Property", - "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true)." - }, - "alwaysPrompt": { - "title": "Always prompt", - "description": "Collect information even if the specified 'property' is not empty." - }, - "allowInterruptions": { - "title": "Allow Interruptions", - "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input." - }, - "$kind": { - "title": "Kind of dialog object", - "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" - }, - "$designer": { - "title": "Designer information", - "description": "Extra information for the Bot Framework Composer." - } - } - }, - "Microsoft.IpEntityRecognizer": { - "title": "Ip Entity Recognizer", - "description": "Recognizer which recognizes internet IP patterns (like 192.1.1.1).", - "patternProperties": { - "^\\$": { - "title": "Tooling property", - "description": "Open ended property for tooling." - } - }, - "properties": { - "$kind": { - "title": "Kind of dialog object", - "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" - }, - "$designer": { - "title": "Designer information", - "description": "Extra information for the Bot Framework Composer." - } - } - }, - "Microsoft.LanguagePolicy": { - "title": "Language Policy", - "description": "This represents a policy map for locales lookups to use for language", - "additionalProperties": { - "title": "Per-locale policy", - "description": "Language policy per locale.", - "items": { - "title": "Locale", - "description": "Locale like en-us." - } - }, - "patternProperties": { - "^\\$": { - "title": "Tooling property", - "description": "Open ended property for tooling." - } - }, - "properties": { - "$kind": { - "title": "Kind of dialog object", - "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" - }, - "$designer": { - "title": "Designer information", - "description": "Extra information for the Bot Framework Composer." - } - } - }, - "Microsoft.LogAction": { - "title": "Log to console", - "description": "Log a message to the host application. Send a TraceActivity to Bot Framework Emulator (optional).", - "patternProperties": { - "^\\$": { - "title": "Tooling property", - "description": "Open ended property for tooling." - } - }, - "properties": { - "id": { - "title": "Id", - "description": "Optional id for the dialog" - }, - "disabled": { - "title": "Disabled", - "description": "Optional condition which if true will disable this action." - }, - "text": { - "title": "Text", - "description": "Information to log." - }, - "label": { - "title": "Label", - "description": "Label for the trace activity (used to identify it in a list of trace activities.)" - }, - "traceActivity": { - "title": "Send Trace Activity", - "description": "If true, automatically sends a TraceActivity (view in Bot Framework Emulator)." - }, - "$kind": { - "title": "Kind of dialog object", - "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" - }, - "$designer": { - "title": "Designer information", - "description": "Extra information for the Bot Framework Composer." - } - } - }, - "Microsoft.LuisRecognizer": { - "title": "LUIS Recognizer", - "description": "LUIS recognizer.", - "patternProperties": { - "^\\$": { - "title": "Tooling property", - "description": "Open ended property for tooling." - } - }, - "properties": { - "id": { - "title": "Id", - "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." - }, - "applicationId": { - "title": "LUIS Application ID", - "description": "Application ID for your model from the LUIS service." - }, - "endpoint": { - "title": "LUIS Endpoint", - "description": "Endpoint to use for LUIS service like https://westus.api.cognitive.microsoft.com." - }, - "endpointKey": { - "title": "LUIS prediction key", - "description": "LUIS prediction key used to call endpoint." - }, - "externalEntityRecognizer": { - "title": "External Entity Recognizer", - "description": "Entities recognized by this recognizer will be passed to LUIS as external entities." - }, - "dynamicLists": { - "title": "Dynamic lists", - "description": "Runtime defined entity lists.", - "items": { - "title": "Entity list", - "description": "Lists of canonical values and synonyms for an entity.", - "properties": { - "entity": { - "title": "Entity", - "description": "Entity to extend with a dynamic list." - }, - "list": { - "title": "Dynamic list", - "description": "List of canonical forms and synonyms.", - "items": { - "title": "List entry", - "description": "Canonical form and synonynms.", - "properties": { - "canonicalForm": { - "title": "Canonical form", - "description": "Resolution if any synonym matches." - }, - "synonyms": { - "title": "Synonyms", - "description": "List of synonyms for a canonical form.", - "items": { - "title": "Synonym", - "description": "Synonym for canonical form." + "1": { + "description": "An Activity is the basic communication type for the Bot Framework 3.0 protocol.", + "title": "Activity", + "properties": { + "type": { + "description": "Contains the activity type. Possible values include: 'message', 'contactRelationUpdate',\n'conversationUpdate', 'typing', 'endOfConversation', 'event', 'invoke', 'deleteUserData',\n'messageUpdate', 'messageDelete', 'installationUpdate', 'messageReaction', 'suggestion',\n'trace', 'handoff'", + "title": "type" + }, + "id": { + "description": "Contains an ID that uniquely identifies the activity on the channel.", + "title": "id" + }, + "timestamp": { + "description": "Contains the date and time that the message was sent, in UTC, expressed in ISO-8601 format.", + "title": "timestamp" + }, + "localTimestamp": { + "description": "Contains the date and time that the message was sent, in local time, expressed in ISO-8601\nformat.\nFor example, 2016-09-23T13:07:49.4714686-07:00.", + "title": "localTimestamp" + }, + "localTimezone": { + "description": "Contains the name of the timezone in which the message, in local time, expressed in IANA Time\nZone database format.\nFor example, America/Los_Angeles.", + "title": "localTimezone" + }, + "serviceUrl": { + "description": "Contains the URL that specifies the channel's service endpoint. Set by the channel.", + "title": "serviceUrl" + }, + "channelId": { + "description": "Contains an ID that uniquely identifies the channel. Set by the channel.", + "title": "channelId" + }, + "from": { + "description": "Identifies the sender of the message.", + "title": "from" + }, + "conversation": { + "description": "Identifies the conversation to which the activity belongs.", + "title": "conversation", + "properties": { + "isGroup": { + "description": "Indicates whether the conversation contains more than two participants at the time the\nactivity was generated", + "title": "isGroup" + }, + "conversationType": { + "description": "Indicates the type of the conversation in channels that distinguish between conversation types", + "title": "conversationType" + }, + "id": { + "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or\n123456)", + "title": "id" + }, + "name": { + "description": "Display friendly name", + "title": "name" + }, + "aadObjectId": { + "description": "This account's object ID within Azure Active Directory (AAD)", + "title": "aadObjectId" + }, + "role": { + "description": "Role of the entity behind the account (Example: User, Bot, etc.). Possible values include:\n'user', 'bot'", + "title": "role" + } + } + }, + "recipient": { + "description": "Identifies the recipient of the message.", + "title": "recipient" + }, + "textFormat": { + "description": "Format of text fields Default:markdown. Possible values include: 'markdown', 'plain', 'xml'", + "title": "textFormat" + }, + "attachmentLayout": { + "description": "The layout hint for multiple attachments. Default: list. Possible values include: 'list',\n'carousel'", + "title": "attachmentLayout" + }, + "membersAdded": { + "description": "The collection of members added to the conversation.", + "title": "membersAdded", + "items": { + "description": "Channel account information needed to route a message", + "title": "ChannelAccount", + "properties": { + "id": { + "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or\n123456)", + "title": "id" + }, + "name": { + "description": "Display friendly name", + "title": "name" + }, + "aadObjectId": { + "description": "This account's object ID within Azure Active Directory (AAD)", + "title": "aadObjectId" + }, + "role": { + "description": "Role of the entity behind the account (Example: User, Bot, etc.). Possible values include:\n'user', 'bot'", + "title": "role" + } + } + } + }, + "membersRemoved": { + "description": "The collection of members removed from the conversation.", + "title": "membersRemoved" + }, + "reactionsAdded": { + "description": "The collection of reactions added to the conversation.", + "title": "reactionsAdded", + "items": { + "description": "Message reaction object", + "title": "MessageReaction", + "properties": { + "type": { + "description": "Message reaction type. Possible values include: 'like', 'plusOne'", + "title": "type" + } + } + } + }, + "reactionsRemoved": { + "description": "The collection of reactions removed from the conversation.", + "title": "reactionsRemoved" + }, + "topicName": { + "description": "The updated topic name of the conversation.", + "title": "topicName" + }, + "historyDisclosed": { + "description": "Indicates whether the prior history of the channel is disclosed.", + "title": "historyDisclosed" + }, + "locale": { + "description": "A locale name for the contents of the text field.\nThe locale name is a combination of an ISO 639 two- or three-letter culture code associated\nwith a language\nand an ISO 3166 two-letter subculture code associated with a country or region.\nThe locale name can also correspond to a valid BCP-47 language tag.", + "title": "locale" + }, + "text": { + "description": "The text content of the message.", + "title": "text" + }, + "speak": { + "description": "The text to speak.", + "title": "speak" + }, + "inputHint": { + "description": "Indicates whether your bot is accepting,\nexpecting, or ignoring user input after the message is delivered to the client. Possible\nvalues include: 'acceptingInput', 'ignoringInput', 'expectingInput'", + "title": "inputHint" + }, + "summary": { + "description": "The text to display if the channel cannot render cards.", + "title": "summary" + }, + "suggestedActions": { + "description": "The suggested actions for the activity.", + "title": "suggestedActions", + "properties": { + "to": { + "description": "Ids of the recipients that the actions should be shown to. These Ids are relative to the\nchannelId and a subset of all recipients of the activity", + "title": "to", + "items": { + "title": "Id", + "description": "Id of recipient." + } + }, + "actions": { + "description": "Actions that can be shown to the user", + "title": "actions", + "items": { + "description": "A clickable action", + "title": "CardAction", + "properties": { + "type": { + "description": "The type of action implemented by this button. Possible values include: 'openUrl', 'imBack',\n'postBack', 'playAudio', 'playVideo', 'showImage', 'downloadFile', 'signin', 'call',\n'payment', 'messageBack'", + "title": "type" + }, + "title": { + "description": "Text description which appears on the button", + "title": "title" + }, + "image": { + "description": "Image URL which will appear on the button, next to text label", + "title": "image" + }, + "text": { + "description": "Text for this action", + "title": "text" + }, + "displayText": { + "description": "(Optional) text to display in the chat feed if the button is clicked", + "title": "displayText" + }, + "value": { + "description": "Supplementary parameter for action. Content of this property depends on the ActionType", + "title": "value" + }, + "channelData": { + "description": "Channel-specific data associated with this action", + "title": "channelData" } } } } } - } - } - }, - "predictionOptions": { - "title": "Prediction options", - "description": "Options to control LUIS prediction behavior.", - "properties": { - "includeAllIntents": { - "title": "Include all intents", - "description": "True for all intents, false for only top intent." }, - "includeInstanceData": { - "title": "Include $instance", - "description": "True to include $instance metadata in the LUIS response." + "attachments": { + "description": "Attachments", + "title": "attachments", + "items": { + "description": "An attachment within an activity", + "title": "Attachment", + "properties": { + "contentType": { + "description": "mimetype/Contenttype for the file", + "title": "contentType" + }, + "contentUrl": { + "description": "Content Url", + "title": "contentUrl" + }, + "content": { + "description": "Embedded content", + "title": "content" + }, + "name": { + "description": "(OPTIONAL) The name of the attachment", + "title": "name" + }, + "thumbnailUrl": { + "description": "(OPTIONAL) Thumbnail associated with attachment", + "title": "thumbnailUrl" + } + } + } }, - "log": { - "title": "Log utterances", - "description": "True to log utterances on LUIS service." + "entities": { + "description": "Represents the entities that were mentioned in the message.", + "title": "entities", + "items": { + "description": "Metadata object pertaining to an activity", + "title": "Entity", + "properties": { + "type": { + "description": "Type of this entity (RFC 3987 IRI)", + "title": "type" + } + } + } }, - "preferExternalEntities": { - "title": "Prefer External Entities", - "description": "True to prefer external entities to those generated by LUIS models." + "channelData": { + "description": "Contains channel-specific content.", + "title": "channelData" }, - "slot": { - "title": "Slot", - "description": "Slot to use for talking to LUIS service like production or staging." + "action": { + "description": "Indicates whether the recipient of a contactRelationUpdate was added or removed from the\nsender's contact list.", + "title": "action" }, - "version": { - "title": "Version", - "description": "LUIS application version to use." - } - } - }, - "$kind": { - "title": "Kind of dialog object", - "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" - }, - "$designer": { - "title": "Designer information", - "description": "Extra information for the Bot Framework Composer." - } - } - }, - "Microsoft.MentionEntityRecognizer": { - "title": "Mentions Entity Recognizer", - "description": "Recognizer which recognizes @Mentions", - "patternProperties": { - "^\\$": { - "title": "Tooling property", - "description": "Open ended property for tooling." + "replyToId": { + "description": "Contains the ID of the message to which this message is a reply.", + "title": "replyToId" + }, + "label": { + "description": "A descriptive label for the activity.", + "title": "label" + }, + "valueType": { + "description": "The type of the activity's value object.", + "title": "valueType" + }, + "value": { + "description": "A value that is associated with the activity.", + "title": "value" + }, + "name": { + "description": "The name of the operation associated with an invoke or event activity.", + "title": "name" + }, + "relatesTo": { + "description": "A reference to another conversation or activity.", + "title": "relatesTo", + "properties": { + "activityId": { + "description": "(Optional) ID of the activity to refer to", + "title": "activityId" + }, + "user": { + "description": "(Optional) User participating in this conversation", + "title": "user" + }, + "bot": { + "description": "Bot participating in this conversation", + "title": "bot" + }, + "conversation": { + "description": "Conversation reference", + "title": "conversation" + }, + "channelId": { + "description": "Channel ID", + "title": "channelId" + }, + "serviceUrl": { + "description": "Service endpoint where operations concerning the referenced conversation may be performed", + "title": "serviceUrl" + } + } + }, + "code": { + "description": "The a code for endOfConversation activities that indicates why the conversation ended.\nPossible values include: 'unknown', 'completedSuccessfully', 'userCancelled', 'botTimedOut',\n'botIssuedInvalidMessage', 'channelFailed'", + "title": "code" + }, + "expiration": { + "description": "The time at which the activity should be considered to be \"expired\" and should not be\npresented to the recipient.", + "title": "expiration" + }, + "importance": { + "description": "The importance of the activity. Possible values include: 'low', 'normal', 'high'", + "title": "importance" + }, + "deliveryMode": { + "description": "A delivery hint to signal to the recipient alternate delivery paths for the activity.\nThe default delivery mode is \"default\". Possible values include: 'normal', 'notification'", + "title": "deliveryMode" + }, + "listenFor": { + "description": "List of phrases and references that speech and language priming systems should listen for", + "title": "listenFor", + "items": { + "title": "Phrase", + "description": "Phrase to listen for." + } + }, + "textHighlights": { + "description": "The collection of text fragments to highlight when the activity contains a ReplyToId value.", + "title": "textHighlights", + "items": { + "description": "Refers to a substring of content within another field", + "title": "TextHighlight", + "properties": { + "text": { + "description": "Defines the snippet of text to highlight", + "title": "text" + }, + "occurrence": { + "description": "Occurrence of the text field within the referenced text, if multiple exist.", + "title": "occurrence" + } + } + } + }, + "semanticAction": { + "description": "An optional programmatic action accompanying this request", + "title": "semanticAction", + "properties": { + "id": { + "description": "ID of this action", + "title": "id" + }, + "entities": { + "description": "Entities associated with this action", + "title": "entities" + } + } + } + } } - }, - "properties": { - "$kind": { - "title": "Kind of dialog object", - "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" - }, - "$designer": { - "title": "Designer information", - "description": "Extra information for the Bot Framework Composer." + } + }, + "Microsoft.IDialog": { + "title": "Microsoft dialogs", + "description": "Components which derive from Dialog" + }, + "Microsoft.IEntityRecognizer": { + "title": "Entity recognizers", + "description": "Components which derive from EntityRecognizer.", + "oneOf": { + "0": { + "title": "Reference to Microsoft.IEntityRecognizer", + "description": "Reference to Microsoft.IEntityRecognizer .dialog file." } } }, - "Microsoft.MostSpecificSelector": { - "title": "Most Specific Trigger Selector", - "description": "Select most specific true events with optional additional selector", - "patternProperties": { - "^\\$": { - "title": "Tooling property", - "description": "Open ended property for tooling." + "Microsoft.ILanguageGenerator": { + "title": "Microsoft LanguageGenerator", + "description": "Components which dervie from the LanguageGenerator class" + }, + "Microsoft.IRecognizer": { + "title": "Microsoft recognizer", + "description": "Components which derive from Recognizer class" + }, + "Microsoft.ITextTemplate": { + "title": "Microsoft TextTemplate", + "description": "Components which derive from TextTemplate class" + }, + "Microsoft.ITrigger": { + "title": "Microsoft Triggers", + "description": "Components which derive from OnCondition class.", + "oneOf": { + "0": { + "title": "Reference to Microsoft.ITrigger", + "description": "Reference to Microsoft.ITrigger .dialog file." } - }, - "properties": { - "$kind": { - "title": "Kind of dialog object", - "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" - }, - "$designer": { - "title": "Designer information", - "description": "Extra information for the Bot Framework Composer." + } + }, + "Microsoft.ITriggerSelector": { + "title": "Selectors", + "description": "Components which derive from TriggerSelector class.", + "oneOf": { + "0": { + "title": "Reference to Microsoft.ITriggerSelector", + "description": "Reference to Microsoft.ITriggerSelector .dialog file." } } }, - "Microsoft.MultiLanguageRecognizer": { - "title": "Multi-language recognizer", - "description": "Configure one recognizer per language and the specify the language fallback policy.", + "Microsoft.IfCondition": { + "title": "If condition", + "description": "Two-way branch the conversation flow based on a condition.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2150,36 +2237,24 @@ "properties": { "id": { "title": "Id", - "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." + "description": "Optional id for the dialog" }, - "languagePolicy": { - "title": "Language policy", - "description": "Defines fall back languages to try per user input language." + "condition": { + "title": "Condition", + "description": "Expression to evaluate." }, - "recognizers": { - "title": "Recognizers", - "description": "Map of language -> Recognizer" + "disabled": { + "title": "Disabled", + "description": "Optional condition which if true will disable this action." }, - "$kind": { - "title": "Kind of dialog object", - "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + "actions": { + "title": "Actions", + "description": "Actions to execute if condition is true." + }, + "elseActions": { + "title": "Else", + "description": "Actions to execute if condition is false." }, - "$designer": { - "title": "Designer information", - "description": "Extra information for the Bot Framework Composer." - } - } - }, - "Microsoft.NumberEntityRecognizer": { - "title": "Number Entity Recognizer", - "description": "Recognizer which recognizes numbers.", - "patternProperties": { - "^\\$": { - "title": "Tooling property", - "description": "Open ended property for tooling." - } - }, - "properties": { "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" @@ -2190,9 +2265,7 @@ } } }, - "Microsoft.NumberInput": { - "title": "Number input dialog", - "description": "Collect information - Ask for a number.", + "Microsoft.InputDialog": { "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2200,22 +2273,6 @@ } }, "properties": { - "defaultValue": { - "title": "Default value", - "description": "'Property' will be set to the value of this expression when max turn count is exceeded." - }, - "value": { - "title": "Value", - "description": "'Property' will be set to the value of this expression unless it evaluates to null." - }, - "outputFormat": { - "title": "Output format", - "description": "Expression to format the number output." - }, - "defaultLocale": { - "title": "Default locale", - "description": "Default locale to use if there is no locale available.." - }, "id": { "title": "Id", "description": "Optional id for the dialog" @@ -2274,9 +2331,9 @@ } } }, - "Microsoft.NumberRangeEntityRecognizer": { - "title": "NumberRange Entity Recognizer", - "description": "Recognizer which recognizes ranges of numbers (Example:2 to 5).", + "Microsoft.IpEntityRecognizer": { + "title": "IP entity recognizer", + "description": "Recognizer which recognizes internet IP patterns (like 192.1.1.1).", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2294,9 +2351,17 @@ } } }, - "Microsoft.OAuthInput": { - "title": "OAuthInput Dialog", - "description": "Collect login information.", + "Microsoft.LanguagePolicy": { + "title": "Language policy", + "description": "This represents a policy map for locales lookups to use for language", + "additionalProperties": { + "title": "Per-locale policy", + "description": "Language policy per locale.", + "items": { + "title": "Locale", + "description": "Locale like en-us." + } + }, "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2304,9 +2369,29 @@ } }, "properties": { - "connectionName": { - "title": "Connection name", - "description": "The connection name configured in Azure Web App Bot OAuth settings." + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + }, + "$designer": { + "title": "Designer information", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.LogAction": { + "title": "Log to console", + "description": "Log a message to the host application. Send a TraceActivity to Bot Framework Emulator (optional).", + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "title": "Id", + "description": "Optional id for the dialog" }, "disabled": { "title": "Disabled", @@ -2314,43 +2399,15 @@ }, "text": { "title": "Text", - "description": "Text shown in the OAuth signin card." + "description": "Information to log." }, - "title": { - "title": "Title", - "description": "Title shown in the OAuth signin card." + "label": { + "title": "Label", + "description": "Label for the trace activity (used to identify it in a list of trace activities.)" }, - "timeout": { - "title": "Timeout", - "description": "Time out setting for the OAuth signin card." - }, - "property": { - "title": "Token property", - "description": "Property to store the OAuth token result." - }, - "invalidPrompt": { - "title": "Invalid prompt", - "description": "Message to send if user response is invalid." - }, - "defaultValueResponse": { - "title": "Default value response", - "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value." - }, - "maxTurnCount": { - "title": "Max turn count", - "description": "Maximum number of re-prompt attempts to collect information." - }, - "defaultValue": { - "title": "Default value", - "description": "Expression to examine on each turn of the conversation as possible value to the property." - }, - "allowInterruptions": { - "title": "Allow Interruptions", - "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input." - }, - "alwaysPrompt": { - "title": "Always prompt", - "description": "Collect information even if the specified 'property' is not empty." + "traceActivity": { + "title": "Send trace activity", + "description": "If true, automatically sends a TraceActivity (view in Bot Framework Emulator)." }, "$kind": { "title": "Kind of dialog object", @@ -2362,9 +2419,9 @@ } } }, - "Microsoft.OnActivity": { - "title": "On activity", - "description": "Actions to perform on receipt of a generic activity.", + "Microsoft.LuisRecognizer": { + "title": "LUIS Recognizer", + "description": "LUIS recognizer.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2372,25 +2429,91 @@ } }, "properties": { - "type": { - "title": "Activity type", - "description": "The Activity.Type to match" + "id": { + "title": "Id", + "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." }, - "condition": { - "title": "Condition", - "description": "Condition (expression)." + "applicationId": { + "title": "LUIS application id", + "description": "Application ID for your model from the LUIS service." }, - "actions": { - "title": "Actions", - "description": "Sequence of actions to execute." + "version": { + "title": "LUIS version", + "description": "Optional version to target. If null then predictionOptions.Slot is used." }, - "priority": { - "title": "Priority", - "description": "Priority for trigger with 0 being the highest and < 0 ignored." + "endpoint": { + "title": "LUIS endpoint", + "description": "Endpoint to use for LUIS service like https://westus.api.cognitive.microsoft.com." }, - "runOnce": { - "title": "Run Once", - "description": "True if rule should run once per unique conditions" + "endpointKey": { + "title": "LUIS prediction key", + "description": "LUIS prediction key used to call endpoint." + }, + "externalEntityRecognizer": { + "title": "External entity recognizer", + "description": "Entities recognized by this recognizer will be passed to LUIS as external entities." + }, + "dynamicLists": { + "title": "Dynamic lists", + "description": "Runtime defined entity lists.", + "items": { + "title": "Entity list", + "description": "Lists of canonical values and synonyms for an entity.", + "properties": { + "entity": { + "title": "Entity", + "description": "Entity to extend with a dynamic list." + }, + "list": { + "title": "Dynamic list", + "description": "List of canonical forms and synonyms.", + "items": { + "title": "List entry", + "description": "Canonical form and synonynms.", + "properties": { + "canonicalForm": { + "title": "Canonical form", + "description": "Resolution if any synonym matches." + }, + "synonyms": { + "title": "Synonyms", + "description": "List of synonyms for a canonical form.", + "items": { + "title": "Synonym", + "description": "Synonym for canonical form." + } + } + } + } + } + } + } + }, + "predictionOptions": { + "title": "Prediction options", + "description": "Options to control LUIS prediction behavior.", + "properties": { + "includeAllIntents": { + "title": "Include all intents", + "description": "True for all intents, false for only top intent." + }, + "includeInstanceData": { + "title": "Include $instance", + "description": "True to include $instance metadata in the LUIS response." + }, + "log": { + "title": "Log utterances", + "description": "True to log utterances on LUIS service." + }, + "preferExternalEntities": { + "title": "Prefer external entities", + "description": "True to prefer external entities to those generated by LUIS models." + }, + "slot": { + "title": "Slot", + "description": "Slot to use for talking to LUIS service like production or staging." + } + } }, "$kind": { "title": "Kind of dialog object", @@ -2402,9 +2525,9 @@ } } }, - "Microsoft.OnAssignEntity": { - "title": "On entity assignment", - "description": "Actions to take when an entity should be assigned to a property.", + "Microsoft.MentionEntityRecognizer": { + "title": "Mentions entity recognizer", + "description": "Recognizer which recognizes @Mentions", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2412,34 +2535,6 @@ } }, "properties": { - "property": { - "title": "Property", - "description": "Property that will be set after entity is selected." - }, - "entity": { - "title": "Entity", - "description": "Entity being put into property" - }, - "operation": { - "title": "Operation", - "description": "Operation for assigning entity." - }, - "condition": { - "title": "Condition", - "description": "Condition (expression)." - }, - "actions": { - "title": "Actions", - "description": "Sequence of actions to execute." - }, - "priority": { - "title": "Priority", - "description": "Priority for trigger with 0 being the highest and < 0 ignored." - }, - "runOnce": { - "title": "Run Once", - "description": "True if rule should run once per unique conditions" - }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" @@ -2450,9 +2545,9 @@ } } }, - "Microsoft.OnBeginDialog": { - "title": "On begin dialog", - "description": "Actions to perform when this dialog begins.", + "Microsoft.MostSpecificSelector": { + "title": "Most specific trigger selector", + "description": "Select most specific true events with optional additional selector", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2460,22 +2555,6 @@ } }, "properties": { - "condition": { - "title": "Condition", - "description": "Condition (expression)." - }, - "actions": { - "title": "Actions", - "description": "Sequence of actions to execute." - }, - "priority": { - "title": "Priority", - "description": "Priority for trigger with 0 being the highest and < 0 ignored." - }, - "runOnce": { - "title": "Run Once", - "description": "True if rule should run once per unique conditions" - }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" @@ -2486,9 +2565,9 @@ } } }, - "Microsoft.OnCancelDialog": { - "title": "On cancel dialog", - "description": "Actions to perform on cancel dialog event.", + "Microsoft.MultiLanguageRecognizer": { + "title": "Multi-language recognizer", + "description": "Configure one recognizer per language and the specify the language fallback policy.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2496,21 +2575,17 @@ } }, "properties": { - "condition": { - "title": "Condition", - "description": "Condition (expression)." - }, - "actions": { - "title": "Actions", - "description": "Sequence of actions to execute." + "id": { + "title": "Id", + "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." }, - "priority": { - "title": "Priority", - "description": "Priority for trigger with 0 being the highest and < 0 ignored." + "languagePolicy": { + "title": "Language policy", + "description": "Defines fall back languages to try per user input language." }, - "runOnce": { - "title": "Run Once", - "description": "True if rule should run once per unique conditions" + "recognizers": { + "title": "Recognizers", + "description": "Map of language -> Recognizer" }, "$kind": { "title": "Kind of dialog object", @@ -2522,9 +2597,9 @@ } } }, - "Microsoft.OnChooseEntity": { - "title": "On choose entity", - "description": "Actions to be performed when an entity value needs to be resolved.", + "Microsoft.NumberEntityRecognizer": { + "title": "Number entity recognizer", + "description": "Recognizer which recognizes numbers.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2532,30 +2607,6 @@ } }, "properties": { - "property": { - "title": "Property to be set", - "description": "Property that will be set after entity is selected." - }, - "entity": { - "title": "Ambiguous entity", - "description": "Ambiguous entity" - }, - "condition": { - "title": "Condition", - "description": "Condition (expression)." - }, - "actions": { - "title": "Actions", - "description": "Sequence of actions to execute." - }, - "priority": { - "title": "Priority", - "description": "Priority for trigger with 0 being the highest and < 0 ignored." - }, - "runOnce": { - "title": "Run Once", - "description": "True if rule should run once per unique conditions" - }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" @@ -2566,9 +2617,9 @@ } } }, - "Microsoft.OnChooseIntent": { - "title": "On ambigious intent", - "description": "Actions to perform on when an intent is ambigious.", + "Microsoft.NumberInput": { + "title": "Number input dialog", + "description": "Collect information - Ask for a number.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2576,29 +2627,69 @@ } }, "properties": { - "intents": { - "title": "Intents", - "description": "Intents that must be in the ChooseIntent result for this condition to trigger.", - "items": { - "title": "Intent", - "description": "Intent name to trigger on." - } + "defaultValue": { + "title": "Default value", + "description": "'Property' will be set to the value of this expression when max turn count is exceeded." }, - "condition": { - "title": "Condition", - "description": "Condition (expression)." + "value": { + "title": "Value", + "description": "'Property' will be set to the value of this expression unless it evaluates to null." }, - "actions": { - "title": "Actions", - "description": "Sequence of actions to execute." + "outputFormat": { + "title": "Output format", + "description": "Expression to format the number output." }, - "priority": { - "title": "Priority", - "description": "Priority for trigger with 0 being the highest and < 0 ignored." + "defaultLocale": { + "title": "Default locale", + "description": "Default locale to use if there is no locale available.." }, - "runOnce": { - "title": "Run Once", - "description": "True if rule should run once per unique conditions" + "id": { + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "title": "Disabled", + "description": "Optional condition which if true will disable this action." + }, + "prompt": { + "title": "Initial prompt", + "description": "Message to send to collect information." + }, + "unrecognizedPrompt": { + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value." + }, + "invalidPrompt": { + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression." + }, + "defaultValueResponse": { + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value." + }, + "maxTurnCount": { + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information." + }, + "validations": { + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid" + } + }, + "property": { + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true)." + }, + "alwaysPrompt": { + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty." + }, + "allowInterruptions": { + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input." }, "$kind": { "title": "Kind of dialog object", @@ -2610,9 +2701,9 @@ } } }, - "Microsoft.OnChooseProperty": { - "title": "On choose property", - "description": "Actions to take when there are multiple possible mappings of entities to properties.", + "Microsoft.NumberRangeEntityRecognizer": { + "title": "Number range entity recognizer", + "description": "Recognizer which recognizes ranges of numbers (Example:2 to 5).", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2620,41 +2711,73 @@ } }, "properties": { - "entity": { - "title": "Entity being assigned", - "description": "Entity being assigned to property choice" + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" }, - "properties": { - "title": "Possible properties", - "description": "Properties to be chosen between.", - "items": { - "title": "Property name", - "description": "Possible property to choose." - } + "$designer": { + "title": "Designer information", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OAuthInput": { + "title": "OAuthInput Dialog", + "description": "Collect login information.", + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "connectionName": { + "title": "Connection name", + "description": "The connection name configured in Azure Web App Bot OAuth settings." }, - "entities": { - "title": "Entities", - "description": "Ambiguous entity names.", - "items": { - "title": "Entity name", - "description": "Entity name being chosen between." - } + "disabled": { + "title": "Disabled", + "description": "Optional condition which if true will disable this action." }, - "condition": { - "title": "Condition", - "description": "Condition (expression)." + "text": { + "title": "Text", + "description": "Text shown in the OAuth signin card." }, - "actions": { - "title": "Actions", - "description": "Sequence of actions to execute." + "title": { + "title": "Title", + "description": "Title shown in the OAuth signin card." }, - "priority": { - "title": "Priority", - "description": "Priority for trigger with 0 being the highest and < 0 ignored." + "timeout": { + "title": "Timeout", + "description": "Time out setting for the OAuth signin card." }, - "runOnce": { - "title": "Run Once", - "description": "True if rule should run once per unique conditions" + "property": { + "title": "Token property", + "description": "Property to store the OAuth token result." + }, + "invalidPrompt": { + "title": "Invalid prompt", + "description": "Message to send if user response is invalid." + }, + "defaultValueResponse": { + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value." + }, + "maxTurnCount": { + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information." + }, + "defaultValue": { + "title": "Default value", + "description": "Expression to examine on each turn of the conversation as possible value to the property." + }, + "allowInterruptions": { + "title": "Allow interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input." + }, + "alwaysPrompt": { + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty." }, "$kind": { "title": "Kind of dialog object", @@ -2666,9 +2789,9 @@ } } }, - "Microsoft.OnCondition": { - "title": "On condition", - "description": "Actions to perform when specified condition is true.", + "Microsoft.OnActivity": { + "title": "On activity", + "description": "Actions to perform on receipt of a generic activity.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2676,6 +2799,10 @@ } }, "properties": { + "type": { + "title": "Activity type", + "description": "The Activity.Type to match" + }, "condition": { "title": "Condition", "description": "Condition (expression)." @@ -2702,9 +2829,9 @@ } } }, - "Microsoft.OnContinueConversation": { - "title": "On Continue Conversation", - "description": "Actions to perform when a conversation is started up again from a ContinueConversationLater action.", + "Microsoft.OnAssignEntity": { + "title": "On entity assignment", + "description": "Actions to take when an entity should be assigned to a property.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2712,6 +2839,18 @@ } }, "properties": { + "property": { + "title": "Property", + "description": "Property that will be set after entity is selected." + }, + "entity": { + "title": "Entity", + "description": "Entity being put into property" + }, + "operation": { + "title": "Operation", + "description": "Operation for assigning entity." + }, "condition": { "title": "Condition", "description": "Condition (expression)." @@ -2738,9 +2877,9 @@ } } }, - "Microsoft.OnConversationUpdateActivity": { - "title": "On ConversationUpdate activity", - "description": "Actions to perform on receipt of an activity with type 'ConversationUpdate'.", + "Microsoft.OnBeginDialog": { + "title": "On begin dialog", + "description": "Actions to perform when this dialog begins.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2774,9 +2913,9 @@ } } }, - "Microsoft.OnDialogEvent": { - "title": "On dialog event", - "description": "Actions to perform when a specific dialog event occurs.", + "Microsoft.OnCancelDialog": { + "title": "On cancel dialog", + "description": "Actions to perform on cancel dialog event.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2784,10 +2923,6 @@ } }, "properties": { - "event": { - "title": "Dialog event name", - "description": "Name of dialog event." - }, "condition": { "title": "Condition", "description": "Condition (expression)." @@ -2814,9 +2949,9 @@ } } }, - "Microsoft.OnEndOfActions": { - "title": "On end of actions", - "description": "Actions to take when there are no more actions in the current dialog.", + "Microsoft.OnChooseEntity": { + "title": "On choose entity", + "description": "Actions to be performed when an entity value needs to be resolved.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2824,6 +2959,14 @@ } }, "properties": { + "property": { + "title": "Property to be set", + "description": "Property that will be set after entity is selected." + }, + "entity": { + "title": "Ambiguous entity", + "description": "Ambiguous entity" + }, "condition": { "title": "Condition", "description": "Condition (expression)." @@ -2850,9 +2993,9 @@ } } }, - "Microsoft.OnEndOfConversationActivity": { - "title": "On EndOfConversation activity", - "description": "Actions to perform on receipt of an activity with type 'EndOfConversation'.", + "Microsoft.OnChooseIntent": { + "title": "On ambigious intent", + "description": "Actions to perform on when an intent is ambigious.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2860,6 +3003,14 @@ } }, "properties": { + "intents": { + "title": "Intents", + "description": "Intents that must be in the ChooseIntent result for this condition to trigger.", + "items": { + "title": "Intent", + "description": "Intent name to trigger on." + } + }, "condition": { "title": "Condition", "description": "Condition (expression)." @@ -2886,9 +3037,9 @@ } } }, - "Microsoft.OnError": { - "title": "On Error", - "description": "Action to perform when an 'Error' dialog event occurs.", + "Microsoft.OnChooseProperty": { + "title": "On choose property", + "description": "Actions to take when there are multiple possible mappings of entities to properties.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2896,6 +3047,26 @@ } }, "properties": { + "entity": { + "title": "Entity being assigned", + "description": "Entity being assigned to property choice" + }, + "properties": { + "title": "Possible properties", + "description": "Properties to be chosen between.", + "items": { + "title": "Property name", + "description": "Possible property to choose." + } + }, + "entities": { + "title": "Entities", + "description": "Ambiguous entity names.", + "items": { + "title": "Entity name", + "description": "Entity name being chosen between." + } + }, "condition": { "title": "Condition", "description": "Condition (expression)." @@ -2922,10 +3093,10 @@ } } }, - "Microsoft.OnEventActivity": { - "title": "On Event activity", - "description": "Actions to perform on receipt of an activity with type 'Event'.", - "patternProperties": { + "Microsoft.OnCondition": { + "title": "On condition", + "description": "Actions to perform when specified condition is true.", + "patternProperties": { "^\\$": { "title": "Tooling property", "description": "Open ended property for tooling." @@ -2958,9 +3129,9 @@ } } }, - "Microsoft.OnHandoffActivity": { - "title": "On Handoff activity", - "description": "Actions to perform on receipt of an activity with type 'HandOff'.", + "Microsoft.OnContinueConversation": { + "title": "On continue conversation", + "description": "Actions to perform when a conversation is started up again from a ContinueConversationLater action.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -2994,9 +3165,9 @@ } } }, - "Microsoft.OnIntent": { - "title": "On intent recognition", - "description": "Actions to perform when specified intent is recognized.", + "Microsoft.OnConversationUpdateActivity": { + "title": "On ConversationUpdate activity", + "description": "Actions to perform on receipt of an activity with type 'ConversationUpdate'.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3004,18 +3175,6 @@ } }, "properties": { - "intent": { - "title": "Intent", - "description": "Name of intent." - }, - "entities": { - "title": "Entities", - "description": "Required entities.", - "items": { - "title": "Entity", - "description": "Entity that must be present." - } - }, "condition": { "title": "Condition", "description": "Condition (expression)." @@ -3042,9 +3201,9 @@ } } }, - "Microsoft.OnInvokeActivity": { - "title": "On Invoke activity", - "description": "Actions to perform on receipt of an activity with type 'Invoke'.", + "Microsoft.OnDialogEvent": { + "title": "On dialog event", + "description": "Actions to perform when a specific dialog event occurs.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3052,6 +3211,10 @@ } }, "properties": { + "event": { + "title": "Dialog event name", + "description": "Name of dialog event." + }, "condition": { "title": "Condition", "description": "Condition (expression)." @@ -3078,9 +3241,9 @@ } } }, - "Microsoft.OnMessageActivity": { - "title": "On Message activity", - "description": "Actions to perform on receipt of an activity with type 'Message'. Overrides Intent trigger.", + "Microsoft.OnEndOfActions": { + "title": "On end of actions", + "description": "Actions to take when there are no more actions in the current dialog.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3114,9 +3277,9 @@ } } }, - "Microsoft.OnMessageDeleteActivity": { - "title": "On MessageDelete activity", - "description": "Actions to perform on receipt of an activity with type 'MessageDelete'.", + "Microsoft.OnEndOfConversationActivity": { + "title": "On EndOfConversation activity", + "description": "Actions to perform on receipt of an activity with type 'EndOfConversation'.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3150,9 +3313,9 @@ } } }, - "Microsoft.OnMessageReactionActivity": { - "title": "On MessageReaction activity", - "description": "Actions to perform on receipt of an activity with type 'MessageReaction'.", + "Microsoft.OnError": { + "title": "On error", + "description": "Action to perform when an 'Error' dialog event occurs.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3186,9 +3349,9 @@ } } }, - "Microsoft.OnMessageUpdateActivity": { - "title": "On MessageUpdate activity", - "description": "Actions to perform on receipt of an activity with type 'MessageUpdate'.", + "Microsoft.OnEventActivity": { + "title": "On Event activity", + "description": "Actions to perform on receipt of an activity with type 'Event'.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3222,9 +3385,9 @@ } } }, - "Microsoft.OnQnAMatch": { - "title": "On QnAMaker Match", - "description": "Actions to perform on when an match from QnAMaker is found.", + "Microsoft.OnHandoffActivity": { + "title": "On Handoff activity", + "description": "Actions to perform on receipt of an activity with type 'HandOff'.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3258,9 +3421,9 @@ } } }, - "Microsoft.OnRepromptDialog": { - "title": "On RepromptDialog", - "description": "Actions to perform when 'RepromptDialog' event occurs.", + "Microsoft.OnInstallationUpdateActivity": { + "title": "On InstallationUpdate activity", + "description": "Actions to perform on receipt of an activity with type 'InstallationUpdate'.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3294,9 +3457,9 @@ } } }, - "Microsoft.OnTypingActivity": { - "title": "On Typing activity", - "description": "Actions to perform on receipt of an activity with type 'Typing'.", + "Microsoft.OnIntent": { + "title": "On intent recognition", + "description": "Actions to perform when specified intent is recognized.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3304,6 +3467,18 @@ } }, "properties": { + "intent": { + "title": "Intent", + "description": "Name of intent." + }, + "entities": { + "title": "Entities", + "description": "Required entities.", + "items": { + "title": "Entity", + "description": "Entity that must be present." + } + }, "condition": { "title": "Condition", "description": "Condition (expression)." @@ -3330,9 +3505,9 @@ } } }, - "Microsoft.OnUnknownIntent": { - "title": "On unknown intent", - "description": "Action to perform when user input is unrecognized or if none of the 'on intent recognition' triggers match recognized intent.", + "Microsoft.OnInvokeActivity": { + "title": "On Invoke activity", + "description": "Actions to perform on receipt of an activity with type 'Invoke'.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3366,9 +3541,9 @@ } } }, - "Microsoft.OrdinalEntityRecognizer": { - "title": "Ordinal Entity Recognizer", - "description": "Recognizer which recognizes ordinals (example: first, second, 3rd).", + "Microsoft.OnMessageActivity": { + "title": "On Message activity", + "description": "Actions to perform on receipt of an activity with type 'Message'. Overrides Intent trigger.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3376,6 +3551,22 @@ } }, "properties": { + "condition": { + "title": "Condition", + "description": "Condition (expression)." + }, + "actions": { + "title": "Actions", + "description": "Sequence of actions to execute." + }, + "priority": { + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "title": "Run Once", + "description": "True if rule should run once per unique conditions" + }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" @@ -3386,9 +3577,9 @@ } } }, - "Microsoft.PercentageEntityRecognizer": { - "title": "Percentage Entity Recognizer", - "description": "Recognizer which recognizes percentages.", + "Microsoft.OnMessageDeleteActivity": { + "title": "On MessageDelete activity", + "description": "Actions to perform on receipt of an activity with type 'MessageDelete'.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3396,6 +3587,22 @@ } }, "properties": { + "condition": { + "title": "Condition", + "description": "Condition (expression)." + }, + "actions": { + "title": "Actions", + "description": "Sequence of actions to execute." + }, + "priority": { + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "title": "Run Once", + "description": "True if rule should run once per unique conditions" + }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" @@ -3406,9 +3613,9 @@ } } }, - "Microsoft.PhoneNumberEntityRecognizer": { - "title": "Phone Number Entity Recognizer", - "description": "Recognizer which recognizes phone numbers.", + "Microsoft.OnMessageReactionActivity": { + "title": "On MessageReaction activity", + "description": "Actions to perform on receipt of an activity with type 'MessageReaction'.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3416,6 +3623,22 @@ } }, "properties": { + "condition": { + "title": "Condition", + "description": "Condition (expression)." + }, + "actions": { + "title": "Actions", + "description": "Sequence of actions to execute." + }, + "priority": { + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "title": "Run Once", + "description": "True if rule should run once per unique conditions" + }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" @@ -3426,9 +3649,9 @@ } } }, - "Microsoft.QnAMakerDialog": { - "title": "QnAMaker Dialog", - "description": "Dialog which uses QnAMAker knowledge base to answer questions.", + "Microsoft.OnMessageUpdateActivity": { + "title": "On MessageUpdate activity", + "description": "Actions to perform on receipt of an activity with type 'MessageUpdate'.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3436,73 +3659,21 @@ } }, "properties": { - "knowledgeBaseId": { - "title": "KnowledgeBase Id", - "description": "KnowledgeBase Id of your QnA Maker KnowledgeBase." - }, - "endpointKey": { - "title": "Endpoint Key", - "description": "Endpoint key for the QnA Maker KB." - }, - "hostname": { - "title": "Hostname", - "description": "Hostname for your QnA Maker service." - }, - "noAnswer": { - "title": "Fallback answer", - "description": "Default answer to return when none found in KB." - }, - "threshold": { - "title": "Threshold", - "description": "Threshold score to filter results." - }, - "activeLearningCardTitle": { - "title": "Active learning card title", - "description": "Title for active learning suggestions card." - }, - "cardNoMatchText": { - "title": "Card no match text", - "description": "Text for no match option." - }, - "cardNoMatchResponse": { - "title": "Card no match response", - "description": "Custom response when no match option was selected." - }, - "strictFilters": { - "title": "Strict Filters", - "description": "Metadata filters to use when calling the QnA Maker KB.", - "items": { - "title": "Metadata filter", - "description": "Metadata filter.", - "properties": { - "name": { - "title": "Name", - "description": "Name of filter property." - }, - "value": { - "title": "Value", - "description": "Value to filter on." - } - } - } + "condition": { + "title": "Condition", + "description": "Condition (expression)." }, - "top": { - "title": "Top", - "description": "The number of answers you want to retrieve." + "actions": { + "title": "Actions", + "description": "Sequence of actions to execute." }, - "isTest": { - "title": "IsTest", - "description": "True, if pointing to Test environment, else false." + "priority": { + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." }, - "rankerType": { - "title": "Ranker Type", - "description": "Type of Ranker.", - "oneOf": { - "0": { - "title": "Standard ranker", - "description": "Standard ranker types." - } - } + "runOnce": { + "title": "Run Once", + "description": "True if rule should run once per unique conditions" }, "$kind": { "title": "Kind of dialog object", @@ -3514,9 +3685,9 @@ } } }, - "Microsoft.QnAMakerRecognizer": { - "title": "QnAMaker Recognizer", - "description": "Recognizer for generating QnAMatch intents from a KB.", + "Microsoft.OnQnAMatch": { + "title": "On QnAMaker match", + "description": "Actions to perform on when an match from QnAMaker is found.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3524,91 +3695,21 @@ } }, "properties": { - "id": { - "title": "Id", - "description": "Optional unique id using with RecognizerSet." + "condition": { + "title": "Condition", + "description": "Condition (expression)." }, - "knowledgeBaseId": { - "title": "KnowledgeBase Id", - "description": "KnowledgeBase Id of your QnA Maker KnowledgeBase." + "actions": { + "title": "Actions", + "description": "Sequence of actions to execute." }, - "endpointKey": { - "title": "Endpoint Key", - "description": "Endpoint key for the QnA Maker KB." + "priority": { + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." }, - "hostname": { - "title": "Hostname", - "description": "Hostname for your QnA Maker service." - }, - "threshold": { - "title": "Threshold", - "description": "Threshold score to filter results." - }, - "strictFilters": { - "title": "Strict Filters", - "description": "Metadata filters to use when calling the QnA Maker KB.", - "items": { - "title": "Metadata filters", - "description": "Metadata filters to use when querying QnA Maker KB.", - "properties": { - "name": { - "title": "Name", - "description": "Name to filter on." - }, - "value": { - "title": "Value", - "description": "Value to restrict filter." - } - } - } - }, - "top": { - "title": "Top", - "description": "The number of answers you want to retrieve." - }, - "isTest": { - "title": "IsTest", - "description": "True, if pointing to Test environment, else false." - }, - "rankerType": { - "title": "Ranker Type", - "description": "Type of Ranker.", - "oneOf": { - "0": { - "title": "Ranker type", - "description": "Type of Ranker." - } - } - }, - "includeDialogNameInMetadata": { - "title": "Include Dialog Name", - "description": "When set to false, the dialog name will not be passed to QnAMaker. (default) is true" - }, - "metadata": { - "title": "Metadata filters", - "description": "Metadata filters to use when calling the QnA Maker KB.", - "items": { - "title": "Metadata filter", - "description": "Metadata filter to use when calling the QnA Maker KB.", - "properties": { - "name": { - "title": "Name", - "description": "Name of value to test." - }, - "value": { - "title": "Value", - "description": "Value to filter against." - } - } - } - }, - "context": { - "title": "QnARequestContext", - "description": "Context to use for ranking." - }, - "qnaId": { - "title": "QnAId", - "description": "A number or expression which is the QnAId to paass to QnAMaker API." + "runOnce": { + "title": "Run Once", + "description": "True if rule should run once per unique conditions" }, "$kind": { "title": "Kind of dialog object", @@ -3620,9 +3721,9 @@ } } }, - "Microsoft.RandomSelector": { - "title": "Random rule selector", - "description": "Select most specific true rule.", + "Microsoft.OnRepromptDialog": { + "title": "On RepromptDialog", + "description": "Actions to perform when 'RepromptDialog' event occurs.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3630,9 +3731,21 @@ } }, "properties": { - "seed": { - "title": "Random seed", - "description": "Random seed to start random number generation." + "condition": { + "title": "Condition", + "description": "Condition (expression)." + }, + "actions": { + "title": "Actions", + "description": "Sequence of actions to execute." + }, + "priority": { + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "title": "Run Once", + "description": "True if rule should run once per unique conditions" }, "$kind": { "title": "Kind of dialog object", @@ -3644,9 +3757,9 @@ } } }, - "Microsoft.RecognizerSet": { - "title": "Recognizer Set", - "description": "Creates the union of the intents and entities of the recognizers in the set.", + "Microsoft.OnTypingActivity": { + "title": "On Typing activity", + "description": "Actions to perform on receipt of an activity with type 'Typing'.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3654,13 +3767,21 @@ } }, "properties": { - "id": { - "title": "Id", - "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." + "condition": { + "title": "Condition", + "description": "Condition (expression)." }, - "recognizers": { - "title": "Recognizers", - "description": "List of Recognizers defined for this set." + "actions": { + "title": "Actions", + "description": "Sequence of actions to execute." + }, + "priority": { + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "title": "Run Once", + "description": "True if rule should run once per unique conditions" }, "$kind": { "title": "Kind of dialog object", @@ -3672,9 +3793,9 @@ } } }, - "Microsoft.RegexEntityRecognizer": { - "title": "Regex Entity Recognizer", - "description": "Recognizer which recognizes patterns of input based on regex.", + "Microsoft.OnUnknownIntent": { + "title": "On unknown intent", + "description": "Action to perform when user input is unrecognized or if none of the 'on intent recognition' triggers match recognized intent.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3682,13 +3803,21 @@ } }, "properties": { - "name": { - "title": "Name", - "description": "Name of the entity" + "condition": { + "title": "Condition", + "description": "Condition (expression)." }, - "pattern": { - "title": "Pattern", - "description": "Pattern expressed as regular expression." + "actions": { + "title": "Actions", + "description": "Sequence of actions to execute." + }, + "priority": { + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "title": "Run Once", + "description": "True if rule should run once per unique conditions" }, "$kind": { "title": "Kind of dialog object", @@ -3700,9 +3829,9 @@ } } }, - "Microsoft.RegexRecognizer": { - "title": "Regex recognizer", - "description": "Use regular expressions to recognize intents and entities from user input.", + "Microsoft.OrdinalEntityRecognizer": { + "title": "Ordinal entity recognizer", + "description": "Recognizer which recognizes ordinals (example: first, second, 3rd).", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3710,32 +3839,6 @@ } }, "properties": { - "id": { - "title": "Id", - "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." - }, - "intents": { - "title": "RegEx patterns to intents", - "description": "Collection of patterns to match for an intent.", - "items": { - "title": "Pattern", - "description": "Intent and regex pattern.", - "properties": { - "intent": { - "title": "Intent", - "description": "The intent name." - }, - "pattern": { - "title": "Pattern", - "description": "The regular expression pattern." - } - } - } - }, - "entities": { - "title": "Entity recognizers", - "description": "Collection of entity recognizers to use." - }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" @@ -3746,9 +3849,9 @@ } } }, - "Microsoft.RepeatDialog": { - "title": "Repeat dialog", - "description": "Repeat current dialog.", + "Microsoft.PercentageEntityRecognizer": { + "title": "Percentage entity recognizer", + "description": "Recognizer which recognizes percentages.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3756,30 +3859,6 @@ } }, "properties": { - "id": { - "title": "Id", - "description": "Optional id for the dialog" - }, - "allowLoop": { - "title": "AllowLoop", - "description": "Optional condition which if true will allow loop of the repeated dialog." - }, - "disabled": { - "title": "Disabled", - "description": "Optional condition which if true will disable this action." - }, - "options": { - "title": "Options", - "description": "One or more options that are passed to the dialog that is called.", - "additionalProperties": { - "title": "Options", - "description": "Options for repeating dialog." - } - }, - "activityProcessed": { - "title": "Activity Processed", - "description": "When set to false, the dialog that is called can process the current activity." - }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" @@ -3790,9 +3869,9 @@ } } }, - "Microsoft.ReplaceDialog": { - "title": "Replace dialog", - "description": "Replace current dialog with another dialog.", + "Microsoft.PhoneNumberEntityRecognizer": { + "title": "Phone number entity recognizer", + "description": "Recognizer which recognizes phone numbers.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3800,35 +3879,6 @@ } }, "properties": { - "id": { - "title": "Id", - "description": "Optional id for the dialog" - }, - "disabled": { - "title": "Disabled", - "description": "Optional condition which if true will disable this action." - }, - "dialog": { - "oneOf": { - "0": { - "title": "Dialog" - } - }, - "title": "Dialog name", - "description": "Name of the dialog to call." - }, - "options": { - "title": "Options", - "description": "One or more options that are passed to the dialog that is called.", - "additionalProperties": { - "title": "Options", - "description": "Options for replacing dialog." - } - }, - "activityProcessed": { - "title": "Activity Processed", - "description": "When set to false, the dialog that is called can process the current activity." - }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" @@ -3839,9 +3889,9 @@ } } }, - "Microsoft.ResourceMultiLanguageGenerator": { - "title": "Resource Multi-Language Generator", - "description": "MultiLanguage Generator which is bound to resource by resource Id.", + "Microsoft.QnAMakerDialog": { + "title": "QnAMaker dialog", + "description": "Dialog which uses QnAMAker knowledge base to answer questions.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3849,49 +3899,83 @@ } }, "properties": { - "id": { - "title": "Id", - "description": "Optional generator ID." + "knowledgeBaseId": { + "title": "KnowledgeBase Id", + "description": "KnowledgeBase Id of your QnA Maker KnowledgeBase." }, - "resourceId": { - "title": "Resource Id", - "description": "Resource which is the root language generator. Other generaters with the same name and language suffix will be loaded into this generator and used based on the Language Policy." + "endpointKey": { + "title": "Endpoint key", + "description": "Endpoint key for the QnA Maker KB." }, - "languagePolicy": { - "title": "Language Policy", - "description": "Set alternate language policy for this generator. If not set, the global language policy will be used." + "hostname": { + "title": "Hostname", + "description": "Hostname for your QnA Maker service." }, - "$kind": { - "title": "Kind of dialog object", - "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + "noAnswer": { + "title": "Fallback answer", + "description": "Default answer to return when none found in KB." }, - "$designer": { - "title": "Designer information", - "description": "Extra information for the Bot Framework Composer." - } - } - }, - "Microsoft.SendActivity": { - "title": "Send an activity", - "description": "Respond with an activity.", - "patternProperties": { - "^\\$": { - "title": "Tooling property", - "description": "Open ended property for tooling." - } - }, - "properties": { - "id": { - "title": "Id", - "description": "Optional id for the dialog" + "threshold": { + "title": "Threshold", + "description": "Threshold score to filter results." }, - "disabled": { - "title": "Disabled", - "description": "Optional condition which if true will disable this action." + "activeLearningCardTitle": { + "title": "Active learning card title", + "description": "Title for active learning suggestions card." }, - "activity": { - "title": "Activity", - "description": "Activity to send." + "cardNoMatchText": { + "title": "Card no match text", + "description": "Text for no match option." + }, + "cardNoMatchResponse": { + "title": "Card no match response", + "description": "Custom response when no match option was selected." + }, + "strictFilters": { + "title": "Strict filters", + "description": "Metadata filters to use when calling the QnA Maker KB.", + "items": { + "title": "Metadata filter", + "description": "Metadata filter.", + "properties": { + "name": { + "title": "Name", + "description": "Name of filter property." + }, + "value": { + "title": "Value", + "description": "Value to filter on." + } + } + } + }, + "top": { + "title": "Top", + "description": "The number of answers you want to retrieve." + }, + "isTest": { + "title": "IsTest", + "description": "True, if pointing to Test environment, else false." + }, + "rankerType": { + "title": "Ranker type", + "description": "Type of Ranker.", + "oneOf": { + "0": { + "title": "Standard ranker", + "description": "Standard ranker types." + } + } + }, + "strictFiltersJoinOperator": { + "title": "StrictFiltersJoinOperator", + "description": "Join operator for Strict Filters.", + "oneOf": { + "0": { + "title": "Join operator", + "description": "Value of Join Operator to be used as conjunction with Strict Filter values." + } + } }, "$kind": { "title": "Kind of dialog object", @@ -3903,9 +3987,9 @@ } } }, - "Microsoft.SetProperties": { - "title": "Set property", - "description": "Set one or more property values.", + "Microsoft.QnAMakerRecognizer": { + "title": "QnAMaker recognizer", + "description": "Recognizer for generating QnAMatch intents from a KB.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3915,30 +3999,100 @@ "properties": { "id": { "title": "Id", - "description": "Optional id for the dialog" + "description": "Optional unique id using with RecognizerSet." }, - "disabled": { - "title": "Disabled", - "description": "Optional condition which if true will disable this action." + "knowledgeBaseId": { + "title": "KnowledgeBase Id", + "description": "Knowledge base Id of your QnA Maker knowledge base." }, - "assignments": { - "title": "Assignments", - "description": "Property value assignments to set.", + "endpointKey": { + "title": "Endpoint key", + "description": "Endpoint key for the QnA Maker KB." + }, + "hostname": { + "title": "Hostname", + "description": "Hostname for your QnA Maker service." + }, + "threshold": { + "title": "Threshold", + "description": "Threshold score to filter results." + }, + "strictFilters": { + "title": "Strict filters", + "description": "Metadata filters to use when calling the QnA Maker KB.", "items": { - "title": "Assignment", - "description": "Property assignment.", + "title": "Metadata filters", + "description": "Metadata filters to use when querying QnA Maker KB.", "properties": { - "property": { - "title": "Property", - "description": "Property (named location to store information)." + "name": { + "title": "Name", + "description": "Name to filter on." }, "value": { "title": "Value", - "description": "New value or expression." + "description": "Value to restrict filter." + } + } + } + }, + "top": { + "title": "Top", + "description": "The number of answers you want to retrieve." + }, + "isTest": { + "title": "Use test environment", + "description": "True, if pointing to Test environment, else false." + }, + "rankerType": { + "title": "Ranker type", + "description": "Type of Ranker.", + "oneOf": { + "0": { + "title": "Ranker type", + "description": "Type of Ranker." + } + } + }, + "strictFiltersJoinOperator": { + "title": "StrictFiltersJoinOperator", + "description": "Join operator for Strict Filters.", + "oneOf": { + "0": { + "title": "Join operator", + "description": "Value of Join Operator to be used as onjuction with Strict Filter values." + } + } + }, + "includeDialogNameInMetadata": { + "title": "Include dialog name", + "description": "When set to false, the dialog name will not be passed to QnAMaker. (default) is true" + }, + "metadata": { + "title": "Metadata filters", + "description": "Metadata filters to use when calling the QnA Maker KB.", + "items": { + "title": "Metadata filter", + "description": "Metadata filter to use when calling the QnA Maker KB.", + "properties": { + "name": { + "title": "Name", + "description": "Name of value to test." + }, + "value": { + "title": "Value", + "description": "Value to filter against." } } } }, + "context": { + "title": "QnA request context", + "description": "Context to use for ranking." + }, + "qnaId": { + "title": "QnA Id", + "description": "A number or expression which is the QnAId to paass to QnAMaker API." + }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" @@ -3949,9 +4103,9 @@ } } }, - "Microsoft.SetProperty": { - "title": "Set property", - "description": "Set property to a value.", + "Microsoft.RandomSelector": { + "title": "Random rule selector", + "description": "Select most specific true rule.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3959,21 +4113,9 @@ } }, "properties": { - "id": { - "title": "Id", - "description": "Optional id for the dialog" - }, - "disabled": { - "title": "Disabled", - "description": "Optional condition which if true will disable this action." - }, - "property": { - "title": "Property", - "description": "Property (named location to store information)." - }, - "value": { - "title": "Value", - "description": "New value or expression." + "seed": { + "title": "Random seed", + "description": "Random seed to start random number generation." }, "$kind": { "title": "Kind of dialog object", @@ -3985,9 +4127,9 @@ } } }, - "Microsoft.SignOutUser": { - "title": "Sign Out User", - "description": "Sign a user out that was logged in previously using OAuthInput.", + "Microsoft.RecognizerSet": { + "title": "Recognizer set", + "description": "Creates the union of the intents and entities of the recognizers in the set.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -3997,19 +4139,11 @@ "properties": { "id": { "title": "Id", - "description": "Optional id for the dialog" - }, - "userId": { - "title": "UserId", - "description": "Expression to an user to signout. Default is user.id." - }, - "connectionName": { - "title": "Connection Name", - "description": "Connection name that was used with OAuthInput to log a user in." + "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." }, - "disabled": { - "title": "Disabled", - "description": "Optional condition which if true will disable this action." + "recognizers": { + "title": "Recognizers", + "description": "List of Recognizers defined for this set." }, "$kind": { "title": "Kind of dialog object", @@ -4021,9 +4155,9 @@ } } }, - "Microsoft.StaticActivityTemplate": { - "title": "Microsoft Static Activity Template", - "description": "This allows you to define a static Activity object", + "Microsoft.RegexEntityRecognizer": { + "title": "Regex entity recognizer", + "description": "Recognizer which recognizes patterns of input based on regex.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -4031,9 +4165,13 @@ } }, "properties": { - "activity": { - "title": "Activity", - "description": "A static Activity to used." + "name": { + "title": "Name", + "description": "Name of the entity" + }, + "pattern": { + "title": "Pattern", + "description": "Pattern expressed as regular expression." }, "$kind": { "title": "Kind of dialog object", @@ -4045,9 +4183,9 @@ } } }, - "Microsoft.SwitchCondition": { - "title": "Switch condition", - "description": "Execute different actions based on the value of a property.", + "Microsoft.RegexRecognizer": { + "title": "Regex recognizer", + "description": "Use regular expressions to recognize intents and entities from user input.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -4057,37 +4195,29 @@ "properties": { "id": { "title": "Id", - "description": "Optional id for the dialog" - }, - "condition": { - "title": "Condition", - "description": "Property to evaluate." - }, - "disabled": { - "title": "Disabled", - "description": "Optional condition which if true will disable this action." + "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." }, - "cases": { - "title": "Cases", - "description": "Actions for each possible condition.", + "intents": { + "title": "RegEx patterns to intents", + "description": "Collection of patterns to match for an intent.", "items": { - "title": "Case", - "description": "Case and actions.", + "title": "Pattern", + "description": "Intent and regex pattern.", "properties": { - "value": { - "title": "Value", - "description": "The value to compare the condition with." + "intent": { + "title": "Intent", + "description": "The intent name." }, - "actions": { - "title": "Actions", - "description": "Actions to execute." + "pattern": { + "title": "Pattern", + "description": "The regular expression pattern." } } } }, - "default": { - "title": "Default", - "description": "Actions to execute if none of the cases meet the condition." + "entities": { + "title": "Entity recognizers", + "description": "Collection of entity recognizers to use." }, "$kind": { "title": "Kind of dialog object", @@ -4099,9 +4229,9 @@ } } }, - "Microsoft.TelemetryTrackEvent": { - "title": "Telemetry - Track Event", - "description": "Track a custom event using the registered Telemetry Client.", + "Microsoft.RepeatDialog": { + "title": "Repeat dialog", + "description": "Repeat current dialog.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -4113,17 +4243,25 @@ "title": "Id", "description": "Optional id for the dialog" }, + "allowLoop": { + "title": "AllowLoop", + "description": "Optional condition which if true will allow loop of the repeated dialog." + }, "disabled": { "title": "Disabled", "description": "Optional condition which if true will disable this action." }, - "eventName": { - "title": "Event Name", - "description": "The name of the event to track." + "options": { + "title": "Options", + "description": "One or more options that are passed to the dialog that is called.", + "additionalProperties": { + "title": "Options", + "description": "Options for repeating dialog." + } }, - "properties": { - "title": "Properties", - "description": "One or more properties to attach to the event being tracked." + "activityProcessed": { + "title": "Activity processed", + "description": "When set to false, the dialog that is called can process the current activity." }, "$kind": { "title": "Kind of dialog object", @@ -4135,9 +4273,9 @@ } } }, - "Microsoft.TemperatureEntityRecognizer": { - "title": "Temperature Recognizer", - "description": "Recognizer which recognizes temperatures.", + "Microsoft.ReplaceDialog": { + "title": "Replace dialog", + "description": "Replace current dialog with another dialog.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -4145,6 +4283,35 @@ } }, "properties": { + "id": { + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "title": "Disabled", + "description": "Optional condition which if true will disable this action." + }, + "dialog": { + "oneOf": { + "0": { + "title": "Dialog" + } + }, + "title": "Dialog name", + "description": "Name of the dialog to call." + }, + "options": { + "title": "Options", + "description": "One or more options that are passed to the dialog that is called.", + "additionalProperties": { + "title": "Options", + "description": "Options for replacing dialog." + } + }, + "activityProcessed": { + "title": "Activity processed", + "description": "When set to false, the dialog that is called can process the current activity." + }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" @@ -4155,9 +4322,9 @@ } } }, - "Microsoft.TemplateEngineLanguageGenerator": { - "title": "Template Multi-Language Generator", - "description": "Template Generator which allows only inline evaluation of templates.", + "Microsoft.ResourceMultiLanguageGenerator": { + "title": "Resource multi-language generator", + "description": "MultiLanguage Generator which is bound to resource by resource Id.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -4169,6 +4336,14 @@ "title": "Id", "description": "Optional generator ID." }, + "resourceId": { + "title": "Resource Id", + "description": "Resource which is the root language generator. Other generaters with the same name and language suffix will be loaded into this generator and used based on the Language Policy." + }, + "languagePolicy": { + "title": "Language policy", + "description": "Set alternate language policy for this generator. If not set, the global language policy will be used." + }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" @@ -4179,9 +4354,9 @@ } } }, - "Microsoft.TextInput": { - "title": "Text input dialog", - "description": "Collection information - Ask for a word or sentence.", + "Microsoft.SendActivity": { + "title": "Send an activity", + "description": "Respond with an activity.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -4189,18 +4364,6 @@ } }, "properties": { - "defaultValue": { - "title": "Default value", - "description": "'Property' will be set to the value of this expression when max turn count is exceeded." - }, - "value": { - "title": "Value", - "description": "'Property' will be set to the value of this expression unless it evaluates to null." - }, - "outputFormat": { - "title": "Output format", - "description": "Expression to format the output." - }, "id": { "title": "Id", "description": "Optional id for the dialog" @@ -4209,45 +4372,9 @@ "title": "Disabled", "description": "Optional condition which if true will disable this action." }, - "prompt": { - "title": "Initial prompt", - "description": "Message to send to collect information." - }, - "unrecognizedPrompt": { - "title": "Unrecognized prompt", - "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value." - }, - "invalidPrompt": { - "title": "Invalid prompt", - "description": "Message to send when the user input does not meet any validation expression." - }, - "defaultValueResponse": { - "title": "Default value response", - "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value." - }, - "maxTurnCount": { - "title": "Max turn count", - "description": "Maximum number of re-prompt attempts to collect information." - }, - "validations": { - "title": "Validation expressions", - "description": "Expression to validate user input.", - "items": { - "title": "Condition", - "description": "Expression which needs to met for the input to be considered valid" - } - }, - "property": { - "title": "Property", - "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true)." - }, - "alwaysPrompt": { - "title": "Always prompt", - "description": "Collect information even if the specified 'property' is not empty." - }, - "allowInterruptions": { - "title": "Allow Interruptions", - "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input." + "activity": { + "title": "Activity", + "description": "Activity to send." }, "$kind": { "title": "Kind of dialog object", @@ -4259,9 +4386,9 @@ } } }, - "Microsoft.TextTemplate": { - "title": "Microsoft TextTemplate", - "description": "Use LG Templates to create text", + "Microsoft.SetProperties": { + "title": "Set property", + "description": "Set one or more property values.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -4269,9 +4396,31 @@ } }, "properties": { - "template": { - "title": "Template", - "description": "Language Generator template to evaluate to create the text." + "id": { + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "title": "Disabled", + "description": "Optional condition which if true will disable this action." + }, + "assignments": { + "title": "Assignments", + "description": "Property value assignments to set.", + "items": { + "title": "Assignment", + "description": "Property assignment.", + "properties": { + "property": { + "title": "Property", + "description": "Property (named location to store information)." + }, + "value": { + "title": "Value", + "description": "New value or expression." + } + } + } }, "$kind": { "title": "Kind of dialog object", @@ -4283,9 +4432,9 @@ } } }, - "Microsoft.TraceActivity": { - "title": "Send a TraceActivity", - "description": "Send a trace activity to the transcript logger and/ or Bot Framework Emulator.", + "Microsoft.SetProperty": { + "title": "Set property", + "description": "Set property to a value.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -4301,21 +4450,13 @@ "title": "Disabled", "description": "Optional condition which if true will disable this action." }, - "name": { - "title": "Name", - "description": "Name of the trace activity" - }, - "label": { - "title": "Label", - "description": "Label for the trace activity (used to identify it in a list of trace activities.)" - }, - "valueType": { - "title": "Value type", - "description": "Type of value" + "property": { + "title": "Property", + "description": "Property (named location to store information)." }, "value": { "title": "Value", - "description": "Property that holds the value to send as trace activity." + "description": "New value or expression." }, "$kind": { "title": "Kind of dialog object", @@ -4327,9 +4468,9 @@ } } }, - "Microsoft.TrueSelector": { - "title": "True Trigger Selector", - "description": "Selector for all true events", + "Microsoft.SignOutUser": { + "title": "Sign out user", + "description": "Sign a user out that was logged in previously using OAuthInput.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -4337,6 +4478,22 @@ } }, "properties": { + "id": { + "title": "Id", + "description": "Optional id for the dialog" + }, + "userId": { + "title": "UserId", + "description": "Expression to an user to signout. Default is user.id." + }, + "connectionName": { + "title": "Connection name", + "description": "Connection name that was used with OAuthInput to log a user in." + }, + "disabled": { + "title": "Disabled", + "description": "Optional condition which if true will disable this action." + }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" @@ -4347,9 +4504,9 @@ } } }, - "Microsoft.UpdateActivity": { - "title": "Send an activity", - "description": "Respond with an activity.", + "Microsoft.StaticActivityTemplate": { + "title": "Microsoft static activity template", + "description": "This allows you to define a static Activity object", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -4357,21 +4514,9 @@ } }, "properties": { - "id": { - "title": "Id", - "description": "Optional id for the dialog" - }, - "disabled": { - "title": "Disabled", - "description": "Optional condition which if true will disable this action." - }, - "activityId": { - "title": "Activity Id", - "description": "An string expression with the activity id to update." - }, "activity": { "title": "Activity", - "description": "Activity to send." + "description": "A static Activity to used." }, "$kind": { "title": "Kind of dialog object", @@ -4383,9 +4528,9 @@ } } }, - "Microsoft.UrlEntityRecognizer": { - "title": "Confirmation Url Recognizer", - "description": "Recognizer which recognizes urls.", + "Microsoft.SwitchCondition": { + "title": "Switch condition", + "description": "Execute different actions based on the value of a property.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -4393,37 +4538,53 @@ } }, "properties": { - "$kind": { - "title": "Kind of dialog object", - "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + "id": { + "title": "Id", + "description": "Optional id for the dialog" }, - "$designer": { - "title": "Designer information", - "description": "Extra information for the Bot Framework Composer." - } - } - }, - "arrayExpression": { - "title": "Array or expression", - "description": "Array or expression to evaluate.", - "oneOf": { - "0": { - "title": "Array", - "description": "Array constant." - } - } - }, - "booleanExpression": { - "title": "Boolean or expression", - "description": "Boolean constant or expression to evaluate.", - "oneOf": { - "0": { - "title": "Boolean", - "description": "Boolean constant." + "condition": { + "title": "Condition", + "description": "Property to evaluate." + }, + "disabled": { + "title": "Disabled", + "description": "Optional condition which if true will disable this action." + }, + "cases": { + "title": "Cases", + "description": "Actions for each possible condition.", + "items": { + "title": "Case", + "description": "Case and actions.", + "properties": { + "value": { + "title": "Value", + "description": "The value to compare the condition with." + }, + "actions": { + "title": "Actions", + "description": "Actions to execute." + } + } + } + }, + "default": { + "title": "Default", + "description": "Actions to execute if none of the cases meet the condition." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + }, + "$designer": { + "title": "Designer information", + "description": "Extra information for the Bot Framework Composer." } } }, - "component": { + "Microsoft.TelemetryTrackEvent": { + "title": "Telemetry - track event", + "description": "Track a custom event using the registered Telemetry Client.", "patternProperties": { "^\\$": { "title": "Tooling property", @@ -4431,6 +4592,22 @@ } }, "properties": { + "id": { + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "title": "Disabled", + "description": "Optional condition which if true will disable this action." + }, + "eventName": { + "title": "Event name", + "description": "The name of the event to track." + }, + "properties": { + "title": "Properties", + "description": "One or more properties to attach to the event being tracked." + }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" @@ -4441,480 +4618,429 @@ } } }, - "condition": { - "title": "Boolean condition", - "description": "Boolean constant or expression to evaluate.", - "oneOf": { - "1": { - "title": "Boolean", - "description": "Boolean value." + "Microsoft.TemperatureEntityRecognizer": { + "title": "Temperature recognizer", + "description": "Recognizer which recognizes temperatures.", + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." } - } - }, - "equalsExpression": { - "title": "Expression", - "description": "Expression starting with =." - }, - "expression": { - "title": "Expression", - "description": "Expression to evaluate." - }, - "integerExpression": { - "title": "Integer or expression", - "description": "Integer constant or expression to evaluate.", - "oneOf": { - "0": { - "title": "Integer", - "description": "Integer constant." + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + }, + "$designer": { + "title": "Designer information", + "description": "Extra information for the Bot Framework Composer." } } }, - "numberExpression": { - "title": "Number or expression", - "description": "Number constant or expression to evaluate.", - "oneOf": { - "0": { - "title": "Number", - "description": "Number constant." + "Microsoft.TemplateEngineLanguageGenerator": { + "title": "Template multi-language generator", + "description": "Template Generator which allows only inline evaluation of templates.", + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." } - } - }, - "objectExpression": { - "title": "Object or expression", - "description": "Object or expression to evaluate.", - "oneOf": { - "0": { - "title": "Object", - "description": "Object constant." + }, + "properties": { + "id": { + "title": "Id", + "description": "Optional generator ID." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + }, + "$designer": { + "title": "Designer information", + "description": "Extra information for the Bot Framework Composer." } } }, - "role": { - "title": "$role", - "description": "Defines the role played in the dialog schema from [expression|interface|implements($kind)|extends($kind)]." - }, - "stringExpression": { - "title": "String or expression", - "description": "Interpolated string or expression to evaluate.", - "oneOf": { - "0": { - "title": "String", - "description": "Interpolated string" + "Microsoft.TextInput": { + "title": "Text input dialog", + "description": "Collection information - Ask for a word or sentence.", + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." } - } - }, - "valueExpression": { - "title": "Any or expression", - "description": "Any constant or expression to evaluate.", - "oneOf": { - "0": { - "title": "Object", - "description": "Object constant." + }, + "properties": { + "defaultValue": { + "title": "Default value", + "description": "'Property' will be set to the value of this expression when max turn count is exceeded." }, - "1": { - "title": "Array", - "description": "Array constant." + "value": { + "title": "Value", + "description": "'Property' will be set to the value of this expression unless it evaluates to null." }, - "2": { - "title": "String", - "description": "Interpolated string." + "outputFormat": { + "title": "Output format", + "description": "Expression to format the output." }, - "3": { - "title": "Boolean", - "description": "Boolean constant" + "id": { + "title": "Id", + "description": "Optional id for the dialog" }, - "4": { - "title": "Number", - "description": "Number constant." - } - } - }, - "schema": { - "title": "Core schema meta-schema" - }, - "botframework.json": { - "definitions": { - "ChannelAccount": { - "description": "Channel account information needed to route a message", - "title": "ChannelAccount", - "properties": { - "id": { - "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or\n123456)", - "title": "id" - }, - "name": { - "description": "Display friendly name", - "title": "name" - }, - "aadObjectId": { - "description": "This account's object ID within Azure Active Directory (AAD)", - "title": "aadObjectId" - }, - "role": { - "description": "Role of the entity behind the account (Example: User, Bot, etc.). Possible values include:\n'user', 'bot'", - "title": "role" - } - } + "disabled": { + "title": "Disabled", + "description": "Optional condition which if true will disable this action." }, - "ConversationAccount": { - "description": "Channel account information for a conversation", - "title": "ConversationAccount", - "properties": { - "isGroup": { - "description": "Indicates whether the conversation contains more than two participants at the time the\nactivity was generated", - "title": "isGroup" - }, - "conversationType": { - "description": "Indicates the type of the conversation in channels that distinguish between conversation types", - "title": "conversationType" - }, - "id": { - "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or\n123456)", - "title": "id" - }, - "name": { - "description": "Display friendly name", - "title": "name" - }, - "aadObjectId": { - "description": "This account's object ID within Azure Active Directory (AAD)", - "title": "aadObjectId" - }, - "role": { - "description": "Role of the entity behind the account (Example: User, Bot, etc.). Possible values include:\n'user', 'bot'", - "title": "role" - } - } + "prompt": { + "title": "Initial prompt", + "description": "Message to send to collect information." }, - "MessageReaction": { - "description": "Message reaction object", - "title": "MessageReaction", - "properties": { - "type": { - "description": "Message reaction type. Possible values include: 'like', 'plusOne'", - "title": "type" - } - } + "unrecognizedPrompt": { + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value." }, - "CardAction": { - "description": "A clickable action", - "title": "CardAction", - "properties": { - "type": { - "description": "The type of action implemented by this button. Possible values include: 'openUrl', 'imBack',\n'postBack', 'playAudio', 'playVideo', 'showImage', 'downloadFile', 'signin', 'call',\n'payment', 'messageBack'", - "title": "type" - }, - "title": { - "description": "Text description which appears on the button", - "title": "title" - }, - "image": { - "description": "Image URL which will appear on the button, next to text label", - "title": "image" - }, - "text": { - "description": "Text for this action", - "title": "text" - }, - "displayText": { - "description": "(Optional) text to display in the chat feed if the button is clicked", - "title": "displayText" - }, - "value": { - "description": "Supplementary parameter for action. Content of this property depends on the ActionType", - "title": "value" - }, - "channelData": { - "description": "Channel-specific data associated with this action", - "title": "channelData" - } - } + "invalidPrompt": { + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression." }, - "SuggestedActions": { - "description": "SuggestedActions that can be performed", - "title": "SuggestedActions", - "properties": { - "to": { - "description": "Ids of the recipients that the actions should be shown to. These Ids are relative to the\nchannelId and a subset of all recipients of the activity", - "title": "to", - "items": { - "title": "Id", - "description": "Id of recipient." - } - }, - "actions": { - "description": "Actions that can be shown to the user", - "title": "actions" - } - } + "defaultValueResponse": { + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value." }, - "Attachment": { - "description": "An attachment within an activity", - "title": "Attachment", - "properties": { - "contentType": { - "description": "mimetype/Contenttype for the file", - "title": "contentType" - }, - "contentUrl": { - "description": "Content Url", - "title": "contentUrl" - }, - "content": { - "description": "Embedded content", - "title": "content" - }, - "name": { - "description": "(OPTIONAL) The name of the attachment", - "title": "name" - }, - "thumbnailUrl": { - "description": "(OPTIONAL) Thumbnail associated with attachment", - "title": "thumbnailUrl" - } - } + "maxTurnCount": { + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information." }, - "Entity": { - "description": "Metadata object pertaining to an activity", - "title": "Entity", - "properties": { - "type": { - "description": "Type of this entity (RFC 3987 IRI)", - "title": "type" - } + "validations": { + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid" } }, - "ConversationReference": { - "description": "An object relating to a particular point in a conversation", - "title": "ConversationReference", - "properties": { - "activityId": { - "description": "(Optional) ID of the activity to refer to", - "title": "activityId" - }, - "user": { - "description": "(Optional) User participating in this conversation", - "title": "user" - }, - "bot": { - "description": "Bot participating in this conversation", - "title": "bot" - }, - "conversation": { - "description": "Conversation reference", - "title": "conversation" - }, - "channelId": { - "description": "Channel ID", - "title": "channelId" - }, - "serviceUrl": { - "description": "Service endpoint where operations concerning the referenced conversation may be performed", - "title": "serviceUrl" - } - } + "property": { + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true)." }, - "TextHighlight": { - "description": "Refers to a substring of content within another field", - "title": "TextHighlight", - "properties": { - "text": { - "description": "Defines the snippet of text to highlight", - "title": "text" - }, - "occurrence": { - "description": "Occurrence of the text field within the referenced text, if multiple exist.", - "title": "occurrence" - } - } + "alwaysPrompt": { + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty." }, - "SemanticAction": { - "description": "Represents a reference to a programmatic action", - "title": "SemanticAction", - "properties": { - "id": { - "description": "ID of this action", - "title": "id" - }, - "entities": { - "description": "Entities associated with this action", - "title": "entities" - } - } + "allowInterruptions": { + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input." }, - "Activity": { - "description": "An Activity is the basic communication type for the Bot Framework 3.0 protocol.", + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + }, + "$designer": { + "title": "Designer information", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TextTemplate": { + "title": "Microsoft TextTemplate", + "description": "Use LG Templates to create text", + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "template": { + "title": "Template", + "description": "Language Generator template to evaluate to create the text." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + }, + "$designer": { + "title": "Designer information", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ThrowException": { + "title": "Throw an exception", + "description": "Throw an exception. Capture this exception with OnError trigger.", + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "title": "Disabled", + "description": "Optional condition which if true will disable this action." + }, + "errorValue": { + "title": "Error value", + "description": "Error value to throw." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + }, + "$designer": { + "title": "Designer information", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TraceActivity": { + "title": "Send a TraceActivity", + "description": "Send a trace activity to the transcript logger and/ or Bot Framework Emulator.", + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "title": "Disabled", + "description": "Optional condition which if true will disable this action." + }, + "name": { + "title": "Name", + "description": "Name of the trace activity" + }, + "label": { + "title": "Label", + "description": "Label for the trace activity (used to identify it in a list of trace activities.)" + }, + "valueType": { + "title": "Value type", + "description": "Type of value" + }, + "value": { + "title": "Value", + "description": "Property that holds the value to send as trace activity." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + }, + "$designer": { + "title": "Designer information", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TrueSelector": { + "title": "True trigger selector", + "description": "Selector for all true events", + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + }, + "$designer": { + "title": "Designer information", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.UpdateActivity": { + "title": "Update an activity", + "description": "Respond with an activity.", + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "title": "Disabled", + "description": "Optional condition which if true will disable this action." + }, + "activityId": { + "title": "Activity Id", + "description": "An string expression with the activity id to update." + }, + "activity": { "title": "Activity", - "properties": { - "type": { - "description": "Contains the activity type. Possible values include: 'message', 'contactRelationUpdate',\n'conversationUpdate', 'typing', 'endOfConversation', 'event', 'invoke', 'deleteUserData',\n'messageUpdate', 'messageDelete', 'installationUpdate', 'messageReaction', 'suggestion',\n'trace', 'handoff'", - "title": "type" - }, - "id": { - "description": "Contains an ID that uniquely identifies the activity on the channel.", - "title": "id" - }, - "timestamp": { - "description": "Contains the date and time that the message was sent, in UTC, expressed in ISO-8601 format.", - "title": "timestamp" - }, - "localTimestamp": { - "description": "Contains the date and time that the message was sent, in local time, expressed in ISO-8601\nformat.\nFor example, 2016-09-23T13:07:49.4714686-07:00.", - "title": "localTimestamp" - }, - "localTimezone": { - "description": "Contains the name of the timezone in which the message, in local time, expressed in IANA Time\nZone database format.\nFor example, America/Los_Angeles.", - "title": "localTimezone" - }, - "serviceUrl": { - "description": "Contains the URL that specifies the channel's service endpoint. Set by the channel.", - "title": "serviceUrl" - }, - "channelId": { - "description": "Contains an ID that uniquely identifies the channel. Set by the channel.", - "title": "channelId" - }, - "from": { - "description": "Identifies the sender of the message.", - "title": "from" - }, - "conversation": { - "description": "Identifies the conversation to which the activity belongs.", - "title": "conversation" - }, - "recipient": { - "description": "Identifies the recipient of the message.", - "title": "recipient" - }, - "textFormat": { - "description": "Format of text fields Default:markdown. Possible values include: 'markdown', 'plain', 'xml'", - "title": "textFormat" - }, - "attachmentLayout": { - "description": "The layout hint for multiple attachments. Default: list. Possible values include: 'list',\n'carousel'", - "title": "attachmentLayout" - }, - "membersAdded": { - "description": "The collection of members added to the conversation.", - "title": "membersAdded" - }, - "membersRemoved": { - "description": "The collection of members removed from the conversation.", - "title": "membersRemoved" - }, - "reactionsAdded": { - "description": "The collection of reactions added to the conversation.", - "title": "reactionsAdded" - }, - "reactionsRemoved": { - "description": "The collection of reactions removed from the conversation.", - "title": "reactionsRemoved" - }, - "topicName": { - "description": "The updated topic name of the conversation.", - "title": "topicName" - }, - "historyDisclosed": { - "description": "Indicates whether the prior history of the channel is disclosed.", - "title": "historyDisclosed" - }, - "locale": { - "description": "A locale name for the contents of the text field.\nThe locale name is a combination of an ISO 639 two- or three-letter culture code associated\nwith a language\nand an ISO 3166 two-letter subculture code associated with a country or region.\nThe locale name can also correspond to a valid BCP-47 language tag.", - "title": "locale" - }, - "text": { - "description": "The text content of the message.", - "title": "text" - }, - "speak": { - "description": "The text to speak.", - "title": "speak" - }, - "inputHint": { - "description": "Indicates whether your bot is accepting,\nexpecting, or ignoring user input after the message is delivered to the client. Possible\nvalues include: 'acceptingInput', 'ignoringInput', 'expectingInput'", - "title": "inputHint" - }, - "summary": { - "description": "The text to display if the channel cannot render cards.", - "title": "summary" - }, - "suggestedActions": { - "description": "The suggested actions for the activity.", - "title": "suggestedActions" - }, - "attachments": { - "description": "Attachments", - "title": "attachments" - }, - "entities": { - "description": "Represents the entities that were mentioned in the message.", - "title": "entities" - }, - "channelData": { - "description": "Contains channel-specific content.", - "title": "channelData" - }, - "action": { - "description": "Indicates whether the recipient of a contactRelationUpdate was added or removed from the\nsender's contact list.", - "title": "action" - }, - "replyToId": { - "description": "Contains the ID of the message to which this message is a reply.", - "title": "replyToId" - }, - "label": { - "description": "A descriptive label for the activity.", - "title": "label" - }, - "valueType": { - "description": "The type of the activity's value object.", - "title": "valueType" - }, - "value": { - "description": "A value that is associated with the activity.", - "title": "value" - }, - "name": { - "description": "The name of the operation associated with an invoke or event activity.", - "title": "name" - }, - "relatesTo": { - "description": "A reference to another conversation or activity.", - "title": "relatesTo" - }, - "code": { - "description": "The a code for endOfConversation activities that indicates why the conversation ended.\nPossible values include: 'unknown', 'completedSuccessfully', 'userCancelled', 'botTimedOut',\n'botIssuedInvalidMessage', 'channelFailed'", - "title": "code" - }, - "expiration": { - "description": "The time at which the activity should be considered to be \"expired\" and should not be\npresented to the recipient.", - "title": "expiration" - }, - "importance": { - "description": "The importance of the activity. Possible values include: 'low', 'normal', 'high'", - "title": "importance" - }, - "deliveryMode": { - "description": "A delivery hint to signal to the recipient alternate delivery paths for the activity.\nThe default delivery mode is \"default\". Possible values include: 'normal', 'notification'", - "title": "deliveryMode" - }, - "listenFor": { - "description": "List of phrases and references that speech and language priming systems should listen for", - "title": "listenFor", - "items": { - "title": "Phrase", - "description": "Phrase to listen for." - } - }, - "textHighlights": { - "description": "The collection of text fragments to highlight when the activity contains a ReplyToId value.", - "title": "textHighlights" - }, - "semanticAction": { - "description": "An optional programmatic action accompanying this request", - "title": "semanticAction" - } - } + "description": "Activity to send." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + }, + "$designer": { + "title": "Designer information", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.UrlEntityRecognizer": { + "title": "Url recognizer", + "description": "Recognizer which recognizes urls.", + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + }, + "$designer": { + "title": "Designer information", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "arrayExpression": { + "title": "Array or expression", + "description": "Array or expression to evaluate.", + "oneOf": { + "0": { + "title": "Array", + "description": "Array constant." + } + } + }, + "booleanExpression": { + "title": "Boolean or expression", + "description": "Boolean constant or expression to evaluate.", + "oneOf": { + "0": { + "title": "Boolean", + "description": "Boolean constant." + } + } + }, + "component": { + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)" + }, + "$designer": { + "title": "Designer information", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "condition": { + "title": "Boolean condition", + "description": "Boolean constant or expression to evaluate.", + "oneOf": { + "1": { + "title": "Boolean", + "description": "Boolean value." + } + } + }, + "equalsExpression": { + "title": "Expression", + "description": "Expression starting with =." + }, + "expression": { + "title": "Expression", + "description": "Expression to evaluate." + }, + "integerExpression": { + "title": "Integer or expression", + "description": "Integer constant or expression to evaluate.", + "oneOf": { + "0": { + "title": "Integer", + "description": "Integer constant." + } + } + }, + "numberExpression": { + "title": "Number or expression", + "description": "Number constant or expression to evaluate.", + "oneOf": { + "0": { + "title": "Number", + "description": "Number constant." + } + } + }, + "objectExpression": { + "title": "Object or expression", + "description": "Object or expression to evaluate.", + "oneOf": { + "0": { + "title": "Object", + "description": "Object constant." + } + } + }, + "role": { + "title": "$role", + "description": "Defines the role played in the dialog schema from [expression|interface|implements($kind)|extends($kind)]." + }, + "stringExpression": { + "title": "String or expression", + "description": "Interpolated string or expression to evaluate.", + "oneOf": { + "0": { + "title": "String", + "description": "Interpolated string" + } + } + }, + "valueExpression": { + "title": "Any or expression", + "description": "Any constant or expression to evaluate.", + "oneOf": { + "0": { + "title": "Object", + "description": "Object constant." + }, + "1": { + "title": "Array", + "description": "Array constant." + }, + "2": { + "title": "String", + "description": "Interpolated string." + }, + "3": { + "title": "Boolean", + "description": "Boolean constant" + }, + "4": { + "title": "Number", + "description": "Number constant." } } } diff --git a/Composer/packages/server/schemas/sdk.en-US.uischema b/Composer/packages/server/schemas/sdk.en-US.uischema index 4e9c20df03..ee6a4a14c3 100644 --- a/Composer/packages/server/schemas/sdk.en-US.uischema +++ b/Composer/packages/server/schemas/sdk.en-US.uischema @@ -11,6 +11,54 @@ } } }, + "Microsoft.Ask": { + "form": { + "label": "Send a response to ask a question", + "subtitle": "Ask Activity" + } + }, + "Microsoft.AttachmentInput": { + "form": { + "label": "Prompt for a file or an attachment", + "subtitle": "Attachment Input" + } + }, + "Microsoft.ChoiceInput": { + "form": { + "label": "Prompt with multi-choice", + "subtitle": "Choice Input" + } + }, + "Microsoft.ConfirmInput": { + "form": { + "label": "Prompt for confirmation", + "subtitle": "Confirm Input" + } + }, + "Microsoft.DateTimeInput": { + "form": { + "label": "Prompt for a date or a time", + "subtitle": "Date Time Input" + } + }, + "Microsoft.NumberInput": { + "form": { + "label": "Prompt for a number", + "subtitle": "Number Input" + } + }, + "Microsoft.OAuthInput": { + "form": { + "label": "OAuth login", + "subtitle": "OAuth Input" + } + }, + "Microsoft.TextInput": { + "form": { + "label": "Prompt for text", + "subtitle": "Text Input" + } + }, "Microsoft.BeginDialog": { "form": { "label": "Begin a new dialog", @@ -172,54 +220,6 @@ "subtitle": "Trace Activity" } }, - "Microsoft.Ask": { - "form": { - "label": "Send a response to ask a question", - "subtitle": "Ask Activity" - } - }, - "Microsoft.AttachmentInput": { - "form": { - "label": "Prompt for a file or an attachment", - "subtitle": "Attachment Input" - } - }, - "Microsoft.ChoiceInput": { - "form": { - "label": "Prompt with multi-choice", - "subtitle": "Choice Input" - } - }, - "Microsoft.ConfirmInput": { - "form": { - "label": "Prompt for confirmation", - "subtitle": "Confirm Input" - } - }, - "Microsoft.DateTimeInput": { - "form": { - "label": "Prompt for a date or a time", - "subtitle": "Date Time Input" - } - }, - "Microsoft.NumberInput": { - "form": { - "label": "Prompt for a number", - "subtitle": "Number Input" - } - }, - "Microsoft.OAuthInput": { - "form": { - "label": "OAuth login", - "subtitle": "OAuth Input" - } - }, - "Microsoft.TextInput": { - "form": { - "label": "Prompt for text", - "subtitle": "Text Input" - } - }, "Microsoft.OnActivity": { "form": { "label": "Activities", diff --git a/Composer/packages/server/schemas/sdk.es.schema b/Composer/packages/server/schemas/sdk.es.schema index 78d7358437..8a69e95d16 100644 --- a/Composer/packages/server/schemas/sdk.es.schema +++ b/Composer/packages/server/schemas/sdk.es.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.fr.schema b/Composer/packages/server/schemas/sdk.fr.schema index 37ec51db98..f741bac829 100644 --- a/Composer/packages/server/schemas/sdk.fr.schema +++ b/Composer/packages/server/schemas/sdk.fr.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.hu.schema b/Composer/packages/server/schemas/sdk.hu.schema index b955a31a18..5815799c54 100644 --- a/Composer/packages/server/schemas/sdk.hu.schema +++ b/Composer/packages/server/schemas/sdk.hu.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.it.schema b/Composer/packages/server/schemas/sdk.it.schema index 16a3523efa..beb8fe14ee 100644 --- a/Composer/packages/server/schemas/sdk.it.schema +++ b/Composer/packages/server/schemas/sdk.it.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.ja.schema b/Composer/packages/server/schemas/sdk.ja.schema index 363885a333..e2f4ffc980 100644 --- a/Composer/packages/server/schemas/sdk.ja.schema +++ b/Composer/packages/server/schemas/sdk.ja.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.ko.schema b/Composer/packages/server/schemas/sdk.ko.schema index b70bea930b..568800fd1a 100644 --- a/Composer/packages/server/schemas/sdk.ko.schema +++ b/Composer/packages/server/schemas/sdk.ko.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.nl.schema b/Composer/packages/server/schemas/sdk.nl.schema index cf99199edf..acf40b609f 100644 --- a/Composer/packages/server/schemas/sdk.nl.schema +++ b/Composer/packages/server/schemas/sdk.nl.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.pl.schema b/Composer/packages/server/schemas/sdk.pl.schema index b84fbbd8c6..03d4578aac 100644 --- a/Composer/packages/server/schemas/sdk.pl.schema +++ b/Composer/packages/server/schemas/sdk.pl.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Wyślij działanie", + "title": "Zmodernizować działanie", "description": "Reaguj działaniem.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.pt-BR.schema b/Composer/packages/server/schemas/sdk.pt-BR.schema index 06711c6553..075892c959 100644 --- a/Composer/packages/server/schemas/sdk.pt-BR.schema +++ b/Composer/packages/server/schemas/sdk.pt-BR.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.pt-PT.schema b/Composer/packages/server/schemas/sdk.pt-PT.schema index ad6caf5408..b7719b0aa7 100644 --- a/Composer/packages/server/schemas/sdk.pt-PT.schema +++ b/Composer/packages/server/schemas/sdk.pt-PT.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.ru.schema b/Composer/packages/server/schemas/sdk.ru.schema index fdc1363f6c..f2028f4ab9 100644 --- a/Composer/packages/server/schemas/sdk.ru.schema +++ b/Composer/packages/server/schemas/sdk.ru.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.schema b/Composer/packages/server/schemas/sdk.schema index e498b0f146..a1f0436797 100644 --- a/Composer/packages/server/schemas/sdk.schema +++ b/Composer/packages/server/schemas/sdk.schema @@ -34,6 +34,9 @@ { "$ref": "#/definitions/Microsoft.CancelDialog" }, + { + "$ref": "#/definitions/Microsoft.ChannelMentionEntityRecognizer" + }, { "$ref": "#/definitions/Microsoft.ChoiceInput" }, @@ -46,6 +49,9 @@ { "$ref": "#/definitions/Microsoft.ConfirmationEntityRecognizer" }, + { + "$ref": "#/definitions/Microsoft.ContinueConversationLater" + }, { "$ref": "#/definitions/Microsoft.ContinueLoop" }, @@ -202,6 +208,9 @@ { "$ref": "#/definitions/Microsoft.OnHandoffActivity" }, + { + "$ref": "#/definitions/Microsoft.OnInstallationUpdateActivity" + }, { "$ref": "#/definitions/Microsoft.OnIntent" }, @@ -301,6 +310,9 @@ { "$ref": "#/definitions/Microsoft.TextTemplate" }, + { + "$ref": "#/definitions/Microsoft.ThrowException" + }, { "$ref": "#/definitions/Microsoft.TraceActivity" }, @@ -317,7 +329,7 @@ "definitions": { "Microsoft.ActivityTemplate": { "$role": "implements(Microsoft.IActivityTemplate)", - "title": "Microsoft ActivityTemplate", + "title": "Microsoft activity template", "type": "object", "required": [ "template", @@ -325,7 +337,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -356,12 +368,12 @@ }, "Microsoft.AdaptiveDialog": { "$role": "implements(Microsoft.IDialog)", - "title": "Adaptive Dialog", + "title": "Adaptive dialog", "description": "Flexible, data driven dialog that can adapt to the conversation.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -399,7 +411,7 @@ }, "generator": { "$kind": "Microsoft.ILanguageGenerator", - "title": "Language Generator", + "title": "Language generator", "description": "Language generator that generates bot responses.", "$ref": "#/definitions/Microsoft.ILanguageGenerator" }, @@ -425,7 +437,239 @@ "description": "Schema to fill in.", "anyOf": [ { - "$ref": "#/definitions/schema" + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0" + } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "uniqueItems": true, + "default": [], + "items": { + "type": "string" + } + } + }, + "type": [ + "object", + "boolean" + ], + "properties": { + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0/definitions/nonNegativeInteger" + }, + "minLength": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0/definitions/nonNegativeIntegerDefault0" + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0" + }, + "items": { + "anyOf": [ + { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0" + }, + { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0/definitions/schemaArray" + } + ], + "default": true + }, + "maxItems": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0/definitions/nonNegativeInteger" + }, + "minItems": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0/definitions/nonNegativeIntegerDefault0" + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0" + }, + "maxProperties": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0/definitions/nonNegativeInteger" + }, + "minProperties": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0/definitions/nonNegativeIntegerDefault0" + }, + "required": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0/definitions/stringArray" + }, + "additionalProperties": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0" + }, + "definitions": { + "type": "object", + "default": {}, + "additionalProperties": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0" + } + }, + "properties": { + "type": "object", + "default": {}, + "additionalProperties": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0" + } + }, + "patternProperties": { + "type": "object", + "propertyNames": { + "format": "regex" + }, + "default": {}, + "additionalProperties": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0" + } + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0" + }, + { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0/definitions/stringArray" + } + ] + } + }, + "propertyNames": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0" + }, + "const": true, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": true + }, + "type": { + "anyOf": [ + { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0/definitions/simpleTypes" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0/definitions/simpleTypes" + }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "contentEncoding": { + "type": "string" + }, + "if": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0" + }, + "then": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0" + }, + "else": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0" + }, + "allOf": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0/definitions/schemaArray" + }, + "anyOf": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0/definitions/schemaArray" + }, + "oneOf": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0/definitions/schemaArray" + }, + "not": { + "$ref": "#/definitions/Microsoft.AdaptiveDialog/properties/schema/anyOf/0" + } + }, + "default": true }, { "type": "string", @@ -449,13 +693,16 @@ } }, "Microsoft.AgeEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Age Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Age entity recognizer", "description": "Recognizer which recognizes age.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -487,12 +734,12 @@ "implements(Microsoft.IDialog)", "extends(Microsoft.SendActivity)" ], - "title": "Send Activity to Ask a question", + "title": "Send activity to ask a question", "description": "This is an action which sends an activity to the user when a response is expected", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -507,7 +754,7 @@ "properties": { "expectedProperties": { "$ref": "#/definitions/arrayExpression", - "title": "Expected Properties", + "title": "Expected properties", "description": "Properties expected from the user.", "examples": [ [ @@ -523,7 +770,7 @@ }, "defaultOperation": { "$ref": "#/definitions/stringExpression", - "title": "Default Operation", + "title": "Default operation", "description": "Sets the default operation that will be used when no operation is recognized in the response to this Ask.", "examples": [ "Add()", @@ -536,7 +783,7 @@ "description": "Optional id for the dialog" }, "disabled": { - "$ref": "#/definitions/stringExpression", + "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", "examples": [ @@ -572,7 +819,7 @@ "description": "Collect information - Ask for a file or image.", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "type": "object", "required": [ @@ -592,7 +839,7 @@ "description": "'Property' will be set to the object or the result of this expression when max turn count is exceeded.", "oneOf": [ { - "$ref": "#/definitions/botframework.json/definitions/Attachment", + "$ref": "#/definitions/Microsoft.IActivityTemplate/oneOf/1/properties/attachments/items", "title": "Object", "description": "Attachment object." }, @@ -607,7 +854,7 @@ "description": "'Property' will be set to the object or the result of this expression unless it evaluates to null.", "oneOf": [ { - "$ref": "#/definitions/botframework.json/definitions/Attachment", + "$ref": "#/definitions/Microsoft.IActivityTemplate/oneOf/1/properties/attachments/items", "title": "Object", "description": "Attachment object." }, @@ -762,7 +1009,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -823,7 +1070,7 @@ }, "activityProcessed": { "$ref": "#/definitions/booleanExpression", - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the dialog that is called can process the current activity.", "default": true }, @@ -856,7 +1103,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -885,7 +1132,7 @@ }, "activityProcessed": { "$ref": "#/definitions/booleanExpression", - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the skill will be started using the activity in the current turn context instead of the activity in the Activity property.", "default": true, "examples": [ @@ -918,13 +1165,13 @@ }, "connectionName": { "$ref": "#/definitions/stringExpression", - "title": "OAuth Connection Name (SSO)", + "title": "OAuth connection name (SSO)", "description": "The OAuth Connection Name, that would be used to perform Single SignOn with a skill.", "default": "=settings.connectionName" }, "skillAppId": { "$ref": "#/definitions/stringExpression", - "title": "Skill App ID", + "title": "Skill App Id", "description": "The Microsoft App ID for the skill." }, "skillEndpoint": { @@ -942,13 +1189,13 @@ "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "allowInterruptions": { - "$ref": "schema:#/definitions/booleanExpression", - "title": "Allow Interruptions", + "$ref": "#/definitions/booleanExpression", + "title": "Allow interruptions", "description": "A boolean expression that determines whether the parent should be allowed to interrupt the skill.", "default": true, "examples": [ - true, - "=user.xyz" + true, + "=user.xyz" ] }, "$kind": { @@ -967,7 +1214,7 @@ }, "Microsoft.BreakLoop": { "$role": "implements(Microsoft.IDialog)", - "title": "Break Loop", + "title": "Break loop", "description": "Stop executing this loop", "type": "object", "required": [ @@ -975,7 +1222,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -1019,7 +1266,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -1047,7 +1294,7 @@ }, "activityProcessed": { "$ref": "#/definitions/booleanExpression", - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the caller dialog is told it should process the current activity.", "default": true }, @@ -1083,7 +1330,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -1111,7 +1358,7 @@ }, "activityProcessed": { "$ref": "#/definitions/booleanExpression", - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the caller dialog is told it should process the current activity.", "default": true }, @@ -1140,6 +1387,42 @@ } } }, + "Microsoft.ChannelMentionEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)" + ], + "title": "Channel mention entity recognizer", + "description": "Promotes mention entities passed by a channel via the activity.entities into recognizer result.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.11.0-rc2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ChannelMentionEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, "Microsoft.ChoiceInput": { "$role": [ "implements(Microsoft.IDialog)", @@ -1150,7 +1433,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -1236,7 +1519,7 @@ "description": "Value to return when this choice is selected." }, "action": { - "$ref": "#/definitions/botframework.json/definitions/CardAction", + "$ref": "#/definitions/Microsoft.IActivityTemplate/oneOf/1/properties/suggestedActions/properties/actions/items", "title": "Action", "description": "Card action for the choice." }, @@ -1493,7 +1776,7 @@ }, "Microsoft.ConditionalSelector": { "$role": "implements(Microsoft.ITriggerSelector)", - "title": "Conditional Trigger Selector", + "title": "Conditional trigger selector", "description": "Use a rule selector based on a condition", "type": "object", "required": [ @@ -1504,7 +1787,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -1551,7 +1834,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -1606,7 +1889,7 @@ ] }, "choiceOptions": { - "title": "Choice Options", + "title": "Choice options", "description": "Choice Options or expression which provides Choice Options to control display choices to the user.", "oneOf": [ { @@ -1696,7 +1979,7 @@ "description": "Value to return when this choice is selected." }, "action": { - "$ref": "#/definitions/botframework.json/definitions/CardAction", + "$ref": "#/definitions/Microsoft.IActivityTemplate/oneOf/1/properties/suggestedActions/properties/actions/items", "title": "Action", "description": "Card action for the choice." }, @@ -1839,13 +2122,16 @@ } }, "Microsoft.ConfirmationEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Confirmation Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Confirmation entity recognizer", "description": "Recognizer which recognizes confirmation choices (yes/no).", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -1872,9 +2158,71 @@ } } }, + "Microsoft.ContinueConversationLater": { + "$role": "implements(Microsoft.IDialog)", + "title": "Continue conversation later (Queue)", + "description": "Continue conversation at later time (via Azure Storage Queue).", + "type": "object", + "required": [ + "date", + "connectionString", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.11.0-rc2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "date": { + "$ref": "#/definitions/stringExpression", + "title": "Date", + "description": "Date in the future as a ISO string when the conversation should continue.", + "examples": [ + "=addHours(utcNow(), 1)" + ] + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "Value to send in the activity.value." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ContinueConversationLater" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, "Microsoft.ContinueLoop": { "$role": "implements(Microsoft.IDialog)", - "title": "Continue Loop", + "title": "Continue loop", "description": "Stop executing this template and continue with the next iteration of the loop.", "type": "object", "required": [ @@ -1882,7 +2230,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -1921,7 +2269,7 @@ }, "Microsoft.CrossTrainedRecognizerSet": { "$role": "implements(Microsoft.IRecognizer)", - "title": "Cross-trained Recognizer Set", + "title": "Cross-trained recognizer set", "description": "Recognizer for selecting between cross trained recognizers.", "type": "object", "required": [ @@ -1930,7 +2278,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -1969,13 +2317,16 @@ } }, "Microsoft.CurrencyEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Currency Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Currency entity recognizer", "description": "Recognizer which recognizes currency.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2003,13 +2354,16 @@ } }, "Microsoft.DateTimeEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "DateTime Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Date and time entity recognizer", "description": "Recognizer which recognizes dates and time fragments.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2052,7 +2406,7 @@ }, "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2068,7 +2422,7 @@ "defaultValue": { "$ref": "#/definitions/stringExpression", "format": "date-time", - "title": "Default Date", + "title": "Default date", "description": "'Property' will be set to the value or the result of the expression when max turn count is exceeded.", "examples": [ "=user.birthday" @@ -2217,7 +2571,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2268,7 +2622,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -2324,7 +2678,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -2373,7 +2727,7 @@ }, "Microsoft.DeleteProperty": { "$role": "implements(Microsoft.IDialog)", - "title": "Delete Property", + "title": "Delete property", "description": "Delete a property and any value it holds.", "type": "object", "required": [ @@ -2382,7 +2736,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -2425,13 +2779,16 @@ } }, "Microsoft.DimensionEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Dimension Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Dimension entity recognizer", "description": "Recognizer which recognizes dimension.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2470,7 +2827,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -2548,7 +2905,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -2569,7 +2926,7 @@ "oneOf": [ { "type": "string", - "title": "Enum", + "title": "Change type", "description": "Standard change type.", "enum": [ "push", @@ -2599,7 +2956,7 @@ }, "resultProperty": { "$ref": "#/definitions/stringExpression", - "title": "Result Property", + "title": "Result property", "description": "Property to store the result of this action." }, "value": { @@ -2627,13 +2984,16 @@ } }, "Microsoft.EmailEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Email Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Email entity recognizer", "description": "Recognizer which recognizes email.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2671,7 +3031,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -2760,7 +3120,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2816,7 +3176,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2858,12 +3218,12 @@ }, "Microsoft.FirstSelector": { "$role": "implements(Microsoft.ITriggerSelector)", - "title": "First Trigger Selector", + "title": "First trigger selector", "description": "Selector for first true rule", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2902,7 +3262,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -2980,7 +3340,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -3054,12 +3414,12 @@ }, "Microsoft.GetActivityMembers": { "$role": "implements(Microsoft.IDialog)", - "title": "Get Activity Members", + "title": "Get activity members", "description": "Get the members who are participating in an activity. (BotFrameworkAdapter only)", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -3087,7 +3447,7 @@ }, "activityId": { "$ref": "#/definitions/stringExpression", - "title": "ActivityId", + "title": "Activity Id", "description": "Activity ID or expression to an activityId to use to get the members. If none is defined then the current activity id will be used.", "examples": [ "$lastActivity" @@ -3117,12 +3477,12 @@ }, "Microsoft.GetConversationMembers": { "$role": "implements(Microsoft.IDialog)", - "title": "Get Converation Members", + "title": "Get conversation members", "description": "Get the members who are participating in an conversation. (BotFrameworkAdapter only)", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -3172,7 +3532,7 @@ }, "Microsoft.GotoAction": { "$role": "implements(Microsoft.IDialog)", - "title": "Go to Action", + "title": "Go to action", "description": "Go to an an action by id.", "type": "object", "required": [ @@ -3181,7 +3541,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -3224,13 +3584,16 @@ } }, "Microsoft.GuidEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Guid Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Guid entity recognizer", "description": "Recognizer which recognizes guids.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -3258,13 +3621,16 @@ } }, "Microsoft.HashtagEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Hashtag Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Hashtag entity recognizer", "description": "Recognizer which recognizes Hashtags.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -3303,7 +3669,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -3427,73 +3793,579 @@ "type": "string" }, { - "$ref": "#/definitions/botframework.json/definitions/Activity", "required": [ "type" - ] - }, - { - "$ref": "#/definitions/Microsoft.ActivityTemplate" - }, - { - "$ref": "#/definitions/Microsoft.StaticActivityTemplate" - } - ], - "$package": { - "name": "Microsoft.Bot.Builder.Dialogs.Declarative", - "version": "4.10.0" - } - }, - "Microsoft.IDialog": { - "title": "Microsoft Dialogs", - "description": "Components which derive from Dialog", - "$role": "interface", - "oneOf": [ - { - "type": "string" - }, - { - "$ref": "#/definitions/Microsoft.QnAMakerDialog" - }, - { - "$ref": "#/definitions/Microsoft.AdaptiveDialog" - }, - { - "$ref": "#/definitions/Microsoft.BeginDialog" - }, - { - "$ref": "#/definitions/Microsoft.BeginSkill" - }, - { - "$ref": "#/definitions/Microsoft.BreakLoop" - }, - { - "$ref": "#/definitions/Microsoft.CancelAllDialogs" - }, - { - "$ref": "#/definitions/Microsoft.CancelDialog" - }, - { - "$ref": "#/definitions/Microsoft.ContinueLoop" - }, - { - "$ref": "#/definitions/Microsoft.DebugBreak" - }, - { - "$ref": "#/definitions/Microsoft.DeleteActivity" - }, - { - "$ref": "#/definitions/Microsoft.DeleteProperties" - }, - { - "$ref": "#/definitions/Microsoft.DeleteProperty" - }, - { - "$ref": "#/definitions/Microsoft.EditActions" - }, - { - "$ref": "#/definitions/Microsoft.EditArray" - }, + ], + "description": "An Activity is the basic communication type for the Bot Framework 3.0 protocol.", + "title": "Activity", + "type": "object", + "properties": { + "type": { + "description": "Contains the activity type. Possible values include: 'message', 'contactRelationUpdate',\n'conversationUpdate', 'typing', 'endOfConversation', 'event', 'invoke', 'deleteUserData',\n'messageUpdate', 'messageDelete', 'installationUpdate', 'messageReaction', 'suggestion',\n'trace', 'handoff'", + "type": "string", + "title": "type" + }, + "id": { + "description": "Contains an ID that uniquely identifies the activity on the channel.", + "type": "string", + "title": "id" + }, + "timestamp": { + "description": "Contains the date and time that the message was sent, in UTC, expressed in ISO-8601 format.", + "type": "string", + "format": "date-time", + "title": "timestamp" + }, + "localTimestamp": { + "description": "Contains the date and time that the message was sent, in local time, expressed in ISO-8601\nformat.\nFor example, 2016-09-23T13:07:49.4714686-07:00.", + "type": "string", + "format": "date-time", + "title": "localTimestamp" + }, + "localTimezone": { + "description": "Contains the name of the timezone in which the message, in local time, expressed in IANA Time\nZone database format.\nFor example, America/Los_Angeles.", + "type": "string", + "title": "localTimezone" + }, + "serviceUrl": { + "description": "Contains the URL that specifies the channel's service endpoint. Set by the channel.", + "type": "string", + "title": "serviceUrl" + }, + "channelId": { + "description": "Contains an ID that uniquely identifies the channel. Set by the channel.", + "type": "string", + "title": "channelId" + }, + "from": { + "$ref": "#/definitions/Microsoft.IActivityTemplate/oneOf/1/properties/membersAdded/items", + "description": "Identifies the sender of the message.", + "title": "from" + }, + "conversation": { + "description": "Identifies the conversation to which the activity belongs.", + "title": "conversation", + "type": "object", + "required": [ + "conversationType", + "id", + "isGroup", + "name" + ], + "properties": { + "isGroup": { + "description": "Indicates whether the conversation contains more than two participants at the time the\nactivity was generated", + "type": "boolean", + "title": "isGroup" + }, + "conversationType": { + "description": "Indicates the type of the conversation in channels that distinguish between conversation types", + "type": "string", + "title": "conversationType" + }, + "id": { + "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or\n123456)", + "type": "string", + "title": "id" + }, + "name": { + "description": "Display friendly name", + "type": "string", + "title": "name" + }, + "aadObjectId": { + "description": "This account's object ID within Azure Active Directory (AAD)", + "type": "string", + "title": "aadObjectId" + }, + "role": { + "description": "Role of the entity behind the account (Example: User, Bot, etc.). Possible values include:\n'user', 'bot'", + "enum": [ + "bot", + "user" + ], + "type": "string", + "title": "role" + } + } + }, + "recipient": { + "$ref": "#/definitions/Microsoft.IActivityTemplate/oneOf/1/properties/membersAdded/items", + "description": "Identifies the recipient of the message.", + "title": "recipient" + }, + "textFormat": { + "description": "Format of text fields Default:markdown. Possible values include: 'markdown', 'plain', 'xml'", + "type": "string", + "title": "textFormat" + }, + "attachmentLayout": { + "description": "The layout hint for multiple attachments. Default: list. Possible values include: 'list',\n'carousel'", + "type": "string", + "title": "attachmentLayout" + }, + "membersAdded": { + "description": "The collection of members added to the conversation.", + "type": "array", + "title": "membersAdded", + "items": { + "description": "Channel account information needed to route a message", + "title": "ChannelAccount", + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or\n123456)", + "type": "string", + "title": "id" + }, + "name": { + "description": "Display friendly name", + "type": "string", + "title": "name" + }, + "aadObjectId": { + "description": "This account's object ID within Azure Active Directory (AAD)", + "type": "string", + "title": "aadObjectId" + }, + "role": { + "description": "Role of the entity behind the account (Example: User, Bot, etc.). Possible values include:\n'user', 'bot'", + "type": "string", + "title": "role" + } + } + } + }, + "membersRemoved": { + "description": "The collection of members removed from the conversation.", + "type": "array", + "title": "membersRemoved", + "items": { + "$ref": "#/definitions/Microsoft.IActivityTemplate/oneOf/1/properties/membersAdded/items" + } + }, + "reactionsAdded": { + "description": "The collection of reactions added to the conversation.", + "type": "array", + "title": "reactionsAdded", + "items": { + "description": "Message reaction object", + "title": "MessageReaction", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "description": "Message reaction type. Possible values include: 'like', 'plusOne'", + "type": "string", + "title": "type" + } + } + } + }, + "reactionsRemoved": { + "description": "The collection of reactions removed from the conversation.", + "type": "array", + "title": "reactionsRemoved", + "items": { + "$ref": "#/definitions/Microsoft.IActivityTemplate/oneOf/1/properties/reactionsAdded/items" + } + }, + "topicName": { + "description": "The updated topic name of the conversation.", + "type": "string", + "title": "topicName" + }, + "historyDisclosed": { + "description": "Indicates whether the prior history of the channel is disclosed.", + "type": "boolean", + "title": "historyDisclosed" + }, + "locale": { + "description": "A locale name for the contents of the text field.\nThe locale name is a combination of an ISO 639 two- or three-letter culture code associated\nwith a language\nand an ISO 3166 two-letter subculture code associated with a country or region.\nThe locale name can also correspond to a valid BCP-47 language tag.", + "type": "string", + "title": "locale" + }, + "text": { + "description": "The text content of the message.", + "type": "string", + "title": "text" + }, + "speak": { + "description": "The text to speak.", + "type": "string", + "title": "speak" + }, + "inputHint": { + "description": "Indicates whether your bot is accepting,\nexpecting, or ignoring user input after the message is delivered to the client. Possible\nvalues include: 'acceptingInput', 'ignoringInput', 'expectingInput'", + "type": "string", + "title": "inputHint" + }, + "summary": { + "description": "The text to display if the channel cannot render cards.", + "type": "string", + "title": "summary" + }, + "suggestedActions": { + "description": "The suggested actions for the activity.", + "title": "suggestedActions", + "type": "object", + "required": [ + "actions", + "to" + ], + "properties": { + "to": { + "description": "Ids of the recipients that the actions should be shown to. These Ids are relative to the\nchannelId and a subset of all recipients of the activity", + "type": "array", + "title": "to", + "items": { + "title": "Id", + "description": "Id of recipient.", + "type": "string" + } + }, + "actions": { + "description": "Actions that can be shown to the user", + "type": "array", + "title": "actions", + "items": { + "description": "A clickable action", + "title": "CardAction", + "type": "object", + "required": [ + "title", + "type", + "value" + ], + "properties": { + "type": { + "description": "The type of action implemented by this button. Possible values include: 'openUrl', 'imBack',\n'postBack', 'playAudio', 'playVideo', 'showImage', 'downloadFile', 'signin', 'call',\n'payment', 'messageBack'", + "type": "string", + "title": "type" + }, + "title": { + "description": "Text description which appears on the button", + "type": "string", + "title": "title" + }, + "image": { + "description": "Image URL which will appear on the button, next to text label", + "type": "string", + "title": "image" + }, + "text": { + "description": "Text for this action", + "type": "string", + "title": "text" + }, + "displayText": { + "description": "(Optional) text to display in the chat feed if the button is clicked", + "type": "string", + "title": "displayText" + }, + "value": { + "description": "Supplementary parameter for action. Content of this property depends on the ActionType", + "title": "value" + }, + "channelData": { + "description": "Channel-specific data associated with this action", + "title": "channelData" + } + } + } + } + } + }, + "attachments": { + "description": "Attachments", + "type": "array", + "title": "attachments", + "items": { + "description": "An attachment within an activity", + "title": "Attachment", + "type": "object", + "required": [ + "contentType" + ], + "properties": { + "contentType": { + "description": "mimetype/Contenttype for the file", + "type": "string", + "title": "contentType" + }, + "contentUrl": { + "description": "Content Url", + "type": "string", + "title": "contentUrl" + }, + "content": { + "type": "object", + "description": "Embedded content", + "title": "content" + }, + "name": { + "description": "(OPTIONAL) The name of the attachment", + "type": "string", + "title": "name" + }, + "thumbnailUrl": { + "description": "(OPTIONAL) Thumbnail associated with attachment", + "type": "string", + "title": "thumbnailUrl" + } + } + } + }, + "entities": { + "description": "Represents the entities that were mentioned in the message.", + "type": "array", + "title": "entities", + "items": { + "description": "Metadata object pertaining to an activity", + "title": "Entity", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "description": "Type of this entity (RFC 3987 IRI)", + "type": "string", + "title": "type" + } + } + } + }, + "channelData": { + "description": "Contains channel-specific content.", + "title": "channelData" + }, + "action": { + "description": "Indicates whether the recipient of a contactRelationUpdate was added or removed from the\nsender's contact list.", + "type": "string", + "title": "action" + }, + "replyToId": { + "description": "Contains the ID of the message to which this message is a reply.", + "type": "string", + "title": "replyToId" + }, + "label": { + "description": "A descriptive label for the activity.", + "type": "string", + "title": "label" + }, + "valueType": { + "description": "The type of the activity's value object.", + "type": "string", + "title": "valueType" + }, + "value": { + "description": "A value that is associated with the activity.", + "title": "value" + }, + "name": { + "description": "The name of the operation associated with an invoke or event activity.", + "type": "string", + "title": "name" + }, + "relatesTo": { + "description": "A reference to another conversation or activity.", + "title": "relatesTo", + "type": "object", + "required": [ + "bot", + "channelId", + "conversation", + "serviceUrl" + ], + "properties": { + "activityId": { + "description": "(Optional) ID of the activity to refer to", + "type": "string", + "title": "activityId" + }, + "user": { + "description": "(Optional) User participating in this conversation", + "$ref": "#/definitions/Microsoft.IActivityTemplate/oneOf/1/properties/membersAdded/items", + "title": "user" + }, + "bot": { + "$ref": "#/definitions/Microsoft.IActivityTemplate/oneOf/1/properties/membersAdded/items", + "description": "Bot participating in this conversation", + "title": "bot" + }, + "conversation": { + "$ref": "#/definitions/Microsoft.IActivityTemplate/oneOf/1/properties/conversation", + "description": "Conversation reference", + "title": "conversation" + }, + "channelId": { + "description": "Channel ID", + "type": "string", + "title": "channelId" + }, + "serviceUrl": { + "description": "Service endpoint where operations concerning the referenced conversation may be performed", + "type": "string", + "title": "serviceUrl" + } + } + }, + "code": { + "description": "The a code for endOfConversation activities that indicates why the conversation ended.\nPossible values include: 'unknown', 'completedSuccessfully', 'userCancelled', 'botTimedOut',\n'botIssuedInvalidMessage', 'channelFailed'", + "type": "string", + "title": "code" + }, + "expiration": { + "description": "The time at which the activity should be considered to be \"expired\" and should not be\npresented to the recipient.", + "type": "string", + "format": "date-time", + "title": "expiration" + }, + "importance": { + "description": "The importance of the activity. Possible values include: 'low', 'normal', 'high'", + "type": "string", + "title": "importance" + }, + "deliveryMode": { + "description": "A delivery hint to signal to the recipient alternate delivery paths for the activity.\nThe default delivery mode is \"default\". Possible values include: 'normal', 'notification'", + "type": "string", + "title": "deliveryMode" + }, + "listenFor": { + "description": "List of phrases and references that speech and language priming systems should listen for", + "type": "array", + "title": "listenFor", + "items": { + "type": "string", + "title": "Phrase", + "description": "Phrase to listen for." + } + }, + "textHighlights": { + "description": "The collection of text fragments to highlight when the activity contains a ReplyToId value.", + "type": "array", + "title": "textHighlights", + "items": { + "description": "Refers to a substring of content within another field", + "title": "TextHighlight", + "type": "object", + "required": [ + "occurrence", + "text" + ], + "properties": { + "text": { + "description": "Defines the snippet of text to highlight", + "type": "string", + "title": "text" + }, + "occurrence": { + "description": "Occurrence of the text field within the referenced text, if multiple exist.", + "type": "number", + "title": "occurrence" + } + } + } + }, + "semanticAction": { + "description": "An optional programmatic action accompanying this request", + "title": "semanticAction", + "type": "object", + "required": [ + "entities", + "id" + ], + "properties": { + "id": { + "description": "ID of this action", + "type": "string", + "title": "id" + }, + "entities": { + "description": "Entities associated with this action", + "type": "object", + "title": "entities", + "additionalProperties": { + "$ref": "#/definitions/Microsoft.IActivityTemplate/oneOf/1/properties/entities/items" + } + } + } + } + } + }, + { + "$ref": "#/definitions/Microsoft.ActivityTemplate" + }, + { + "$ref": "#/definitions/Microsoft.StaticActivityTemplate" + } + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Declarative", + "version": "4.11.0-rc2" + } + }, + "Microsoft.IDialog": { + "title": "Microsoft dialogs", + "description": "Components which derive from Dialog", + "$role": "interface", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Microsoft.QnAMakerDialog" + }, + { + "$ref": "#/definitions/Microsoft.AdaptiveDialog" + }, + { + "$ref": "#/definitions/Microsoft.BeginDialog" + }, + { + "$ref": "#/definitions/Microsoft.BeginSkill" + }, + { + "$ref": "#/definitions/Microsoft.BreakLoop" + }, + { + "$ref": "#/definitions/Microsoft.CancelAllDialogs" + }, + { + "$ref": "#/definitions/Microsoft.CancelDialog" + }, + { + "$ref": "#/definitions/Microsoft.ContinueConversationLater" + }, + { + "$ref": "#/definitions/Microsoft.ContinueLoop" + }, + { + "$ref": "#/definitions/Microsoft.DebugBreak" + }, + { + "$ref": "#/definitions/Microsoft.DeleteActivity" + }, + { + "$ref": "#/definitions/Microsoft.DeleteProperties" + }, + { + "$ref": "#/definitions/Microsoft.DeleteProperty" + }, + { + "$ref": "#/definitions/Microsoft.EditActions" + }, + { + "$ref": "#/definitions/Microsoft.EditArray" + }, { "$ref": "#/definitions/Microsoft.EmitEvent" }, @@ -3551,6 +4423,9 @@ { "$ref": "#/definitions/Microsoft.TelemetryTrackEvent" }, + { + "$ref": "#/definitions/Microsoft.ThrowException" + }, { "$ref": "#/definitions/Microsoft.TraceActivity" }, @@ -3584,17 +4459,17 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Declarative", - "version": "4.10.0" + "version": "4.11.0-rc2" } }, "Microsoft.IEntityRecognizer": { "$role": "interface", - "title": "Entity Recognizers", + "title": "Entity recognizers", "description": "Components which derive from EntityRecognizer.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "oneOf": [ { @@ -3675,11 +4550,11 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" } }, "Microsoft.IRecognizer": { - "title": "Microsoft Recognizer", + "title": "Microsoft recognizer", "description": "Components which derive from Recognizer class", "$role": "interface", "oneOf": [ @@ -3703,11 +4578,68 @@ }, { "$ref": "#/definitions/Microsoft.RegexRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.AgeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.ChannelMentionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.ConfirmationEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.CurrencyEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DateTimeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DimensionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.EmailEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.GuidEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.HashtagEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.IpEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.MentionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberRangeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.OrdinalEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PercentageEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PhoneNumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.RegexEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.TemperatureEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.UrlEntityRecognizer" } ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Declarative", - "version": "4.10.0" + "version": "4.11.0-rc2" } }, "Microsoft.ITextTemplate": { @@ -3724,7 +4656,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Declarative", - "version": "4.10.0" + "version": "4.11.0-rc2" } }, "Microsoft.ITrigger": { @@ -3733,7 +4665,7 @@ "description": "Components which derive from OnCondition class.", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "oneOf": [ { @@ -3789,6 +4721,9 @@ { "$ref": "#/definitions/Microsoft.OnHandoffActivity" }, + { + "$ref": "#/definitions/Microsoft.OnInstallationUpdateActivity" + }, { "$ref": "#/definitions/Microsoft.OnIntent" }, @@ -3827,7 +4762,7 @@ "description": "Components which derive from TriggerSelector class.", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "oneOf": [ { @@ -3864,7 +4799,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -3932,7 +4867,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -4065,13 +5000,16 @@ } }, "Microsoft.IpEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Ip Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "IP entity recognizer", "description": "Recognizer which recognizes internet IP patterns (like 192.1.1.1).", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -4099,12 +5037,12 @@ } }, "Microsoft.LanguagePolicy": { - "title": "Language Policy", + "title": "Language policy", "description": "This represents a policy map for locales lookups to use for language", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -4151,7 +5089,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -4187,7 +5125,7 @@ }, "traceActivity": { "$ref": "#/definitions/booleanExpression", - "title": "Send Trace Activity", + "title": "Send trace activity", "description": "If true, automatically sends a TraceActivity (view in Bot Framework Emulator)." }, "$kind": { @@ -4217,7 +5155,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.AI.Luis", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -4234,12 +5172,17 @@ }, "applicationId": { "$ref": "#/definitions/stringExpression", - "title": "LUIS Application ID", + "title": "LUIS application id", "description": "Application ID for your model from the LUIS service." }, + "version": { + "$ref": "#/definitions/stringExpression", + "title": "LUIS version", + "description": "Optional version to target. If null then predictionOptions.Slot is used." + }, "endpoint": { "$ref": "#/definitions/stringExpression", - "title": "LUIS Endpoint", + "title": "LUIS endpoint", "description": "Endpoint to use for LUIS service like https://westus.api.cognitive.microsoft.com." }, "endpointKey": { @@ -4249,7 +5192,7 @@ }, "externalEntityRecognizer": { "$kind": "Microsoft.IRecognizer", - "title": "External Entity Recognizer", + "title": "External entity recognizer", "description": "Entities recognized by this recognizer will be passed to LUIS as external entities.", "$ref": "#/definitions/Microsoft.IRecognizer" }, @@ -4303,34 +5246,29 @@ "description": "Options to control LUIS prediction behavior.", "properties": { "includeAllIntents": { - "type": "boolean", + "$ref": "#/definitions/booleanExpression", "title": "Include all intents", "description": "True for all intents, false for only top intent." }, "includeInstanceData": { - "type": "boolean", + "$ref": "#/definitions/booleanExpression", "title": "Include $instance", "description": "True to include $instance metadata in the LUIS response." }, "log": { - "type": "boolean", + "$ref": "#/definitions/booleanExpression", "title": "Log utterances", "description": "True to log utterances on LUIS service." }, "preferExternalEntities": { - "type": "boolean", - "title": "Prefer External Entities", + "$ref": "#/definitions/booleanExpression", + "title": "Prefer external entities", "description": "True to prefer external entities to those generated by LUIS models." }, "slot": { - "type": "string", + "$ref": "#/definitions/stringExpression", "title": "Slot", "description": "Slot to use for talking to LUIS service like production or staging." - }, - "version": { - "type": "string", - "title": "Version", - "description": "LUIS application version to use." } } }, @@ -4349,13 +5287,16 @@ } }, "Microsoft.MentionEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Mentions Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Mentions entity recognizer", "description": "Recognizer which recognizes @Mentions", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -4384,12 +5325,12 @@ }, "Microsoft.MostSpecificSelector": { "$role": "implements(Microsoft.ITriggerSelector)", - "title": "Most Specific Trigger Selector", + "title": "Most specific trigger selector", "description": "Select most specific true events with optional additional selector", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -4431,7 +5372,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -4477,13 +5418,16 @@ } }, "Microsoft.NumberEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Number Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Number entity recognizer", "description": "Recognizer which recognizes numbers.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -4520,7 +5464,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -4686,13 +5630,16 @@ } }, "Microsoft.NumberRangeEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "NumberRange Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Number range entity recognizer", "description": "Recognizer which recognizes ranges of numbers (Example:2 to 5).", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -4730,7 +5677,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -4825,7 +5772,7 @@ }, "allowInterruptions": { "$ref": "#/definitions/booleanExpression", - "title": "Allow Interruptions", + "title": "Allow interruptions", "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", "default": true, "examples": [ @@ -4871,7 +5818,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -4941,7 +5888,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "actions", @@ -5025,7 +5972,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "actions", @@ -5094,7 +6041,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "actions", @@ -5163,7 +6110,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "actions", @@ -5246,7 +6193,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -5321,7 +6268,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "actions", @@ -5416,7 +6363,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -5476,7 +6423,7 @@ "implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)" ], - "title": "On Continue Conversation", + "title": "On continue conversation", "description": "Actions to perform when a conversation is started up again from a ContinueConversationLater action.", "type": "object", "required": [ @@ -5485,7 +6432,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -5554,7 +6501,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -5624,7 +6571,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -5693,7 +6640,7 @@ "description": "Actions to take when there are no more actions in the current dialog.", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "type": "object", "required": [ @@ -5767,7 +6714,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -5827,12 +6774,12 @@ "implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)" ], - "title": "On Error", + "title": "On error", "description": "Action to perform when an 'Error' dialog event occurs.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "actions", @@ -5905,7 +6852,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -5974,7 +6921,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6029,6 +6976,75 @@ } } }, + "Microsoft.OnInstallationUpdateActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On InstallationUpdate activity", + "description": "Actions to perform on receipt of an activity with type 'InstallationUpdate'.", + "type": "object", + "required": [ + "actions", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.11.0-rc2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/integerExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnInstallationUpdateActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, "Microsoft.OnIntent": { "$role": [ "implements(Microsoft.ITrigger)", @@ -6043,7 +7059,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6127,7 +7143,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6196,7 +7212,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6265,7 +7281,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6334,7 +7350,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6403,7 +7419,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6463,7 +7479,7 @@ "implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)" ], - "title": "On QnAMaker Match", + "title": "On QnAMaker match", "description": "Actions to perform on when an match from QnAMaker is found.", "type": "object", "required": [ @@ -6472,7 +7488,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6537,7 +7553,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "actions", @@ -6610,7 +7626,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6679,7 +7695,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6735,13 +7751,16 @@ } }, "Microsoft.OrdinalEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Ordinal Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Ordinal entity recognizer", "description": "Recognizer which recognizes ordinals (example: first, second, 3rd).", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -6769,13 +7788,16 @@ } }, "Microsoft.PercentageEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Percentage Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Percentage entity recognizer", "description": "Recognizer which recognizes percentages.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -6803,13 +7825,16 @@ } }, "Microsoft.PhoneNumberEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Phone Number Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Phone number entity recognizer", "description": "Recognizer which recognizes phone numbers.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -6838,7 +7863,7 @@ }, "Microsoft.QnAMakerDialog": { "$role": "implements(Microsoft.IDialog)", - "title": "QnAMaker Dialog", + "title": "QnAMaker dialog", "description": "Dialog which uses QnAMAker knowledge base to answer questions.", "type": "object", "required": [ @@ -6849,7 +7874,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.AI.QnA", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6867,7 +7892,7 @@ }, "endpointKey": { "$ref": "#/definitions/stringExpression", - "title": "Endpoint Key", + "title": "Endpoint key", "description": "Endpoint key for the QnA Maker KB.", "default": "=settings.qna.endpointkey" }, @@ -6914,7 +7939,7 @@ }, "strictFilters": { "$ref": "#/definitions/arrayExpression", - "title": "Strict Filters", + "title": "Strict filters", "description": "Metadata filters to use when calling the QnA Maker KB.", "items": { "type": "object", @@ -6950,7 +7975,7 @@ }, "rankerType": { "$ref": "#/definitions/stringExpression", - "title": "Ranker Type", + "title": "Ranker type", "description": "Type of Ranker.", "oneOf": [ { @@ -6968,6 +7993,25 @@ } ] }, + "strictFiltersJoinOperator": { + "$ref": "#/definitions/stringExpression", + "title": "StrictFiltersJoinOperator", + "description": "Join operator for Strict Filters.", + "oneOf": [ + { + "title": "Join operator", + "description": "Value of Join Operator to be used as conjunction with Strict Filter values.", + "enum": [ + "AND", + "OR" + ], + "default": "AND" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", @@ -6984,7 +8028,7 @@ }, "Microsoft.QnAMakerRecognizer": { "$role": "implements(Microsoft.IRecognizer)", - "title": "QnAMaker Recognizer", + "title": "QnAMaker recognizer", "description": "Recognizer for generating QnAMatch intents from a KB.", "type": "object", "required": [ @@ -6995,7 +8039,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.AI.QnA", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7013,20 +8057,20 @@ "knowledgeBaseId": { "$ref": "#/definitions/stringExpression", "title": "KnowledgeBase Id", - "description": "KnowledgeBase Id of your QnA Maker KnowledgeBase.", - "default": "settings.qna.knowledgebaseid" + "description": "Knowledge base Id of your QnA Maker knowledge base.", + "default": "=settings.qna.knowledgebaseid" }, "endpointKey": { "$ref": "#/definitions/stringExpression", - "title": "Endpoint Key", + "title": "Endpoint key", "description": "Endpoint key for the QnA Maker KB.", - "default": "settings.qna.endpointkey" + "default": "=settings.qna.endpointkey" }, "hostname": { "$ref": "#/definitions/stringExpression", "title": "Hostname", "description": "Hostname for your QnA Maker service.", - "default": "settings.qna.hostname", + "default": "=settings.qna.hostname", "examples": [ "https://yourserver.azurewebsites.net/qnamaker" ] @@ -7039,7 +8083,7 @@ }, "strictFilters": { "$ref": "#/definitions/arrayExpression", - "title": "Strict Filters", + "title": "Strict filters", "description": "Metadata filters to use when calling the QnA Maker KB.", "items": { "type": "object", @@ -7069,7 +8113,7 @@ }, "isTest": { "$ref": "#/definitions/booleanExpression", - "title": "IsTest", + "title": "Use test environment", "description": "True, if pointing to Test environment, else false.", "examples": [ true, @@ -7077,7 +8121,7 @@ ] }, "rankerType": { - "title": "Ranker Type", + "title": "Ranker type", "description": "Type of Ranker.", "oneOf": [ { @@ -7096,9 +8140,28 @@ } ] }, + "strictFiltersJoinOperator": { + "$ref": "#/definitions/stringExpression", + "title": "StrictFiltersJoinOperator", + "description": "Join operator for Strict Filters.", + "oneOf": [ + { + "title": "Join operator", + "description": "Value of Join Operator to be used as onjuction with Strict Filter values.", + "enum": [ + "AND", + "OR" + ], + "default": "AND" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, "includeDialogNameInMetadata": { "$ref": "#/definitions/booleanExpression", - "title": "Include Dialog Name", + "title": "Include dialog name", "description": "When set to false, the dialog name will not be passed to QnAMaker. (default) is true", "default": true, "examples": [ @@ -7130,12 +8193,12 @@ }, "context": { "$ref": "#/definitions/objectExpression", - "title": "QnARequestContext", + "title": "QnA request context", "description": "Context to use for ranking." }, "qnaId": { "$ref": "#/definitions/integerExpression", - "title": "QnAId", + "title": "QnA Id", "description": "A number or expression which is the QnAId to paass to QnAMaker API." }, "$kind": { @@ -7159,7 +8222,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -7193,7 +8256,7 @@ }, "Microsoft.RecognizerSet": { "$role": "implements(Microsoft.IRecognizer)", - "title": "Recognizer Set", + "title": "Recognizer set", "description": "Creates the union of the intents and entities of the recognizers in the set.", "type": "object", "required": [ @@ -7202,7 +8265,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7241,8 +8304,11 @@ } }, "Microsoft.RegexEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Regex Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Regex entity recognizer", "description": "Recognizer which recognizes patterns of input based on regex.", "type": "object", "required": [ @@ -7252,7 +8318,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7293,7 +8359,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -7363,7 +8429,7 @@ "description": "Repeat current dialog.", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -7409,7 +8475,7 @@ }, "activityProcessed": { "$ref": "#/definitions/booleanExpression", - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the dialog that is called can process the current activity.", "default": true }, @@ -7434,7 +8500,7 @@ "description": "Replace current dialog with another dialog.", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -7490,7 +8556,7 @@ }, "activityProcessed": { "$ref": "#/definitions/booleanExpression", - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the dialog that is called can process the current activity.", "default": true }, @@ -7510,12 +8576,12 @@ }, "Microsoft.ResourceMultiLanguageGenerator": { "$role": "implements(Microsoft.ILanguageGenerator)", - "title": "Resource Multi-Language Generator", + "title": "Resource multi-language generator", "description": "MultiLanguage Generator which is bound to resource by resource Id.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -7541,7 +8607,7 @@ }, "languagePolicy": { "type": "object", - "title": "Language Policy", + "title": "Language policy", "description": "Set alternate language policy for this generator. If not set, the global language policy will be used." }, "$kind": { @@ -7565,7 +8631,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -7584,7 +8650,7 @@ "description": "Optional id for the dialog" }, "disabled": { - "$ref": "#/definitions/stringExpression", + "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", "examples": [ @@ -7622,7 +8688,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7702,7 +8768,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7760,12 +8826,12 @@ }, "Microsoft.SignOutUser": { "$role": "implements(Microsoft.IDialog)", - "title": "Sign Out User", + "title": "Sign out user", "description": "Sign a user out that was logged in previously using OAuthInput.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -7791,7 +8857,7 @@ }, "connectionName": { "$ref": "#/definitions/stringExpression", - "title": "Connection Name", + "title": "Connection name", "description": "Connection name that was used with OAuthInput to log a user in." }, "disabled": { @@ -7819,7 +8885,7 @@ }, "Microsoft.StaticActivityTemplate": { "$role": "implements(Microsoft.IActivityTemplate)", - "title": "Microsoft Static Activity Template", + "title": "Microsoft static activity template", "description": "This allows you to define a static Activity object", "type": "object", "required": [ @@ -7828,7 +8894,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7839,7 +8905,7 @@ }, "properties": { "activity": { - "$ref": "#/definitions/botframework.json/definitions/Activity", + "$ref": "#/definitions/Microsoft.IActivityTemplate/oneOf/1", "title": "Activity", "description": "A static Activity to used.", "required": [ @@ -7871,7 +8937,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7969,7 +9035,7 @@ "Microsoft.TelemetryTrackEvent": { "$role": "implements(Microsoft.IDialog)", "type": "object", - "title": "Telemetry - Track Event", + "title": "Telemetry - track event", "description": "Track a custom event using the registered Telemetry Client.", "required": [ "url", @@ -7978,7 +9044,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -8003,7 +9069,7 @@ }, "eventName": { "$ref": "#/definitions/stringExpression", - "title": "Event Name", + "title": "Event name", "description": "The name of the event to track.", "examples": [ "MyEventStarted", @@ -8033,13 +9099,16 @@ } }, "Microsoft.TemperatureEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Temperature Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Temperature recognizer", "description": "Recognizer which recognizes temperatures.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -8068,12 +9137,12 @@ }, "Microsoft.TemplateEngineLanguageGenerator": { "$role": "implements(Microsoft.ILanguageGenerator)", - "title": "Template Multi-Language Generator", + "title": "Template multi-language generator", "description": "Template Generator which allows only inline evaluation of templates.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -8115,7 +9184,7 @@ "description": "Collection information - Ask for a word or sentence.", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -8282,12 +9351,52 @@ "description": "Use LG Templates to create text", "type": "object", "required": [ - "template", + "template", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.11.0-rc2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "template": { + "title": "Template", + "description": "Language Generator template to evaluate to create the text.", + "type": "string" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.TextTemplate" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ThrowException": { + "$role": "implements(Microsoft.IDialog)", + "title": "Throw an exception", + "description": "Throw an exception. Capture this exception with OnError trigger.", + "type": "object", + "required": [ + "errorValue", "$kind" ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -8297,17 +9406,30 @@ } }, "properties": { - "template": { - "title": "Template", - "description": "Language Generator template to evaluate to create the text.", - "type": "string" + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "errorValue": { + "$ref": "#/definitions/valueExpression", + "title": "Error value", + "description": "Error value to throw." }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", "type": "string", "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", - "const": "Microsoft.TextTemplate" + "const": "Microsoft.ThrowException" }, "$designer": { "title": "Designer information", @@ -8323,7 +9445,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -8386,12 +9508,12 @@ }, "Microsoft.TrueSelector": { "$role": "implements(Microsoft.ITriggerSelector)", - "title": "True Trigger Selector", + "title": "True trigger selector", "description": "Selector for all true events", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -8420,12 +9542,12 @@ }, "Microsoft.UpdateActivity": { "$role": "implements(Microsoft.IDialog)", - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -8481,13 +9603,16 @@ } }, "Microsoft.UrlEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Confirmation Url Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Url recognizer", "description": "Recognizer which recognizes urls.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.0" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -8619,920 +9744,136 @@ "$role": "expression", "title": "Integer or expression", "description": "Integer constant or expression to evaluate.", - "oneOf": [ - { - "type": "integer", - "title": "Integer", - "description": "Integer constant.", - "default": 0, - "examples": [ - 15 - ] - }, - { - "$ref": "#/definitions/equalsExpression", - "examples": [ - "=user.age" - ] - } - ] - }, - "numberExpression": { - "$role": "expression", - "title": "Number or expression", - "description": "Number constant or expression to evaluate.", - "oneOf": [ - { - "type": "number", - "title": "Number", - "description": "Number constant.", - "default": 0, - "examples": [ - 15.5 - ] - }, - { - "$ref": "#/definitions/equalsExpression", - "examples": [ - "=dialog.quantity" - ] - } - ] - }, - "objectExpression": { - "$role": "expression", - "title": "Object or expression", - "description": "Object or expression to evaluate.", - "oneOf": [ - { - "type": "object", - "title": "Object", - "description": "Object constant." - }, - { - "$ref": "#/definitions/equalsExpression" - } - ] - }, - "role": { - "title": "$role", - "description": "Defines the role played in the dialog schema from [expression|interface|implements($kind)|extends($kind)].", - "type": "string", - "pattern": "^((expression)|(interface)|(implements\\([a-zA-Z][a-zA-Z0-9.]*\\))|(extends\\([a-zA-Z][a-zA-Z0-9.]*\\)))$" - }, - "stringExpression": { - "$role": "expression", - "title": "String or expression", - "description": "Interpolated string or expression to evaluate.", - "oneOf": [ - { - "type": "string", - "title": "String", - "description": "Interpolated string", - "pattern": "^(?!(=)).*", - "examples": [ - "Hello ${user.name}" - ] - }, - { - "$ref": "#/definitions/equalsExpression", - "examples": [ - "=concat('x','y','z')" - ] - } - ] - }, - "valueExpression": { - "$role": "expression", - "title": "Any or expression", - "description": "Any constant or expression to evaluate.", - "oneOf": [ - { - "type": "object", - "title": "Object", - "description": "Object constant." - }, - { - "type": "array", - "title": "Array", - "description": "Array constant." - }, - { - "type": "string", - "title": "String", - "description": "Interpolated string.", - "pattern": "^(?!(=)).*", - "examples": [ - "Hello ${user.name}" - ] - }, - { - "type": "boolean", - "title": "Boolean", - "description": "Boolean constant", - "examples": [ - false - ] - }, - { - "type": "number", - "title": "Number", - "description": "Number constant.", - "examples": [ - 15.5 - ] - }, - { - "$ref": "#/definitions/equalsExpression", - "examples": [ - "=..." - ] - } - ] - }, - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Core schema meta-schema", - "definitions": { - "schemaArray": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/definitions/schema" - } - }, - "nonNegativeInteger": { - "type": "integer", - "minimum": 0 - }, - "nonNegativeIntegerDefault0": { - "type": "integer", - "minimum": 0, - "default": 0 - }, - "simpleTypes": { - "enum": [ - "array", - "boolean", - "integer", - "null", - "number", - "object", - "string" - ] - }, - "stringArray": { - "type": "array", - "uniqueItems": true, - "default": [], - "items": { - "type": "string" - } - } - }, - "type": [ - "object", - "boolean" - ], - "default": true, - "properties": { - "$schema": { - "type": "string", - "format": "uri" - }, - "$ref": { - "type": "string", - "format": "uri-reference" - }, - "$comment": { - "type": "string" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "default": true, - "readOnly": { - "type": "boolean", - "default": false - }, - "writeOnly": { - "type": "boolean", - "default": false - }, - "examples": { - "type": "array", - "items": true - }, - "multipleOf": { - "type": "number", - "exclusiveMinimum": 0 - }, - "maximum": { - "type": "number" - }, - "exclusiveMaximum": { - "type": "number" - }, - "minimum": { - "type": "number" - }, - "exclusiveMinimum": { - "type": "number" - }, - "maxLength": { - "$ref": "#/definitions/schema/definitions/nonNegativeInteger" - }, - "minLength": { - "$ref": "#/definitions/schema/definitions/nonNegativeIntegerDefault0" - }, - "pattern": { - "type": "string", - "format": "regex" - }, - "additionalItems": { - "$ref": "#/definitions/schema" - }, - "items": { - "anyOf": [ - { - "$ref": "#/definitions/schema" - }, - { - "$ref": "#/definitions/schema/definitions/schemaArray" - } - ], - "default": true - }, - "maxItems": { - "$ref": "#/definitions/schema/definitions/nonNegativeInteger" - }, - "minItems": { - "$ref": "#/definitions/schema/definitions/nonNegativeIntegerDefault0" - }, - "uniqueItems": { - "type": "boolean", - "default": false - }, - "contains": { - "$ref": "#/definitions/schema" - }, - "maxProperties": { - "$ref": "#/definitions/schema/definitions/nonNegativeInteger" - }, - "minProperties": { - "$ref": "#/definitions/schema/definitions/nonNegativeIntegerDefault0" - }, - "required": { - "$ref": "#/definitions/schema/definitions/stringArray" - }, - "additionalProperties": { - "$ref": "#/definitions/schema" - }, - "definitions": { - "type": "object", - "default": {}, - "additionalProperties": { - "$ref": "#/definitions/schema" - } - }, - "properties": { - "type": "object", - "default": {}, - "additionalProperties": { - "$ref": "#/definitions/schema" - } - }, - "patternProperties": { - "type": "object", - "propertyNames": { - "format": "regex" - }, - "default": {}, - "additionalProperties": { - "$ref": "#/definitions/schema" - } - }, - "dependencies": { - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "$ref": "#/definitions/schema" - }, - { - "$ref": "#/definitions/schema/definitions/stringArray" - } - ] - } - }, - "propertyNames": { - "$ref": "#/definitions/schema" - }, - "const": true, - "enum": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": true - }, - "type": { - "anyOf": [ - { - "$ref": "#/definitions/schema/definitions/simpleTypes" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/schema/definitions/simpleTypes" - }, - "minItems": 1, - "uniqueItems": true - } + "oneOf": [ + { + "type": "integer", + "title": "Integer", + "description": "Integer constant.", + "default": 0, + "examples": [ + 15 ] }, - "format": { - "type": "string" - }, - "contentMediaType": { - "type": "string" - }, - "contentEncoding": { - "type": "string" - }, - "if": { - "$ref": "#/definitions/schema" - }, - "then": { - "$ref": "#/definitions/schema" - }, - "else": { - "$ref": "#/definitions/schema" - }, - "allOf": { - "$ref": "#/definitions/schema/definitions/schemaArray" - }, - "anyOf": { - "$ref": "#/definitions/schema/definitions/schemaArray" - }, - "oneOf": { - "$ref": "#/definitions/schema/definitions/schemaArray" - }, - "not": { - "$ref": "#/definitions/schema" + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=user.age" + ] } - } + ] }, - "botframework.json": { - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "ChannelAccount": { - "description": "Channel account information needed to route a message", - "title": "ChannelAccount", - "type": "object", - "required": [ - "id", - "name" - ], - "properties": { - "id": { - "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or\n123456)", - "type": "string", - "title": "id" - }, - "name": { - "description": "Display friendly name", - "type": "string", - "title": "name" - }, - "aadObjectId": { - "description": "This account's object ID within Azure Active Directory (AAD)", - "type": "string", - "title": "aadObjectId" - }, - "role": { - "description": "Role of the entity behind the account (Example: User, Bot, etc.). Possible values include:\n'user', 'bot'", - "type": "string", - "title": "role" - } - } - }, - "ConversationAccount": { - "description": "Channel account information for a conversation", - "title": "ConversationAccount", - "type": "object", - "required": [ - "conversationType", - "id", - "isGroup", - "name" - ], - "properties": { - "isGroup": { - "description": "Indicates whether the conversation contains more than two participants at the time the\nactivity was generated", - "type": "boolean", - "title": "isGroup" - }, - "conversationType": { - "description": "Indicates the type of the conversation in channels that distinguish between conversation types", - "type": "string", - "title": "conversationType" - }, - "id": { - "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or\n123456)", - "type": "string", - "title": "id" - }, - "name": { - "description": "Display friendly name", - "type": "string", - "title": "name" - }, - "aadObjectId": { - "description": "This account's object ID within Azure Active Directory (AAD)", - "type": "string", - "title": "aadObjectId" - }, - "role": { - "description": "Role of the entity behind the account (Example: User, Bot, etc.). Possible values include:\n'user', 'bot'", - "enum": [ - "bot", - "user" - ], - "type": "string", - "title": "role" - } - } - }, - "MessageReaction": { - "description": "Message reaction object", - "title": "MessageReaction", - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "description": "Message reaction type. Possible values include: 'like', 'plusOne'", - "type": "string", - "title": "type" - } - } - }, - "CardAction": { - "description": "A clickable action", - "title": "CardAction", - "type": "object", - "required": [ - "title", - "type", - "value" - ], - "properties": { - "type": { - "description": "The type of action implemented by this button. Possible values include: 'openUrl', 'imBack',\n'postBack', 'playAudio', 'playVideo', 'showImage', 'downloadFile', 'signin', 'call',\n'payment', 'messageBack'", - "type": "string", - "title": "type" - }, - "title": { - "description": "Text description which appears on the button", - "type": "string", - "title": "title" - }, - "image": { - "description": "Image URL which will appear on the button, next to text label", - "type": "string", - "title": "image" - }, - "text": { - "description": "Text for this action", - "type": "string", - "title": "text" - }, - "displayText": { - "description": "(Optional) text to display in the chat feed if the button is clicked", - "type": "string", - "title": "displayText" - }, - "value": { - "description": "Supplementary parameter for action. Content of this property depends on the ActionType", - "title": "value" - }, - "channelData": { - "description": "Channel-specific data associated with this action", - "title": "channelData" - } - } + "numberExpression": { + "$role": "expression", + "title": "Number or expression", + "description": "Number constant or expression to evaluate.", + "oneOf": [ + { + "type": "number", + "title": "Number", + "description": "Number constant.", + "default": 0, + "examples": [ + 15.5 + ] }, - "SuggestedActions": { - "description": "SuggestedActions that can be performed", - "title": "SuggestedActions", + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=dialog.quantity" + ] + } + ] + }, + "objectExpression": { + "$role": "expression", + "title": "Object or expression", + "description": "Object or expression to evaluate.", + "oneOf": [ + { "type": "object", - "required": [ - "actions", - "to" - ], - "properties": { - "to": { - "description": "Ids of the recipients that the actions should be shown to. These Ids are relative to the\nchannelId and a subset of all recipients of the activity", - "type": "array", - "title": "to", - "items": { - "title": "Id", - "description": "Id of recipient.", - "type": "string" - } - }, - "actions": { - "description": "Actions that can be shown to the user", - "type": "array", - "title": "actions", - "items": { - "$ref": "#/definitions/botframework.json/definitions/CardAction" - } - } - } + "title": "Object", + "description": "Object constant." }, - "Attachment": { - "description": "An attachment within an activity", - "title": "Attachment", - "type": "object", - "required": [ - "contentType" - ], - "properties": { - "contentType": { - "description": "mimetype/Contenttype for the file", - "type": "string", - "title": "contentType" - }, - "contentUrl": { - "description": "Content Url", - "type": "string", - "title": "contentUrl" - }, - "content": { - "type": "object", - "description": "Embedded content", - "title": "content" - }, - "name": { - "description": "(OPTIONAL) The name of the attachment", - "type": "string", - "title": "name" - }, - "thumbnailUrl": { - "description": "(OPTIONAL) Thumbnail associated with attachment", - "type": "string", - "title": "thumbnailUrl" - } - } + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "role": { + "title": "$role", + "description": "Defines the role played in the dialog schema from [expression|interface|implements($kind)|extends($kind)].", + "type": "string", + "pattern": "^((expression)|(interface)|(implements\\([a-zA-Z][a-zA-Z0-9.]*\\))|(extends\\([a-zA-Z][a-zA-Z0-9.]*\\)))$" + }, + "stringExpression": { + "$role": "expression", + "title": "String or expression", + "description": "Interpolated string or expression to evaluate.", + "oneOf": [ + { + "type": "string", + "title": "String", + "description": "Interpolated string", + "pattern": "^(?!(=)).*", + "examples": [ + "Hello ${user.name}" + ] }, - "Entity": { - "description": "Metadata object pertaining to an activity", - "title": "Entity", + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=concat('x','y','z')" + ] + } + ] + }, + "valueExpression": { + "$role": "expression", + "title": "Any or expression", + "description": "Any constant or expression to evaluate.", + "oneOf": [ + { "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "description": "Type of this entity (RFC 3987 IRI)", - "type": "string", - "title": "type" - } - } + "title": "Object", + "description": "Object constant." }, - "ConversationReference": { - "description": "An object relating to a particular point in a conversation", - "title": "ConversationReference", - "type": "object", - "required": [ - "bot", - "channelId", - "conversation", - "serviceUrl" - ], - "properties": { - "activityId": { - "description": "(Optional) ID of the activity to refer to", - "type": "string", - "title": "activityId" - }, - "user": { - "description": "(Optional) User participating in this conversation", - "$ref": "#/definitions/botframework.json/definitions/ChannelAccount", - "title": "user" - }, - "bot": { - "$ref": "#/definitions/botframework.json/definitions/ChannelAccount", - "description": "Bot participating in this conversation", - "title": "bot" - }, - "conversation": { - "$ref": "#/definitions/botframework.json/definitions/ConversationAccount", - "description": "Conversation reference", - "title": "conversation" - }, - "channelId": { - "description": "Channel ID", - "type": "string", - "title": "channelId" - }, - "serviceUrl": { - "description": "Service endpoint where operations concerning the referenced conversation may be performed", - "type": "string", - "title": "serviceUrl" - } - } + { + "type": "array", + "title": "Array", + "description": "Array constant." }, - "TextHighlight": { - "description": "Refers to a substring of content within another field", - "title": "TextHighlight", - "type": "object", - "required": [ - "occurrence", - "text" - ], - "properties": { - "text": { - "description": "Defines the snippet of text to highlight", - "type": "string", - "title": "text" - }, - "occurrence": { - "description": "Occurrence of the text field within the referenced text, if multiple exist.", - "type": "number", - "title": "occurrence" - } - } + { + "type": "string", + "title": "String", + "description": "Interpolated string.", + "pattern": "^(?!(=)).*", + "examples": [ + "Hello ${user.name}" + ] }, - "SemanticAction": { - "description": "Represents a reference to a programmatic action", - "title": "SemanticAction", - "type": "object", - "required": [ - "entities", - "id" - ], - "properties": { - "id": { - "description": "ID of this action", - "type": "string", - "title": "id" - }, - "entities": { - "description": "Entities associated with this action", - "type": "object", - "title": "entities", - "additionalProperties": { - "$ref": "#/definitions/botframework.json/definitions/Entity" - } - } - } + { + "type": "boolean", + "title": "Boolean", + "description": "Boolean constant", + "examples": [ + false + ] }, - "Activity": { - "description": "An Activity is the basic communication type for the Bot Framework 3.0 protocol.", - "title": "Activity", - "type": "object", - "properties": { - "type": { - "description": "Contains the activity type. Possible values include: 'message', 'contactRelationUpdate',\n'conversationUpdate', 'typing', 'endOfConversation', 'event', 'invoke', 'deleteUserData',\n'messageUpdate', 'messageDelete', 'installationUpdate', 'messageReaction', 'suggestion',\n'trace', 'handoff'", - "type": "string", - "title": "type" - }, - "id": { - "description": "Contains an ID that uniquely identifies the activity on the channel.", - "type": "string", - "title": "id" - }, - "timestamp": { - "description": "Contains the date and time that the message was sent, in UTC, expressed in ISO-8601 format.", - "type": "string", - "format": "date-time", - "title": "timestamp" - }, - "localTimestamp": { - "description": "Contains the date and time that the message was sent, in local time, expressed in ISO-8601\nformat.\nFor example, 2016-09-23T13:07:49.4714686-07:00.", - "type": "string", - "format": "date-time", - "title": "localTimestamp" - }, - "localTimezone": { - "description": "Contains the name of the timezone in which the message, in local time, expressed in IANA Time\nZone database format.\nFor example, America/Los_Angeles.", - "type": "string", - "title": "localTimezone" - }, - "serviceUrl": { - "description": "Contains the URL that specifies the channel's service endpoint. Set by the channel.", - "type": "string", - "title": "serviceUrl" - }, - "channelId": { - "description": "Contains an ID that uniquely identifies the channel. Set by the channel.", - "type": "string", - "title": "channelId" - }, - "from": { - "$ref": "#/definitions/botframework.json/definitions/ChannelAccount", - "description": "Identifies the sender of the message.", - "title": "from" - }, - "conversation": { - "$ref": "#/definitions/botframework.json/definitions/ConversationAccount", - "description": "Identifies the conversation to which the activity belongs.", - "title": "conversation" - }, - "recipient": { - "$ref": "#/definitions/botframework.json/definitions/ChannelAccount", - "description": "Identifies the recipient of the message.", - "title": "recipient" - }, - "textFormat": { - "description": "Format of text fields Default:markdown. Possible values include: 'markdown', 'plain', 'xml'", - "type": "string", - "title": "textFormat" - }, - "attachmentLayout": { - "description": "The layout hint for multiple attachments. Default: list. Possible values include: 'list',\n'carousel'", - "type": "string", - "title": "attachmentLayout" - }, - "membersAdded": { - "description": "The collection of members added to the conversation.", - "type": "array", - "title": "membersAdded", - "items": { - "$ref": "#/definitions/botframework.json/definitions/ChannelAccount" - } - }, - "membersRemoved": { - "description": "The collection of members removed from the conversation.", - "type": "array", - "title": "membersRemoved", - "items": { - "$ref": "#/definitions/botframework.json/definitions/ChannelAccount" - } - }, - "reactionsAdded": { - "description": "The collection of reactions added to the conversation.", - "type": "array", - "title": "reactionsAdded", - "items": { - "$ref": "#/definitions/botframework.json/definitions/MessageReaction" - } - }, - "reactionsRemoved": { - "description": "The collection of reactions removed from the conversation.", - "type": "array", - "title": "reactionsRemoved", - "items": { - "$ref": "#/definitions/botframework.json/definitions/MessageReaction" - } - }, - "topicName": { - "description": "The updated topic name of the conversation.", - "type": "string", - "title": "topicName" - }, - "historyDisclosed": { - "description": "Indicates whether the prior history of the channel is disclosed.", - "type": "boolean", - "title": "historyDisclosed" - }, - "locale": { - "description": "A locale name for the contents of the text field.\nThe locale name is a combination of an ISO 639 two- or three-letter culture code associated\nwith a language\nand an ISO 3166 two-letter subculture code associated with a country or region.\nThe locale name can also correspond to a valid BCP-47 language tag.", - "type": "string", - "title": "locale" - }, - "text": { - "description": "The text content of the message.", - "type": "string", - "title": "text" - }, - "speak": { - "description": "The text to speak.", - "type": "string", - "title": "speak" - }, - "inputHint": { - "description": "Indicates whether your bot is accepting,\nexpecting, or ignoring user input after the message is delivered to the client. Possible\nvalues include: 'acceptingInput', 'ignoringInput', 'expectingInput'", - "type": "string", - "title": "inputHint" - }, - "summary": { - "description": "The text to display if the channel cannot render cards.", - "type": "string", - "title": "summary" - }, - "suggestedActions": { - "description": "The suggested actions for the activity.", - "$ref": "#/definitions/botframework.json/definitions/SuggestedActions", - "title": "suggestedActions" - }, - "attachments": { - "description": "Attachments", - "type": "array", - "title": "attachments", - "items": { - "$ref": "#/definitions/botframework.json/definitions/Attachment" - } - }, - "entities": { - "description": "Represents the entities that were mentioned in the message.", - "type": "array", - "title": "entities", - "items": { - "$ref": "#/definitions/botframework.json/definitions/Entity" - } - }, - "channelData": { - "description": "Contains channel-specific content.", - "title": "channelData" - }, - "action": { - "description": "Indicates whether the recipient of a contactRelationUpdate was added or removed from the\nsender's contact list.", - "type": "string", - "title": "action" - }, - "replyToId": { - "description": "Contains the ID of the message to which this message is a reply.", - "type": "string", - "title": "replyToId" - }, - "label": { - "description": "A descriptive label for the activity.", - "type": "string", - "title": "label" - }, - "valueType": { - "description": "The type of the activity's value object.", - "type": "string", - "title": "valueType" - }, - "value": { - "description": "A value that is associated with the activity.", - "title": "value" - }, - "name": { - "description": "The name of the operation associated with an invoke or event activity.", - "type": "string", - "title": "name" - }, - "relatesTo": { - "description": "A reference to another conversation or activity.", - "$ref": "#/definitions/botframework.json/definitions/ConversationReference", - "title": "relatesTo" - }, - "code": { - "description": "The a code for endOfConversation activities that indicates why the conversation ended.\nPossible values include: 'unknown', 'completedSuccessfully', 'userCancelled', 'botTimedOut',\n'botIssuedInvalidMessage', 'channelFailed'", - "type": "string", - "title": "code" - }, - "expiration": { - "description": "The time at which the activity should be considered to be \"expired\" and should not be\npresented to the recipient.", - "type": "string", - "format": "date-time", - "title": "expiration" - }, - "importance": { - "description": "The importance of the activity. Possible values include: 'low', 'normal', 'high'", - "type": "string", - "title": "importance" - }, - "deliveryMode": { - "description": "A delivery hint to signal to the recipient alternate delivery paths for the activity.\nThe default delivery mode is \"default\". Possible values include: 'normal', 'notification'", - "type": "string", - "title": "deliveryMode" - }, - "listenFor": { - "description": "List of phrases and references that speech and language priming systems should listen for", - "type": "array", - "title": "listenFor", - "items": { - "type": "string", - "title": "Phrase", - "description": "Phrase to listen for." - } - }, - "textHighlights": { - "description": "The collection of text fragments to highlight when the activity contains a ReplyToId value.", - "type": "array", - "title": "textHighlights", - "items": { - "$ref": "#/definitions/botframework.json/definitions/TextHighlight" - } - }, - "semanticAction": { - "$ref": "#/definitions/botframework.json/definitions/SemanticAction", - "description": "An optional programmatic action accompanying this request", - "title": "semanticAction" - } - } + { + "type": "number", + "title": "Number", + "description": "Number constant.", + "examples": [ + 15.5 + ] + }, + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=..." + ] } - } + ] } } -} \ No newline at end of file +} diff --git a/Composer/packages/server/schemas/sdk.sv.schema b/Composer/packages/server/schemas/sdk.sv.schema index a6329e9099..d3a7a116f0 100644 --- a/Composer/packages/server/schemas/sdk.sv.schema +++ b/Composer/packages/server/schemas/sdk.sv.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Skicka en aktivitet", + "title": "Uppdatera en aktivitet", "description": "Svara med en aktivitet.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.tr.schema b/Composer/packages/server/schemas/sdk.tr.schema index 9d7e2e0538..668cafdf1f 100644 --- a/Composer/packages/server/schemas/sdk.tr.schema +++ b/Composer/packages/server/schemas/sdk.tr.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.uischema b/Composer/packages/server/schemas/sdk.uischema index 813d3e8e23..3a96f1fd4a 100644 --- a/Composer/packages/server/schemas/sdk.uischema +++ b/Composer/packages/server/schemas/sdk.uischema @@ -23,6 +23,112 @@ } } }, + "Microsoft.Ask": { + "form": { + "helpLink": "https://aka.ms/bfc-send-activity", + "label": "Send a response to ask a question", + "order": [ + "activity", + "*" + ], + "subtitle": "Ask Activity" + } + }, + "Microsoft.AttachmentInput": { + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for a file or an attachment", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Attachment Input" + } + }, + "Microsoft.ChoiceInput": { + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt with multi-choice", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Choice Input" + } + }, + "Microsoft.ConfirmInput": { + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for confirmation", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Confirm Input" + } + }, + "Microsoft.DateTimeInput": { + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for a date or a time", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Date Time Input" + } + }, + "Microsoft.NumberInput": { + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for a number", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Number Input" + } + }, + "Microsoft.OAuthInput": { + "form": { + "helpLink": "https://aka.ms/bfc-using-oauth", + "label": "OAuth login", + "order": [ + "connectionName", + "*" + ], + "subtitle": "OAuth Input" + } + }, + "Microsoft.TextInput": { + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for text", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Text Input" + } + }, "Microsoft.BeginDialog": { "form": { "helpLink": "https://aka.ms/bfc-understanding-dialogs", @@ -252,7 +358,7 @@ }, "Microsoft.LogAction": { "form": { - "helpLink": "https://aka.ms/bfc-debugging-bots", + "helpLink": "https://aka.ms/composer-telemetry", "label": "Log to console", "subtitle": "Log Action" } @@ -359,117 +465,11 @@ }, "Microsoft.TraceActivity": { "form": { - "helpLink": "https://aka.ms/bfc-debugging-bots", + "helpLink": "https://aka.ms/composer-telemetry", "label": "Emit a trace event", "subtitle": "Trace Activity" } }, - "Microsoft.Ask": { - "form": { - "helpLink": "https://aka.ms/bfc-send-activity", - "label": "Send a response to ask a question", - "order": [ - "activity", - "*" - ], - "subtitle": "Ask Activity" - } - }, - "Microsoft.AttachmentInput": { - "form": { - "helpLink": "https://aka.ms/bfc-ask-for-user-input", - "label": "Prompt for a file or an attachment", - "properties": { - "property": { - "intellisenseScopes": [ - "variable-scopes" - ] - } - }, - "subtitle": "Attachment Input" - } - }, - "Microsoft.ChoiceInput": { - "form": { - "helpLink": "https://aka.ms/bfc-ask-for-user-input", - "label": "Prompt with multi-choice", - "properties": { - "property": { - "intellisenseScopes": [ - "variable-scopes" - ] - } - }, - "subtitle": "Choice Input" - } - }, - "Microsoft.ConfirmInput": { - "form": { - "helpLink": "https://aka.ms/bfc-ask-for-user-input", - "label": "Prompt for confirmation", - "properties": { - "property": { - "intellisenseScopes": [ - "variable-scopes" - ] - } - }, - "subtitle": "Confirm Input" - } - }, - "Microsoft.DateTimeInput": { - "form": { - "helpLink": "https://aka.ms/bfc-ask-for-user-input", - "label": "Prompt for a date or a time", - "properties": { - "property": { - "intellisenseScopes": [ - "variable-scopes" - ] - } - }, - "subtitle": "Date Time Input" - } - }, - "Microsoft.NumberInput": { - "form": { - "helpLink": "https://aka.ms/bfc-ask-for-user-input", - "label": "Prompt for a number", - "properties": { - "property": { - "intellisenseScopes": [ - "variable-scopes" - ] - } - }, - "subtitle": "Number Input" - } - }, - "Microsoft.OAuthInput": { - "form": { - "helpLink": "https://aka.ms/bfc-using-oauth", - "label": "OAuth login", - "order": [ - "connectionName", - "*" - ], - "subtitle": "OAuth Input" - } - }, - "Microsoft.TextInput": { - "form": { - "helpLink": "https://aka.ms/bfc-ask-for-user-input", - "label": "Prompt for text", - "properties": { - "property": { - "intellisenseScopes": [ - "variable-scopes" - ] - } - }, - "subtitle": "Text Input" - } - }, "Microsoft.RegexRecognizer": { "form": { "hidden": [ @@ -529,6 +529,17 @@ "subtitle": "Cancel dialog event" } }, + "Microsoft.OnChooseIntent": { + "form": { + "hidden": [ + "actions" + ], + "order": [ + "condition", + "*" + ] + } + }, "Microsoft.OnCondition": { "form": { "hidden": [ diff --git a/Composer/packages/server/schemas/sdk.zh-Hans.schema b/Composer/packages/server/schemas/sdk.zh-Hans.schema index 95d6f18aed..004a06a5f8 100644 --- a/Composer/packages/server/schemas/sdk.zh-Hans.schema +++ b/Composer/packages/server/schemas/sdk.zh-Hans.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/schemas/sdk.zh-Hant.schema b/Composer/packages/server/schemas/sdk.zh-Hant.schema index ba3e0a963c..2afcef3f5f 100644 --- a/Composer/packages/server/schemas/sdk.zh-Hant.schema +++ b/Composer/packages/server/schemas/sdk.zh-Hant.schema @@ -4348,7 +4348,7 @@ } }, "Microsoft.UpdateActivity": { - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "patternProperties": { "^\\$": { diff --git a/Composer/packages/server/src/controllers/__tests__/auth.test.ts b/Composer/packages/server/src/controllers/__tests__/auth.test.ts new file mode 100644 index 0000000000..95fa51ded1 --- /dev/null +++ b/Composer/packages/server/src/controllers/__tests__/auth.test.ts @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { AuthController } from '../auth'; + +const mockGetAccessToken = jest.fn().mockResolvedValue('accessToken'); +jest.mock('../../services/auth/auth', () => ({ + authService: { + getAccessToken: (params) => mockGetAccessToken(params), + }, +})); + +let mockIsElectron = true; +jest.mock('../../utility/isElectron', () => ({ + get isElectron() { + return mockIsElectron; + }, +})); + +describe('auth controller', () => { + const chainedRes = { + json: jest.fn(), + send: jest.fn(), + }; + const mockRes: any = { + status: jest.fn().mockReturnValue(chainedRes), + }; + + beforeEach(() => { + mockRes.status.mockClear(); + chainedRes.json.mockClear(); + chainedRes.send.mockClear(); + }); + + it('should return a 400 if no targetResource is passed in Electron env', async () => { + mockIsElectron = true; + const mockReq: any = { + query: { + targetResource: undefined, + }, + }; + await AuthController.getAccessToken(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(400); + expect(chainedRes.send).toHaveBeenCalledWith( + 'Must pass a "targetResource" parameter to perform authentication in Electron environment.' + ); + }); + + it('should return a 400 if no clientId is passed in web env', async () => { + mockIsElectron = false; + const mockReq: any = { + query: { + clientId: undefined, + }, + }; + await AuthController.getAccessToken(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(400); + expect(chainedRes.send).toHaveBeenCalledWith( + 'Must pass a "clientId" parameter to perform authentication in a Web environment.' + ); + }); + + it('should return an access token from the auth service', async () => { + mockIsElectron = true; + const mockReq: any = { + query: { + targetResource: 'https://graph.microsoft.com/', + }, + }; + await AuthController.getAccessToken(mockReq, mockRes); + + expect(mockGetAccessToken).toHaveBeenCalledWith({ + clientId: undefined, + targetResource: 'https://graph.microsoft.com/', + scopes: [], + }); + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(chainedRes.json).toHaveBeenCalledWith({ accessToken: 'accessToken' }); + }); +}); diff --git a/Composer/packages/server/src/controllers/__tests__/eject.test.ts b/Composer/packages/server/src/controllers/__tests__/eject.test.ts index eb621a17d9..d7ffe661d8 100644 --- a/Composer/packages/server/src/controllers/__tests__/eject.test.ts +++ b/Composer/packages/server/src/controllers/__tests__/eject.test.ts @@ -51,6 +51,7 @@ beforeAll(async () => { name: 'C#', startCommand: 'dotnet run --project azurewebapp', path: './', + identifyManifest: jest.fn(), eject: jest.fn(), build: jest.fn(), run: jest.fn(), diff --git a/Composer/packages/server/src/controllers/auth.ts b/Composer/packages/server/src/controllers/auth.ts new file mode 100644 index 0000000000..23634573a0 --- /dev/null +++ b/Composer/packages/server/src/controllers/auth.ts @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { Request, Response } from 'express'; + +import { authService } from '../services/auth/auth'; +import { isElectron } from '../utility/isElectron'; + +type GetAccessTokenRequest = Request & { + query: { + // used for OAuth flow (web) + clientId?: string; + // used for OAuth flow (web) + scopes: string; + // used for native flow (electron) + targetResource?: string; + }; +}; + +async function getAccessToken(req: GetAccessTokenRequest, res: Response) { + const { clientId, targetResource, scopes = '[]' } = req.query; + if (isElectron && !targetResource) { + return res + .status(400) + .send('Must pass a "targetResource" parameter to perform authentication in Electron environment.'); + } + if (!isElectron && !clientId) { + return res.status(400).send('Must pass a "clientId" parameter to perform authentication in a Web environment.'); + } + const parsedScopes: string[] = JSON.parse(scopes); + + const accessToken = await authService.getAccessToken({ clientId, targetResource, scopes: parsedScopes }); + + res.status(200).json({ + accessToken, + }); +} + +export const AuthController = { + getAccessToken, +}; diff --git a/Composer/packages/server/src/controllers/formDialog.ts b/Composer/packages/server/src/controllers/formDialog.ts index 5d793f0137..f7b8856404 100644 --- a/Composer/packages/server/src/controllers/formDialog.ts +++ b/Composer/packages/server/src/controllers/formDialog.ts @@ -85,9 +85,19 @@ const deleteDialog = async (req: Request, res: Response) => { const currentProject = await BotProjectService.getProjectById(projectId, user); if (currentProject !== undefined) { - await currentProject.deleteFormDialog(dialogId); - const updatedProject = await BotProjectService.getProjectById(projectId, user); - res.status(200).json({ id: projectId, ...updatedProject.getProject() }); + try { + const rootDialogId = currentProject.rootDialogId; + if (rootDialogId === dialogId) { + throw new Error('root dialog is not allowed to delete'); + } + await currentProject.deleteFormDialog(dialogId); + const updatedProject = await BotProjectService.getProjectById(projectId, user); + res.status(200).json({ id: projectId, ...updatedProject.getProject() }); + } catch (e) { + res.status(400).json({ + message: e.message, + }); + } } else { res.status(404).json({ message: `Could not delete form dialog. Project ${projectId} not found.`, diff --git a/Composer/packages/server/src/controllers/project.ts b/Composer/packages/server/src/controllers/project.ts index 33c1410db3..6fdae39dd1 100644 --- a/Composer/packages/server/src/controllers/project.ts +++ b/Composer/packages/server/src/controllers/project.ts @@ -6,6 +6,7 @@ import * as fs from 'fs'; import { Request, Response } from 'express'; import { Archiver } from 'archiver'; import { ExtensionContext } from '@bfc/extension'; +import { SchemaMerger } from '@microsoft/bf-dialog/lib/library/schemaMerger'; import log from '../logger'; import { BotProjectService } from '../services/project'; @@ -54,6 +55,33 @@ async function createProject(req: Request, res: Response) { await AssetService.manager.copyBoilerplate(currentProject.dataDir, currentProject.fileStorage); if (currentProject !== undefined) { + if (currentProject.settings?.runtime?.customRuntime === true) { + const runtime = ExtensionContext.getRuntimeByProject(currentProject); + const runtimePath = currentProject.settings.runtime.path; + + if (!fs.existsSync(runtimePath)) { + await runtime.eject(currentProject, currentProject.fileStorage); + } + + // install all dependencies and build the app + await runtime.build(runtimePath, currentProject); + + const manifestFile = runtime.identifyManifest(runtimePath); + + // run the merge command to merge all package dependencies from the template to the bot project + const realMerge = new SchemaMerger( + [manifestFile], + Path.join(currentProject.dataDir, 'schemas/sdk'), + Path.join(currentProject.dataDir, 'dialogs/imported'), + false, + false, + console.log, + console.warn, + console.error + ); + + await realMerge.merge(); + } await currentProject.updateBotInfo(name, description, preserveRoot); if (schemaUrl) { await currentProject.saveSchemaToProject(schemaUrl, locationRef.path); @@ -367,7 +395,7 @@ async function checkBoilerplateVersion(req: Request, res: Response) { const currentProject = await BotProjectService.getProjectById(projectId, user); if (currentProject !== undefined) { - const latestVersion = AssetService.manager.getBoilerplateCurrentVersion(); + const latestVersion = await AssetService.manager.getBoilerplateCurrentVersion(); const currentVersion = await AssetService.manager.getBoilerplateVersionFromProject(currentProject); const updateRequired = (latestVersion && currentVersion && latestVersion > currentVersion) || // versions are present in both locations, latest is newer diff --git a/Composer/packages/server/src/locales/en-US.json b/Composer/packages/server/src/locales/en-US.json index b02415fd27..71b2714dbd 100644 --- a/Composer/packages/server/src/locales/en-US.json +++ b/Composer/packages/server/src/locales/en-US.json @@ -368,6 +368,9 @@ "boolean_value_98d39ea1": { "message": "Boolean value." }, + "bot_7926b66d": { + "message": "Bot" + }, "bot_asks_5e9f0202": { "message": "Bot Asks" }, @@ -1268,6 +1271,9 @@ "if_this_problem_persists_please_file_an_issue_on_6fbc8e2b": { "message": "If this problem persists, please file an issue on" }, + "if_turned_on_then_externally_stored_templates_will_3f487651": { + "message": "If turned on then externally stored templates will be selectable in the new bot flow template list" + }, "if_you_already_have_a_luis_account_provide_the_inf_bede07a4": { "message": "If you already have a LUIS account, provide the information below. If you do not have an account yet, create a (free) account first." }, @@ -2078,6 +2084,9 @@ "reloading_49d2f661": { "message": "Reloading" }, + "remote_templates_a23c242d": { + "message": "Remote templates" + }, "remove_f47dc62a": { "message": "Remove" }, @@ -2330,12 +2339,12 @@ "spaces_and_special_characters_are_not_allowed_20d47684": { "message": "Spaces and special characters are not allowed." }, - "spaces_and_special_characters_are_not_allowed_use__2a61c454": { - "message": "Spaces and special characters are not allowed. Use letters, numbers, -, or _ and don''t use number at the beginning." - }, "spaces_and_special_characters_are_not_allowed_use__48acec3c": { "message": "Spaces and special characters are not allowed. Use letters, numbers, -, or _." }, + "spaces_and_special_characters_are_not_allowed_use__d24a8636": { + "message": "Spaces and special characters are not allowed. Use letters, numbers, -, or _, and begin the name with a letter." + }, "specify_a_name_and_description_for_your_new_dialog_86eb3130": { "message": "Specify a name and description for your new dialog." }, @@ -2489,6 +2498,9 @@ "this_dialog_has_no_trigger_yet_d1f1d173": { "message": "This dialog has no trigger yet." }, + "this_is_a_botname_notification_c8a391c7": { + "message": "This is a { botName } notification" + }, "this_is_a_required_field_acb9837e": { "message": "This is a required field." }, @@ -2630,6 +2642,9 @@ "uninstall_8730233": { "message": "Uninstall" }, + "unknown_47a3b725": { + "message": "Unknown" + }, "unknown_intent_44b962ba": { "message": "Unknown intent" }, diff --git a/Composer/packages/server/src/middleware/__tests__/csrfProtection.test.ts b/Composer/packages/server/src/middleware/__tests__/csrfProtection.test.ts new file mode 100644 index 0000000000..b40d60fa67 --- /dev/null +++ b/Composer/packages/server/src/middleware/__tests__/csrfProtection.test.ts @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { csrfProtection } from '../csrfProtection'; + +jest.mock('../../services/auth/auth', () => ({ + authService: { + csrfToken: 'csrfToken', + }, +})); + +describe('CSRF protection middleware', () => { + const chainedRes = { + send: jest.fn(), + }; + const mockRes: any = { + status: jest.fn().mockReturnValue(chainedRes), + }; + const mockNext = jest.fn(); + + beforeEach(() => { + chainedRes.send.mockClear(); + mockRes.status.mockClear(); + mockNext.mockClear(); + }); + + it('should fail if the CSRF token is missing from the request', () => { + const mockReq: any = { + get: jest.fn().mockReturnValue(undefined), + }; + csrfProtection(mockReq, mockRes, mockNext); + + expect(mockRes.status).toHaveBeenCalledWith(403); + expect(chainedRes.send).toHaveBeenCalledWith({ message: 'CSRF token required.' }); + expect(mockNext).not.toHaveBeenCalled(); + }); + + it('should fail if the CSRF token provided does not match the generated token', () => { + const mockReq: any = { + get: jest.fn().mockReturnValue('nonMatchingToken'), + }; + csrfProtection(mockReq, mockRes, mockNext); + + expect(mockRes.status).toHaveBeenCalledWith(403); + expect(chainedRes.send).toHaveBeenCalledWith({ message: `CSRF token did not match server's token.` }); + expect(mockNext).not.toHaveBeenCalled(); + }); + + it('should call the next req handler if the CSRF token matches', () => { + const mockReq: any = { + get: jest.fn().mockReturnValue('csrfToken'), + }; + csrfProtection(mockReq, mockRes, mockNext); + + expect(mockRes.status).not.toHaveBeenCalled(); + expect(chainedRes.send).not.toHaveBeenCalled(); + expect(mockNext).toHaveBeenCalled(); + }); +}); diff --git a/Composer/packages/server/src/middleware/csrfProtection.ts b/Composer/packages/server/src/middleware/csrfProtection.ts new file mode 100644 index 0000000000..f01585541c --- /dev/null +++ b/Composer/packages/server/src/middleware/csrfProtection.ts @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { NextFunction, Request, Response } from 'express'; + +import { authService } from '../services/auth/auth'; + +/** + * Middleware that verifies if the server-generated CSRF token is sent with the incoming request via header. + */ +export const csrfProtection = (req: Request, res: Response, next: NextFunction) => { + // the CSRF token will only be generated in the production environment + if (authService.csrfToken) { + const csrfToken = req.get('X-CSRF-Token'); + if (!csrfToken) { + // do not complete the request, the client did not provide the server-generated token + return res.status(403).send({ message: 'CSRF token required.' }); + } + if (csrfToken !== authService.csrfToken) { + // do not complete the request, the client provided a token that did not match the server-generated token + return res.status(403).send({ message: `CSRF token did not match server's token.` }); + } + } + + // check passed, continue to next handler + next(); +}; diff --git a/Composer/packages/server/src/models/asset/assetManager.ts b/Composer/packages/server/src/models/asset/assetManager.ts index 76a614fb20..a5fa019605 100644 --- a/Composer/packages/server/src/models/asset/assetManager.ts +++ b/Composer/packages/server/src/models/asset/assetManager.ts @@ -3,9 +3,13 @@ import fs from 'fs'; import path from 'path'; +import { exec } from 'child_process'; +import { promisify } from 'util'; import find from 'lodash/find'; -import { UserIdentity, ExtensionContext } from '@bfc/extension'; +import { UserIdentity, ExtensionContext, BotTemplate, FileExtensions } from '@bfc/extension'; +import { mkdirs, readFile } from 'fs-extra'; +import rimraf from 'rimraf'; import log from '../../logger'; import { LocalDiskStorage } from '../storage/localDiskStorage'; @@ -16,6 +20,9 @@ import StorageService from '../../services/storage'; import { IFileStorage } from '../storage/interface'; import { BotProject } from '../bot/botProject'; +const execAsync = promisify(exec); +const removeDirAndFiles = promisify(rimraf); + export class AssetManager { public templateStorage: LocalDiskStorage; private _botProjectFileTemplate; @@ -52,13 +59,61 @@ export class AssetManager { return ref; } + private async getRemoteTemplate(template: BotTemplate, destinationPath: string) { + // install package + if (template.package) { + const { stderr: initErr } = await execAsync( + `dotnet new -i ${template.package.packageName}::${template.package.packageVersion}` + ); + if (initErr) { + throw new Error(initErr); + } + const { stderr: initErr2 } = await execAsync(`dotnet new ${template.id}`, { + cwd: destinationPath, + }); + if (initErr2) { + throw new Error(initErr2); + } + } else { + throw new Error('selected template has no local or external address'); + } + } + private async copyDataFilesTo(templateId: string, dstDir: string, dstStorage: IFileStorage, locale?: string) { const template = find(ExtensionContext.extensions.botTemplates, { id: templateId }); - if (template === undefined || template.path === undefined) { + if (template === undefined || (template.path === undefined && template.package === undefined)) { throw new Error(`no such template with id ${templateId}`); } - // copy Composer data files - await copyDir(template.path, this.templateStorage, dstDir, dstStorage); + + let templateSrcPath = template.path; + const isHostedTemplate = !templateSrcPath; + if (isHostedTemplate) { + // create empty temp directory on server for holding externally hosted template src + templateSrcPath = path.resolve(__dirname, '../../../temp'); + if (fs.existsSync(templateSrcPath)) { + await removeDirAndFiles(templateSrcPath); + } + await mkdirs(templateSrcPath, (err) => { + if (err) { + throw new Error('Error creating temp directory for external template storage'); + } + }); + await this.getRemoteTemplate(template, templateSrcPath); + } + + if (templateSrcPath) { + // copy Composer data files + await copyDir(templateSrcPath, this.templateStorage, dstDir, dstStorage); + + if (isHostedTemplate) { + try { + await removeDirAndFiles(templateSrcPath); + } catch (err) { + throw new Error('Issue deleting temp generated file for external template assets'); + } + } + } + // if we have a locale override, copy those files over too if (locale != null) { const localePath = path.join(__dirname, '..', '..', '..', 'schemas', `sdk.${locale}.schema`); @@ -76,7 +131,7 @@ export class AssetManager { public async copyBoilerplate(dstDir: string, dstStorage: IFileStorage) { for (const boilerplate of ExtensionContext.extensions.baseTemplates) { const boilerplatePath = boilerplate.path; - if (await this.templateStorage.exists(boilerplatePath)) { + if (boilerplatePath && (await this.templateStorage.exists(boilerplatePath))) { await copyDir(boilerplatePath, this.templateStorage, dstDir, dstStorage); } } @@ -104,24 +159,27 @@ export class AssetManager { // return the current version of the boilerplate content, if one exists so specified // this is based off of the first boilerplate template added to the app. - public getBoilerplateCurrentVersion(): string | undefined { + public async getBoilerplateCurrentVersion(): Promise { if (!ExtensionContext.extensions.baseTemplates.length) { return undefined; } const boilerplate = ExtensionContext.extensions.baseTemplates[0]; - const location = Path.join(boilerplate.path, 'scripts', 'package.json'); - try { - if (fs.existsSync(location)) { - const raw = fs.readFileSync(location, 'utf8'); - const json = JSON.parse(raw); - if (json && json.version) { - return json.version; - } else { - return undefined; + if (boilerplate.path) { + const location = Path.join(boilerplate.path, 'scripts', 'package.json'); + try { + if (fs.existsSync(location)) { + const raw = await readFile(location, 'utf8'); + + const json = JSON.parse(raw); + if (json && json.version) { + return json.version; + } else { + return undefined; + } } + } catch (err) { + return undefined; } - } catch (err) { - return undefined; } } @@ -131,15 +189,17 @@ export class AssetManager { } const boilerplate = ExtensionContext.extensions.botTemplates[0]; - const location = Path.join(boilerplate.path, `${boilerplate.id}.botproj`); - try { - if (fs.existsSync(location)) { - const raw = fs.readFileSync(location, 'utf8'); - const json = JSON.parse(raw); - return json; + if (boilerplate.path) { + const location = Path.join(boilerplate.path, `${boilerplate.id + FileExtensions.BotProject}`); + try { + if (fs.existsSync(location)) { + const raw = fs.readFileSync(location, 'utf8'); + const json = JSON.parse(raw); + return json; + } + } catch (err) { + return ''; } - } catch (err) { - return ''; } } } diff --git a/Composer/packages/server/src/models/bot/__tests__/botProject.test.ts b/Composer/packages/server/src/models/bot/__tests__/botProject.test.ts index baeebe3c52..e7fcfa384a 100644 --- a/Composer/packages/server/src/models/bot/__tests__/botProject.test.ts +++ b/Composer/packages/server/src/models/bot/__tests__/botProject.test.ts @@ -356,7 +356,7 @@ describe('dialog schema operations', () => { describe('should validate the file name when create a new one', () => { const error = new Error( - "Spaces and special characters are not allowed. Use letters, numbers, -, or _ and don't use number at the beginning." + 'Spaces and special characters are not allowed. Use letters, numbers, -, or _, and begin the name with a letter.' ); const emptyError = new Error('The file name can not be empty'); diff --git a/Composer/packages/server/src/models/bot/__tests__/botStructure.test.ts b/Composer/packages/server/src/models/bot/__tests__/botStructure.test.ts index 4a703382de..2f777111d3 100644 --- a/Composer/packages/server/src/models/bot/__tests__/botStructure.test.ts +++ b/Composer/packages/server/src/models/bot/__tests__/botStructure.test.ts @@ -9,105 +9,111 @@ const defaultLocale = 'en-us'; describe('Bot structure file path', () => { // cross-train config it('should get entry cross-train config file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'cross-train.config.json'); + const targetPath = defaultFilePath(botName, defaultLocale, 'cross-train.config.json', {}); expect(targetPath).toEqual('settings/cross-train.config.json'); }); // recognizer it('should get entry recognizer file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'test.lu.qna.dialog'); + const targetPath = defaultFilePath(botName, defaultLocale, 'test.lu.qna.dialog', {}); expect(targetPath).toEqual('dialogs/test/recognizers/test.lu.qna.dialog'); }); // entry dialog it('should get entry .dialog file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'mybot.dialog'); + const targetPath = defaultFilePath(botName, defaultLocale, 'mybot.dialog', {}); expect(targetPath).toEqual('mybot.dialog'); }); // common.lg it('should get common.lg file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'common.en-us.lg'); + const targetPath = defaultFilePath(botName, defaultLocale, 'common.en-us.lg', {}); expect(targetPath).toEqual('language-generation/en-us/common.en-us.lg'); }); // common.zh-cn.lg it('should get common..lg file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'common.zh-cn.lg'); + const targetPath = defaultFilePath(botName, defaultLocale, 'common.zh-cn.lg', {}); expect(targetPath).toEqual('language-generation/zh-cn/common.zh-cn.lg'); }); // skill manifest it('should get exported skill manifest file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'EchoBot-4-2-1-preview-1-manifest.json'); + const targetPath = defaultFilePath(botName, defaultLocale, 'EchoBot-4-2-1-preview-1-manifest.json', {}); expect(targetPath).toEqual('manifests/EchoBot-4-2-1-preview-1-manifest.json'); }); // mybot.en-us.lg it('should get entry dialog.lg file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'mybot.en-us.lg'); + const targetPath = defaultFilePath(botName, defaultLocale, 'mybot.en-us.lg', {}); expect(targetPath).toEqual('language-generation/en-us/mybot.en-us.lg'); }); // mybot.zh-cn.lg it('should get entry dialog.lg file path', async () => { - const targetPath = defaultFilePath('my-bot', defaultLocale, 'my-bot.zh-cn.lg'); + const targetPath = defaultFilePath('my-bot', defaultLocale, 'my-bot.zh-cn.lg', {}); expect(targetPath).toEqual('language-generation/zh-cn/my-bot.zh-cn.lg'); }); // entry dialog's lu it('should get entry dialog.lu file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'mybot.en-us.lu'); + const targetPath = defaultFilePath(botName, defaultLocale, 'mybot.en-us.lu', {}); expect(targetPath).toEqual('language-understanding/en-us/mybot.en-us.lu'); }); // child dialog's entry it('should get child dialog file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'greeting.dialog'); + const targetPath = defaultFilePath(botName, defaultLocale, 'greeting.dialog', {}); expect(targetPath).toEqual('dialogs/greeting/greeting.dialog'); }); // entry dialog's qna it('should get entry dialog.qna file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'mybot.en-us.qna'); + const targetPath = defaultFilePath(botName, defaultLocale, 'mybot.en-us.qna', {}); expect(targetPath).toEqual('knowledge-base/en-us/mybot.en-us.qna'); }); // entry dialog's source qna it('should get entry dialog.source.qna file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'mybot.myimport1.source.qna'); + const targetPath = defaultFilePath(botName, defaultLocale, 'mybot.myimport1.source.qna', {}); expect(targetPath).toEqual('knowledge-base/source/myimport1.source.qna'); }); // shared source qna it('should get shared .source.qna file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'myimport1.source.qna'); + const targetPath = defaultFilePath(botName, defaultLocale, 'myimport1.source.qna', {}); expect(targetPath).toEqual('knowledge-base/source/myimport1.source.qna'); }); // child dialog's lg it('should get child dialog-lg file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'greeting.en-us.lg'); + const targetPath = defaultFilePath(botName, defaultLocale, 'greeting.en-us.lg', {}); expect(targetPath).toEqual('dialogs/greeting/language-generation/en-us/greeting.en-us.lg'); }); // child dialog's lu it('should get child dialog-lu file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'greeting.en-us.lu'); + const targetPath = defaultFilePath(botName, defaultLocale, 'greeting.en-us.lu', {}); expect(targetPath).toEqual('dialogs/greeting/language-understanding/en-us/greeting.en-us.lu'); }); // child dialog's qna it('should get child dialog-qna file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'greeting.en-us.qna'); + const targetPath = defaultFilePath(botName, defaultLocale, 'greeting.en-us.qna', {}); expect(targetPath).toEqual('dialogs/greeting/knowledge-base/en-us/greeting.en-us.qna'); }); // child dialog's source qna it('should get child dialog.source.qna file path', async () => { - const targetPath = defaultFilePath(botName, defaultLocale, 'greeting.myimport1.source.qna'); + const targetPath = defaultFilePath(botName, defaultLocale, 'greeting.myimport1.source.qna', {}); expect(targetPath).toEqual('dialogs/greeting/knowledge-base/source/myimport1.source.qna'); }); + + // customized endpoint + it('should get child dialog.source.qna file path', async () => { + const targetPath = defaultFilePath(botName, defaultLocale, 'myimport1.qna', { endpoint: 'dialogs/Welcome' }); + expect(targetPath).toEqual('dialogs/Welcome/knowledge-base/en-us/myimport1.en-us.qna'); + }); }); describe('Parse file name', () => { diff --git a/Composer/packages/server/src/models/bot/botProject.ts b/Composer/packages/server/src/models/bot/botProject.ts index d21120113d..2b71dabecb 100644 --- a/Composer/packages/server/src/models/bot/botProject.ts +++ b/Composer/packages/server/src/models/bot/botProject.ts @@ -4,6 +4,7 @@ import { promisify } from 'util'; import fs from 'fs'; +import has from 'lodash/has'; import axios from 'axios'; import { autofixReferInDialog } from '@bfc/indexers'; import { @@ -193,6 +194,7 @@ export class BotProject implements IBotProject { skills: this.skills, diagnostics: this.diagnostics, settings: this.settings, + filesWithoutRecognizers: Array.from(this.files.values()).filter(({ name }) => !isRecognizer(name)), }; }; @@ -203,6 +205,15 @@ export class BotProject implements IBotProject { public getEnvSettings = async (obfuscate: boolean) => { const settings = await this.settingManager.get(obfuscate); + // Resolve relative path for custom runtime if the path is relative + if (settings?.runtime?.customRuntime && settings.runtime.path && !Path.isAbsolute(settings.runtime.path)) { + const absolutePath = Path.resolve(this.dir, 'settings', settings.runtime.path); + if (fs.existsSync(absolutePath)) { + settings.runtime.path = absolutePath; + await this.updateEnvSettings(settings); + } + } + // fix old bot have no language settings if (!settings?.defaultLanguage) { settings.defaultLanguage = defaultLanguage; @@ -436,7 +447,14 @@ export class BotProject implements IBotProject { this._validateFileContent(name, content); const botName = this.name; const defaultLocale = this.settings?.defaultLanguage || defaultLanguage; - const relativePath = defaultFilePath(botName, defaultLocale, filename, this.rootDialogId); + + // find created file belong to which dialog, all resources should be writed to / + const dialogId = name.split('.')[0]; + const dialogFile = this.files.get(`${dialogId}.dialog`); + const endpoint = dialogFile ? Path.dirname(dialogFile.relativePath) : ''; + const rootDialogId = this.rootDialogId; + + const relativePath = defaultFilePath(botName, defaultLocale, filename, { endpoint, rootDialogId }); const file = this.files.get(filename); if (file) { throw new Error(`${filename} dialog already exist`); @@ -535,10 +553,10 @@ export class BotProject implements IBotProject { public async generateDialog(name: string, templateDirs?: string[]) { const defaultLocale = this.settings?.defaultLanguage || defaultLanguage; - const relativePath = defaultFilePath(this.name, defaultLocale, `${name}${FileExtensions.FormDialogSchema}`); + const relativePath = defaultFilePath(this.name, defaultLocale, `${name}${FileExtensions.FormDialogSchema}`, {}); const schemaPath = Path.resolve(this.dir, relativePath); - const dialogPath = defaultFilePath(this.name, defaultLocale, `${name}${FileExtensions.Dialog}`); + const dialogPath = defaultFilePath(this.name, defaultLocale, `${name}${FileExtensions.Dialog}`, {}); const outDir = Path.dirname(Path.resolve(this.dir, dialogPath)); const feedback = (type: FeedbackType, message: string): void => { @@ -585,7 +603,7 @@ export class BotProject implements IBotProject { public async deleteFormDialog(dialogId: string) { const defaultLocale = this.settings?.defaultLanguage || defaultLanguage; - const dialogPath = defaultFilePath(this.name, defaultLocale, `${dialogId}${FileExtensions.Dialog}`); + const dialogPath = defaultFilePath(this.name, defaultLocale, `${dialogId}${FileExtensions.Dialog}`, {}); const dirToDelete = Path.dirname(Path.resolve(this.dir, dialogPath)); // I check that the path is longer 3 to avoid deleting a drive and all its contents. @@ -797,11 +815,22 @@ export class BotProject implements IBotProject { // migration: create qna files for old bots private _createQnAFilesForOldBot = async (files: Map) => { + // flowing migration scripts depends on files; + this.files = new Map([...files]); const dialogFiles: FileInfo[] = []; const qnaFiles: FileInfo[] = []; files.forEach((file) => { if (file.name.endsWith('.dialog')) { - dialogFiles.push(file); + try { + // filter form dialog generated file. + const dialogJson = JSON.parse(file.content); + const isFormDialog = has(dialogJson, 'schema'); + if (!isFormDialog) { + dialogFiles.push(file); + } + } catch (_e) { + // ignore + } } if (file.name.endsWith('.qna')) { qnaFiles.push(file); diff --git a/Composer/packages/server/src/models/bot/botStructure.ts b/Composer/packages/server/src/models/bot/botStructure.ts index c4dce265bf..6ce972eade 100644 --- a/Composer/packages/server/src/models/bot/botStructure.ts +++ b/Composer/packages/server/src/models/bot/botStructure.ts @@ -81,11 +81,15 @@ export const defaultFilePath = ( botName: string, defaultLocale: string, filename: string, - rootDialogId = '' + options: { + endpoint?: string; // / + rootDialogId?: string; + } ): string => { const BOTNAME = botName.toLowerCase(); const CommonFileId = 'common'; + const { endpoint = '', rootDialogId = '' } = options; const { fileId, locale, fileType, dialogId } = parseFileName(filename, defaultLocale); const LOCALE = locale; @@ -93,7 +97,11 @@ export const defaultFilePath = ( if (isRecognizer(filename)) { const dialogId = filename.split('.')[0]; const isRoot = filename.startsWith(botName) || (rootDialogId && filename.startsWith(rootDialogId)); - if (isRoot) { + if (endpoint) { + return templateInterpolate(Path.join(endpoint, BotStructureTemplate.recognizer), { + RECOGNIZERNAME: filename, + }); + } else if (isRoot) { return templateInterpolate(BotStructureTemplate.recognizer, { RECOGNIZERNAME: filename, }); @@ -137,19 +145,46 @@ export const defaultFilePath = ( const isRootFile = BOTNAME === DIALOGNAME.toLowerCase(); if (fileType === FileExtensions.SourceQnA) { + if (endpoint) { + return templateInterpolate(Path.join(endpoint, BotStructureTemplate.sourceQnA), { + FILENAME: fileId, + DIALOGNAME, + }); + } const TemplatePath = isRootFile || !dialogId ? BotStructureTemplate.sourceQnA : BotStructureTemplate.dialogs.sourceQnA; return templateInterpolate(TemplatePath, { FILENAME: fileId, DIALOGNAME, }); + } - return templateInterpolate(BotStructureTemplate.skillManifests, { - MANIFESTFILENAME: filename, + let TemplatePath = ''; + + if (endpoint) { + switch (fileType) { + case FileExtensions.Dialog: + TemplatePath = BotStructureTemplate.entry; + break; + case FileExtensions.Lg: + TemplatePath = BotStructureTemplate.lg; + break; + case FileExtensions.Lu: + TemplatePath = BotStructureTemplate.lu; + break; + case FileExtensions.Qna: + TemplatePath = BotStructureTemplate.qna; + break; + case FileExtensions.DialogSchema: + TemplatePath = BotStructureTemplate.dialogSchema; + } + return templateInterpolate(Path.join(endpoint, TemplatePath), { + BOTNAME: fileId, + DIALOGNAME, + LOCALE, }); } - let TemplatePath = ''; if (fileType === FileExtensions.Dialog) { TemplatePath = isRootFile ? BotStructureTemplate.entry : BotStructureTemplate.dialogs.entry; } else if (fileType === FileExtensions.Lg) { diff --git a/Composer/packages/server/src/models/bot/builder.ts b/Composer/packages/server/src/models/bot/builder.ts index 475913db1e..a79a49f347 100644 --- a/Composer/packages/server/src/models/bot/builder.ts +++ b/Composer/packages/server/src/models/bot/builder.ts @@ -5,13 +5,12 @@ import { FileInfo, IConfig } from '@bfc/shared'; import { ComposerReservoirSampler } from '@microsoft/bf-dispatcher/lib/mathematics/sampler/ComposerReservoirSampler'; import { ComposerBootstrapSampler } from '@microsoft/bf-dispatcher/lib/mathematics/sampler/ComposerBootstrapSampler'; +import { luImportResolverGenerator, getLUFiles, getQnAFiles } from '@bfc/shared/lib/luBuildResolver'; import { Path } from '../../utility/path'; import { IFileStorage } from '../storage/interface'; import log from '../../logger'; -import { luImportResolverGenerator, getLUFiles, getQnAFiles } from './luResolver'; - const crossTrainer = require('@microsoft/bf-lu/lib/parser/cross-train/crossTrainer.js'); const luBuild = require('@microsoft/bf-lu/lib/parser/lubuild/builder.js'); const qnaBuild = require('@microsoft/bf-lu/lib/parser/qnabuild/builder.js'); @@ -217,6 +216,7 @@ export class Builder { suffix: config.suffix, keptVersionCount: 10, isStaging: false, + region: config.region, }); await this.luBuilder.writeDialogAssets(buildResult, { diff --git a/Composer/packages/server/src/models/settings/defaultSettingManager.ts b/Composer/packages/server/src/models/settings/defaultSettingManager.ts index b9011d6cb9..cf6ca3de24 100644 --- a/Composer/packages/server/src/models/settings/defaultSettingManager.ts +++ b/Composer/packages/server/src/models/settings/defaultSettingManager.ts @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import set from 'lodash/set'; import { DialogSetting, SensitiveProperties } from '@bfc/shared'; import { UserIdentity } from '@bfc/extension'; +import has from 'lodash/has'; +import get from 'lodash/get'; +import set from 'lodash/set'; import { Path } from '../../utility/path'; import log from '../../logger'; @@ -11,6 +13,8 @@ import log from '../../logger'; import { FileSettingManager } from './fileSettingManager'; const debug = log.extend('default-settings-manager'); +const newSettingsValuePath = ['downsampling', 'luis.endpoint', 'luis.authoringEndpoint', 'skillConfiguration']; + export class DefaultSettingManager extends FileSettingManager { constructor(basePath: string, user?: UserIdentity) { super(basePath, user); @@ -90,18 +94,19 @@ export class DefaultSettingManager extends FileSettingManager { public async get(obfuscate = false): Promise { const result = await super.get(obfuscate); - //add downsampling property for old bot - if (!result.downsampling) { - result.downsampling = this.createDefaultSettings().downsampling; - } - //add luis endpoint for old bot - if (!result.luis.endpoint && result.luis.endpoint !== '') { - result.luis.endpoint = this.createDefaultSettings().luis.endpoint; - } - //add luis authoring endpoint for old bot - if (!result.luis.authoringEndpoint && result.luis.authoringEndpoint !== '') { - result.luis.authoringEndpoint = this.createDefaultSettings().luis.authoringEndpoint; + const defaultValue = this.createDefaultSettings(); + let updateFile = false; + newSettingsValuePath.forEach((jsonPath: string) => { + if (!has(result, jsonPath)) { + set(result, jsonPath, get(defaultValue, jsonPath)); + updateFile = true; + } + }); + + if (updateFile) { + this.set(result); } + return result; } diff --git a/Composer/packages/server/src/router/api.ts b/Composer/packages/server/src/router/api.ts index 7c5e384fc4..758db184fe 100644 --- a/Composer/packages/server/src/router/api.ts +++ b/Composer/packages/server/src/router/api.ts @@ -13,6 +13,8 @@ import { EjectController } from '../controllers/eject'; import { FormDialogController } from '../controllers/formDialog'; import * as ExtensionsController from '../controllers/extensions'; import { FeatureFlagController } from '../controllers/featureFlags'; +import { AuthController } from '../controllers/auth'; +import { csrfProtection } from '../middleware/csrfProtection'; import { UtilitiesController } from './../controllers/utilities'; @@ -84,6 +86,9 @@ router.get('/extensions/:id/:bundleId', ExtensionsController.getBundleForView); // proxy route for extensions (allows extension client code to make fetch calls using the Composer server as a proxy -- avoids browser blocking request due to CORS) router.post('/extensions/proxy/:url', ExtensionsController.performExtensionFetch); +// authentication from client +router.get('/auth/getAccessToken', csrfProtection, AuthController.getAccessToken); + //FeatureFlags router.get('/featureFlags', FeatureFlagController.getFeatureFlags); router.post('/featureFlags', FeatureFlagController.updateFeatureFlags); diff --git a/Composer/packages/server/src/server.ts b/Composer/packages/server/src/server.ts index d9eeb146ab..f71e91d744 100644 --- a/Composer/packages/server/src/server.ts +++ b/Composer/packages/server/src/server.ts @@ -26,11 +26,16 @@ import { BASEURL } from './constants'; import { attachLSPServer } from './utility/attachLSP'; import log from './logger'; import { setEnvDefault } from './utility/setEnvDefault'; +import { ElectronContext, setElectronContext } from './utility/electronContext'; +import { authService } from './services/auth/auth'; // eslint-disable-next-line @typescript-eslint/no-var-requires const session = require('express-session'); -export async function start(): Promise { +export async function start(electronContext?: ElectronContext): Promise { + if (electronContext) { + setElectronContext(electronContext); + } const clientDirectory = path.resolve(require.resolve('@bfc/client'), '..'); const app: Express = express(); app.set('view engine', 'ejs'); @@ -121,7 +126,10 @@ export async function start(): Promise { }); app.get('*', (req, res) => { - res.render(path.resolve(clientDirectory, 'index.ejs'), { __nonce__: req.__nonce__ }); + res.render(path.resolve(clientDirectory, 'index.ejs'), { + __nonce__: req.__nonce__, + __csrf__: authService.csrfToken, + }); }); const preferredPort = process.env.PORT || 5000; diff --git a/Composer/packages/server/src/services/__tests__/auth.test.ts b/Composer/packages/server/src/services/__tests__/auth.test.ts new file mode 100644 index 0000000000..ebaed6616b --- /dev/null +++ b/Composer/packages/server/src/services/__tests__/auth.test.ts @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +describe('auth service', () => { + const nodeEnvBackup = process.env.NODE_ENV; + + afterAll(() => { + Object.assign(process.env, { ...process.env, NODE_ENV: nodeEnvBackup }); + }); + + beforeEach(() => { + jest.resetModules(); + }); + + it('should generate a CSRF token in the production environment', () => { + Object.assign(process.env, { ...process.env, NODE_ENV: 'production' }); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { authService } = require('../auth/auth'); + + // eslint-disable-next-line no-underscore-dangle + expect((authService as any)._csrfToken).toBeTruthy(); + }); + + it('should not generate a CSRF token in the development environment', () => { + Object.assign(process.env, { ...process.env, NODE_ENV: 'development' }); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { authService } = require('../auth/auth'); + + // eslint-disable-next-line no-underscore-dangle + expect((authService as any)._csrfToken).not.toBeTruthy(); + }); + + it('should get an access token', async () => { + const mockProvider = { + getAccessToken: jest.fn().mockResolvedValue('accessToken'), + }; + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { authService } = require('../auth/auth'); + (authService as any).provider = mockProvider; + const token = await authService.getAccessToken({}); + + expect(token).toBe('accessToken'); + }); +}); diff --git a/Composer/packages/server/src/services/__tests__/electronAuthProvider.test.ts b/Composer/packages/server/src/services/__tests__/electronAuthProvider.test.ts new file mode 100644 index 0000000000..c797396f94 --- /dev/null +++ b/Composer/packages/server/src/services/__tests__/electronAuthProvider.test.ts @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { ElectronAuthProvider } from '../auth/electronAuthProvider'; + +let mockIsLinux = false; +jest.mock('../../utility/platform', () => ({ + isLinux: () => mockIsLinux, +})); + +describe('Electron auth provider', () => { + beforeEach(() => { + mockIsLinux = false; + }); + + it('should not return an access token on Linux', async () => { + mockIsLinux = true; + const provider = new ElectronAuthProvider({}); + // eslint-disable-next-line no-underscore-dangle + (provider as any)._electronContext = { + getAccessToken: jest.fn().mockResolvedValue('accessToken'), + }; + const token = await provider.getAccessToken({} as any); + + expect(token).toBe(''); + }); + + it('should return a fresh access token on Win / Mac', async () => { + const provider = new ElectronAuthProvider({}); + const mockElectronContext = { + getAccessToken: jest.fn().mockResolvedValue({ + accessToken: 'accessToken', + }), + }; + // eslint-disable-next-line no-underscore-dangle + (provider as any)._electronContext = mockElectronContext; + const token = await provider.getAccessToken({ targetResource: 'https://graph.microsoft.com/' }); + + expect(mockElectronContext.getAccessToken).toHaveBeenCalledWith({ targetResource: 'https://graph.microsoft.com/' }); + expect(token).toBe('accessToken'); + }); + + it('should return a cached token', async () => { + const targetResource = 'https://graph.microsoft.com/'; + const provider = new ElectronAuthProvider({}); + const mockElectronContext = { + getAccessToken: jest.fn().mockResolvedValue({ + accessToken: 'accessToken', + }), + }; + // eslint-disable-next-line no-underscore-dangle + (provider as any)._electronContext = mockElectronContext; + (provider as any).tokenCache = { + [targetResource]: { + accessToken: 'cachedToken', + expiryTime: Date.now() + 1000 * 60 * 30, // expires 30 minutes from now + }, + }; + const token = await provider.getAccessToken({ targetResource }); + + expect(mockElectronContext.getAccessToken).not.toHaveBeenCalled(); + expect(token).toBe('cachedToken'); + }); +}); diff --git a/Composer/packages/server/src/services/auth/auth.ts b/Composer/packages/server/src/services/auth/auth.ts new file mode 100644 index 0000000000..ec3c1ef40a --- /dev/null +++ b/Composer/packages/server/src/services/auth/auth.ts @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { v4 as uuid } from 'uuid'; +import { AuthParameters } from '@botframework-composer/types'; + +import { isElectron } from '../../utility/isElectron'; +import logger from '../../logger'; + +import { AuthProvider } from './authProvider'; +import { ElectronAuthProvider } from './electronAuthProvider'; +import { WebAuthProvider } from './webAuthProvider'; + +const log = logger.extend('auth-service'); + +class AuthService { + private provider: AuthProvider; + private _csrfToken: string; + + constructor() { + if (isElectron) { + // desktop scenario + this.provider = new ElectronAuthProvider({}); + log('Initialized in Electron context.'); + } else { + // hosted / web scenario + this.provider = new WebAuthProvider({}); + log('Initialized in Web context.'); + } + // generate anti-csrf token in production environment + if (process.env.NODE_ENV === 'production') { + log('Production environment detected. Generating CSRF token.'); + this._csrfToken = uuid(); + } else { + this._csrfToken = ''; + } + } + + async getAccessToken(params: AuthParameters): Promise { + return this.provider.getAccessToken(params); + } + + get csrfToken(): string { + return this._csrfToken; + } +} + +export const authService = new AuthService(); diff --git a/Composer/packages/server/src/services/auth/authProvider.ts b/Composer/packages/server/src/services/auth/authProvider.ts new file mode 100644 index 0000000000..fb1e417d3f --- /dev/null +++ b/Composer/packages/server/src/services/auth/authProvider.ts @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { AuthParameters } from '@botframework-composer/types'; + +export type AuthConfig = {}; + +export abstract class AuthProvider { + constructor(protected config: AuthConfig) {} + + abstract async getAccessToken(params: AuthParameters): Promise; +} diff --git a/Composer/packages/server/src/services/auth/electronAuthProvider.ts b/Composer/packages/server/src/services/auth/electronAuthProvider.ts new file mode 100644 index 0000000000..936a85dcc6 --- /dev/null +++ b/Composer/packages/server/src/services/auth/electronAuthProvider.ts @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { AuthParameters } from '@botframework-composer/types'; + +import logger from '../../logger'; +import { ElectronContext, useElectronContext } from '../../utility/electronContext'; +import { isLinux } from '../../utility/platform'; + +import { AuthConfig, AuthProvider } from './authProvider'; + +const log = logger.extend('electron-auth-provider'); + +type TokenRecord = { + expiryTime: number; + accessToken: string; +}; + +type TokenCache = Record; + +export class ElectronAuthProvider extends AuthProvider { + private _electronContext: ElectronContext | undefined; + private tokenRefreshFactor = 0.75; // refresh the token after 75% of the expiry time has passed + private tokenCache: TokenCache; + + constructor(config: AuthConfig) { + super(config); + this.tokenCache = {}; + log('Initialized.'); + } + + async getAccessToken(params: AuthParameters): Promise { + const { getAccessToken } = this.electronContext; + const { targetResource = '' } = params; + + if (isLinux()) { + log('Auth login flow is currently unsupported in Linux.'); + return ''; + } + + log('Getting access token.'); + + // try to get a cached token + const cachedToken = this.getCachedToken(params); + if (!!cachedToken && Date.now() <= cachedToken.expiryTime.valueOf()) { + log('Returning cached token.'); + return cachedToken.accessToken; + } + + try { + // otherwise get a fresh token + log('Did not find cached token. Getting fresh token.'); + const { accessToken, acquiredAt, expiryTime } = await getAccessToken({ targetResource }); + this.cacheTokens({ accessToken, acquiredAt, expiryTime }, params); + + return accessToken; + } catch (e) { + log('Error while trying to get access token: %O', e); + return ''; + } + } + + private get electronContext() { + if (!this._electronContext) { + this._electronContext = useElectronContext(); + } + return this._electronContext; + } + + private getCachedToken(params: AuthParameters): TokenRecord | undefined { + const tokenHash = this.getTokenHash(params); + const cachedToken = this.tokenCache[tokenHash]; + return cachedToken; + } + + private cacheTokens( + tokenInfo: { accessToken: string; acquiredAt: number; expiryTime: number }, + params: AuthParameters + ): void { + const { accessToken, acquiredAt, expiryTime } = tokenInfo; + const tokenHash = this.getTokenHash(params); + const expiresIn = expiryTime - acquiredAt; + + log('Caching token...'); + + // cache token + this.tokenCache[tokenHash] = { + accessToken, + expiryTime, + }; + + log('Token cached.'); + + // setup timer to refresh token + const timeUntilRefresh = this.tokenRefreshFactor * expiresIn; + setTimeout(() => this.refreshAccessToken(params), timeUntilRefresh); + } + + private async refreshAccessToken(params: AuthParameters) { + log('Refreshing access token...'); + const { getAccessToken } = this.electronContext; + const { targetResource = '' } = params; + const cachedToken = this.tokenCache[this.getTokenHash(params)]; + if (cachedToken) { + const { accessToken, acquiredAt, expiryTime } = await getAccessToken({ targetResource }); + this.cacheTokens({ accessToken, acquiredAt, expiryTime }, params); + log('Access token refreshed.'); + } + } + + private getTokenHash(params: AuthParameters): string { + return params.targetResource || ''; + } +} diff --git a/Composer/packages/server/src/services/auth/webAuthProvider.ts b/Composer/packages/server/src/services/auth/webAuthProvider.ts new file mode 100644 index 0000000000..7413571024 --- /dev/null +++ b/Composer/packages/server/src/services/auth/webAuthProvider.ts @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { AuthParameters } from '@botframework-composer/types'; + +import { AuthConfig, AuthProvider } from './authProvider'; + +export class WebAuthProvider extends AuthProvider { + constructor(config: AuthConfig) { + super(config); + } + + // TODO (toanzian / ccastro): implement + async getAccessToken(params: AuthParameters): Promise { + throw new Error( + 'WebAuthProvider has not been implemented yet. Implicit auth flow currently only works in Electron.' + ); + } +} diff --git a/Composer/packages/server/src/services/featureFlags.ts b/Composer/packages/server/src/services/featureFlags.ts index 392d738976..acb108d746 100644 --- a/Composer/packages/server/src/services/featureFlags.ts +++ b/Composer/packages/server/src/services/featureFlags.ts @@ -21,69 +21,64 @@ export class FeatureFlagService { private static updateFeatureFlags = () => { const currentFeatureFlagKeys = Object.keys(FeatureFlagService.currentFeatureFlagMap); const defaultFeatureFlagKeys = Object.keys(FeatureFlagService.defaultFeatureFlags); - const keysToAdd: string[] = []; - const keysToUpdateHidden: string[] = []; - - defaultFeatureFlagKeys.forEach((key: string) => { - if (!currentFeatureFlagKeys.includes(key)) { - keysToAdd.push(key); - } else if ( - currentFeatureFlagKeys.includes(key) && - FeatureFlagService.currentFeatureFlagMap[key as FeatureFlagKey].isHidden !== - FeatureFlagService.defaultFeatureFlags[key as FeatureFlagKey].isHidden - ) { - keysToUpdateHidden.push(key); - } - }); - - const keysToRemove = currentFeatureFlagKeys.filter((key: string) => { - if (!defaultFeatureFlagKeys.includes(key)) { - return key; - } - }); - keysToAdd.forEach((key: string) => { - FeatureFlagService.currentFeatureFlagMap[key] = FeatureFlagService.defaultFeatureFlags[key]; + // apply defaults to the loaded feature flags + Object.keys(FeatureFlagService.currentFeatureFlagMap).forEach((key) => { + FeatureFlagService.currentFeatureFlagMap[key] = { + ...FeatureFlagService.defaultFeatureFlags[key], + ...FeatureFlagService.currentFeatureFlagMap[key], + }; }); - keysToRemove.forEach((key: string) => { - delete FeatureFlagService.currentFeatureFlagMap[key]; - }); - - keysToUpdateHidden.forEach((key: string) => { - FeatureFlagService.currentFeatureFlagMap[key as FeatureFlagKey].isHidden = - FeatureFlagService.defaultFeatureFlags[key as FeatureFlagKey].isHidden; - }); - - const hiddenFeatureFlagUpdated = FeatureFlagService.updateHiddenFeatureFlags(); - - if ( - keysToRemove?.length > 0 || - keysToAdd?.length > 0 || - keysToUpdateHidden?.length > 0 || - hiddenFeatureFlagUpdated - ) { - Store.set(storeKey, FeatureFlagService.currentFeatureFlagMap); + let saveNeeded = false; + + // add any new keys defined in the defaults that aren't in current + defaultFeatureFlagKeys + .filter((key: string) => !currentFeatureFlagKeys.includes(key)) + .forEach((key: string) => { + FeatureFlagService.currentFeatureFlagMap[key] = FeatureFlagService.defaultFeatureFlags[key]; + saveNeeded = true; + }); + + // remove any keys no longer in default that are in current + currentFeatureFlagKeys + .filter((key: string) => !defaultFeatureFlagKeys.includes(key)) + .forEach((key: string) => { + delete FeatureFlagService.currentFeatureFlagMap[key]; + saveNeeded = true; + }); + + // set any hidden feature flags from the process + // process should override value for hidden features flags + Object.keys(FeatureFlagService.currentFeatureFlagMap) + .filter((key) => FeatureFlagService.currentFeatureFlagMap[key as FeatureFlagKey].isHidden) + .forEach((key) => { + const enabled = FeatureFlagService.currentFeatureFlagMap[key as FeatureFlagKey].enabled; + const processEnvEnabled = key in process.env; + + if (enabled !== processEnvEnabled) { + FeatureFlagService.currentFeatureFlagMap[key as FeatureFlagKey].enabled = processEnvEnabled; + saveNeeded = true; + } + }); + + if (saveNeeded) { + FeatureFlagService.saveFeatureFlags(); } }; - private static updateHiddenFeatureFlags = (): boolean => { - const hiddenFeatureFlagKeys = Object.keys(FeatureFlagService.currentFeatureFlagMap).filter((key: string) => { - if (FeatureFlagService.currentFeatureFlagMap[key as FeatureFlagKey].isHidden) { - return key; - } - }); + private static saveFeatureFlags() { + const saveFeatureFlagMap: FeatureFlagMap = {} as FeatureFlagMap; - let result = false; - hiddenFeatureFlagKeys.forEach((key: string) => { - if (process.env[key] && process.env[key] !== FeatureFlagService.currentFeatureFlagMap[key]) { - FeatureFlagService.currentFeatureFlagMap[key as FeatureFlagKey].enabled = - process.env[key]?.toLowerCase() === 'true'; - result = true; - } + // only save user data to avoid replacing values from defaults like displayName + Object.keys(FeatureFlagService.currentFeatureFlagMap).forEach((key) => { + saveFeatureFlagMap[key] = { + enabled: FeatureFlagService.currentFeatureFlagMap[key].enabled, + isHidden: FeatureFlagService.currentFeatureFlagMap[key].isHidden, + }; }); - return result; - }; + Store.set(storeKey, saveFeatureFlagMap); + } public static getFeatureFlags(): FeatureFlagMap { FeatureFlagService.initialize(); @@ -92,7 +87,7 @@ export class FeatureFlagService { public static updateFeatureFlag(newFeatureFlags: FeatureFlagMap) { FeatureFlagService.currentFeatureFlagMap = newFeatureFlags; - Store.set(storeKey, newFeatureFlags); + FeatureFlagService.saveFeatureFlags(); } public static getFeatureFlagValue(featureFlagKey: FeatureFlagKey): boolean { diff --git a/Composer/packages/server/src/utility/electronContext.ts b/Composer/packages/server/src/utility/electronContext.ts new file mode 100644 index 0000000000..26f42be5c5 --- /dev/null +++ b/Composer/packages/server/src/utility/electronContext.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { ElectronAuthParameters } from '@botframework-composer/types'; + +export type ElectronContext = { + getAccessToken: ( + params: ElectronAuthParameters + ) => Promise<{ accessToken: string; acquiredAt: number; expiryTime: number }>; +}; + +let context; + +export const useElectronContext = (): ElectronContext => context; + +export const setElectronContext = (c: ElectronContext) => { + context = c; +}; diff --git a/Composer/packages/server/src/utility/isElectron.ts b/Composer/packages/server/src/utility/isElectron.ts new file mode 100644 index 0000000000..d0b063f43e --- /dev/null +++ b/Composer/packages/server/src/utility/isElectron.ts @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export const isElectron: boolean = process.versions && (process.versions as any).electron; diff --git a/Composer/packages/server/src/utility/platform.ts b/Composer/packages/server/src/utility/platform.ts new file mode 100644 index 0000000000..7b71987430 --- /dev/null +++ b/Composer/packages/server/src/utility/platform.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export function isMac() { + return process.platform === 'darwin'; +} + +export function isLinux() { + return process.platform === 'linux'; +} + +export function isWindows() { + return process.platform === 'win32'; +} diff --git a/Composer/packages/types/src/auth.ts b/Composer/packages/types/src/auth.ts new file mode 100644 index 0000000000..75661f28cb --- /dev/null +++ b/Composer/packages/types/src/auth.ts @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export type AuthParameters = { + /** (Web) Client ID of the AAD app that the user is authenticating against. */ + clientId?: string; + /** (Web) List of OAuth scopes that will be granted once the user has authenticated. */ + scopes?: string[]; + + /** + * (Desktop) The resource for which we want to get a token for. + * + * ex: https://microsoft.graph.com/ or 1a3e55f-a8503cf-32bfde0 + */ + targetResource?: string; +}; + +export type ElectronAuthParameters = { + /** + * The resource for which we want to get a token for. + * + * ex: https://microsoft.graph.com/ or 1a3e55f-a8503cf-32bfde0 + */ + targetResource: string; +}; + +export type WebAuthParameters = { + /** Client ID of the AAD app that the user is authenticating against. */ + clientId: string; + /** List of OAuth scopes that will be granted once the user has authenticated. */ + scopes?: string[]; +}; diff --git a/Composer/packages/types/src/index.ts b/Composer/packages/types/src/index.ts index 7e2863d243..5b27ece516 100644 --- a/Composer/packages/types/src/index.ts +++ b/Composer/packages/types/src/index.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +export * from './auth'; export * from './diagnostic'; export * from './dialogUtils'; export * from './extension'; diff --git a/Composer/packages/types/src/indexers.ts b/Composer/packages/types/src/indexers.ts index a51401e16c..538d0d9bea 100644 --- a/Composer/packages/types/src/indexers.ts +++ b/Composer/packages/types/src/indexers.ts @@ -62,6 +62,7 @@ export type DialogInfo = { triggers: ITrigger[]; intentTriggers: IIntentTrigger[]; skills: string[]; + isFormDialog: boolean; }; export type LgTemplateJsonPath = { diff --git a/Composer/packages/types/src/runtime.ts b/Composer/packages/types/src/runtime.ts index 3ba1d7dee3..1da0fe28aa 100644 --- a/Composer/packages/types/src/runtime.ts +++ b/Composer/packages/types/src/runtime.ts @@ -37,11 +37,17 @@ export type BotTemplate = { name: string; description: string; /* absolute path */ - path: string; + path?: string; /* tags for further grouping and search secenario */ tags?: string[]; /* list of supported runtime versions */ support?: string[]; + package?: { + packageName: string; + packageSource: string; + packageVersion: string; + }; + index?: number; }; export type RuntimeTemplate = { @@ -53,6 +59,8 @@ export type RuntimeTemplate = { run: (project: IBotProject, localDisk?: any) => Promise; + identifyManifest: (runtimePath: string) => string; + /** build for deploy method */ buildDeploy: ( runtimePath: string, diff --git a/Composer/packages/types/src/server.ts b/Composer/packages/types/src/server.ts index 3dd18f023b..08a3bed1b7 100644 --- a/Composer/packages/types/src/server.ts +++ b/Composer/packages/types/src/server.ts @@ -4,18 +4,6 @@ import { Skill, FileInfo } from './indexers'; import { IDiagnostic } from './diagnostic'; import { DialogSetting } from './settings'; -export type ProjectTemplate = { - id: string; - name: string; - description: string; - /* absolute path */ - path: string; - /* tags for further grouping and search secenario */ - tags?: string[]; - /* list of supported runtime versions */ - support?: string[]; -}; - export type IBotProject = { fileStorage: any; dir: string; diff --git a/Composer/packages/types/src/shell.ts b/Composer/packages/types/src/shell.ts index 6062fbc3ba..84a3798239 100644 --- a/Composer/packages/types/src/shell.ts +++ b/Composer/packages/types/src/shell.ts @@ -121,9 +121,9 @@ export type ActionContextApi = { }; export type DialogEditingContextApi = { - saveData: (newData: T, updatePath?: string) => void; - onFocusSteps: (stepIds: string[], focusedTab?: string) => void; - onFocusEvent: (eventId: string) => void; + saveData: (newData: T, updatePath?: string, callback?: () => void | Promise) => Promise; + onFocusSteps: (stepIds: string[], focusedTab?: string) => Promise; + onFocusEvent: (eventId: string) => Promise; onSelect: (ids: string[]) => void; onCopy: (clipboardActions: any[]) => void; undo: () => void; diff --git a/Composer/packages/ui-plugins/lg/src/LgField.tsx b/Composer/packages/ui-plugins/lg/src/LgField.tsx index 3c16dc0b21..683cc494a8 100644 --- a/Composer/packages/ui-plugins/lg/src/LgField.tsx +++ b/Composer/packages/ui-plugins/lg/src/LgField.tsx @@ -78,6 +78,7 @@ const LgField: React.FC> = (props) => { if (body) { updateLgTemplate(body); props.onChange(new LgTemplateRef(lgName).toString()); + shellApi.commitChanges(); } else { shellApi.removeLgTemplate(lgFileId, lgName); props.onChange(); diff --git a/Composer/yarn.lock b/Composer/yarn.lock index dce1056d3f..86c3e52b71 100644 --- a/Composer/yarn.lock +++ b/Composer/yarn.lock @@ -7,6 +7,15 @@ resolved "https://botbuilder.myget.org/F/botframework-cli/npm/7zip-bin/-/7zip-bin-5.0.3.tgz#bc5b5532ecafd923a61f2fb097e3b108c0106a3f" integrity sha1-vFtVMuyv2SOmHy+wl+OxCMAQaj8= +"@apidevtools/json-schema-ref-parser@^9.0.1": + version "9.0.6" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz#5d9000a3ac1fd25404da886da6b266adcd99cf1c" + integrity sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg== + dependencies: + "@jsdevtools/ono" "^7.1.3" + call-me-maybe "^1.0.1" + js-yaml "^3.13.1" + "@azure/cognitiveservices-luis-authoring@4.0.0-preview.1": version "4.0.0-preview.1" resolved "https://botbuilder.myget.org/F/botframework-cli/npm/@azure/cognitiveservices-luis-authoring/-/@azure/cognitiveservices-luis-authoring-4.0.0-preview.1.tgz#79de764893dc997d95713bb6a0487d887dc78f40" @@ -881,9 +890,9 @@ js-tokens "^4.0.0" "@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.10.5", "@babel/parser@^7.11.3", "@babel/parser@^7.11.5", "@babel/parser@^7.2.2", "@babel/parser@^7.3.4", "@babel/parser@^7.4.0", "@babel/parser@^7.7.0", "@babel/parser@^7.7.4", "@babel/parser@^7.7.5", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0", "@babel/parser@^7.9.6": - version "7.12.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.3.tgz#a305415ebe7a6c7023b40b5122a0662d928334cd" - integrity sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw== + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.5.tgz#b4af32ddd473c0bfa643bd7ff0728b8e71b81ea0" + integrity sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ== "@babel/plugin-proposal-async-generator-functions@^7.2.0": version "7.2.0" @@ -2965,6 +2974,26 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@jsdevtools/ono@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" + integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== + +"@microsoft/bf-cli-command@4.11.0-dev.20201025.69cf2b9": + version "4.11.0-dev.20201025.69cf2b9" + resolved "https://registry.yarnpkg.com/@microsoft/bf-cli-command/-/bf-cli-command-4.11.0-dev.20201025.69cf2b9.tgz#286b9dfef6483a55562baf4ebf7a0d99a43075e4" + integrity sha512-jhcpVBrFrFepevPZlP9iGJrUPh30E/L8yHr5wvX6jcSt/7RSVD+A7DIShDO+b8n45ii+8ZxVGqEGv7aN5ggp6g== + dependencies: + "@oclif/command" "~1.5.19" + "@oclif/config" "~1.13.3" + "@oclif/errors" "~1.2.2" + applicationinsights "^1.0.8" + chalk "2.4.1" + cli-ux "~4.9.3" + debug "^4.1.1" + fs-extra "^7.0.1" + tslib "^2.0.3" + "@microsoft/bf-cli-command@^4.10.0-preview.141651": version "4.10.0-preview.141651" resolved "https://botbuilder.myget.org/F/botframework-cli/npm/@microsoft/bf-cli-command/-/@microsoft/bf-cli-command-4.10.0-preview.141651.tgz#680875f716285fb8658da8098a0ee524b07c5765" @@ -2980,6 +3009,33 @@ fs-extra "^7.0.1" tslib "~1.10.0" +"@microsoft/bf-dialog@4.11.0-dev.20201025.69cf2b9": + version "4.11.0-dev.20201025.69cf2b9" + resolved "https://registry.yarnpkg.com/@microsoft/bf-dialog/-/bf-dialog-4.11.0-dev.20201025.69cf2b9.tgz#7f33e2b4d52e355d1f5888b9d1f341ae32fe90c3" + integrity sha512-gzBRwKSEkHXgPN9bB5ttuEORRbK7o6Py+7P9JKYQjDrytt2C7ZSqKn7UtVm8/OBO8BDwu9EMONpfq2Ep3FPUtw== + dependencies: + "@apidevtools/json-schema-ref-parser" "^9.0.1" + "@microsoft/bf-cli-command" "4.11.0-dev.20201025.69cf2b9" + "@oclif/command" "~1.5.19" + "@oclif/config" "~1.13.3" + "@oclif/errors" "~1.2.2" + "@snyk/nuget-semver" "~1.3.0" + "@types/lru-cache" "^5.1.0" + "@types/xml2js" "^0.4.4" + ajv "^6.12.2" + chalk "^2.4.2" + clone "^2.1.2" + fs-extra "^8.1.0" + get-uri "~3.0.2" + globby "^11.0.0" + json-ptr "~1.3.0" + json-schema-merge-allof "~0.7.0" + os "~0.1.1" + path "^0.12.7" + seedrandom "~3.0.5" + tslib "^2.0.3" + xml2js "^0.4.19" + "@microsoft/bf-dispatcher@^4.11.0-beta.20201016.393c6b2": version "4.11.0-beta.20201016.393c6b2" resolved "https://registry.yarnpkg.com/@microsoft/bf-dispatcher/-/bf-dispatcher-4.11.0-beta.20201016.393c6b2.tgz#7dab414752f8711fed37ae5625c38fdd0192eddb" @@ -3243,6 +3299,11 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@snyk/nuget-semver@~1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@snyk/nuget-semver/-/nuget-semver-1.3.0.tgz#a74ef5340dcada3bc4b3a019b7950738df51199c" + integrity sha512-1CL4BzQKFPwml+BBefKuM0v9UfsFOgSKzrTfYpUkiSNkUVsMxXK37LlT3HtG7zGpMxXiG+XXfPopo/96Z0wfNg== + "@svgr/babel-plugin-add-jsx-attribute@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.0.0.tgz#5acf239cd2747b1a36ec7e708de05d914cb9b948" @@ -3416,6 +3477,11 @@ "@testing-library/dom" "^7.2.2" "@types/testing-library__react" "^10.0.1" +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + "@types/anymatch@*": version "1.3.1" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" @@ -4068,6 +4134,13 @@ dependencies: "@types/node" "*" +"@types/xml2js@^0.4.4": + version "0.4.5" + resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.5.tgz#d21759b056f282d9c7066f15bbf5c19b908f22fa" + integrity sha512-yohU3zMn0fkhlape1nxXG2bLEGZRc1FeqF80RoHaYXJN7uibaauXfhzhOJr1Xh36sn+/tx21QAOf07b/xYVk1w== + dependencies: + "@types/node" "*" + "@types/xmldom@^0.1.29": version "0.1.29" resolved "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/@types/xmldom/-/@types/xmldom-0.1.29.tgz#c4428b0ca86d3b881475726fd94980b38a27c381" @@ -8107,6 +8180,11 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +data-uri-to-buffer@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" + integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== + data-urls@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" @@ -8140,6 +8218,13 @@ debug@3.1.0, debug@=3.1.0: dependencies: ms "2.0.0" +debug@4: + version "4.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" + integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== + dependencies: + ms "2.1.2" + debug@4.1.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" @@ -9839,6 +9924,11 @@ file-loader@4.2.0: loader-utils "^1.2.3" schema-utils "^2.0.0" +file-uri-to-path@2: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz#7b415aeba227d575851e0a5b0c640d7656403fba" + integrity sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg== + filelist@^1.0.1: version "1.0.1" resolved "https://botbuilder.myget.org/F/botframework-cli/npm/filelist/-/filelist-1.0.1.tgz#f10d1a3ae86c1694808e8f20906f43d4c9132dbb" @@ -10275,6 +10365,14 @@ fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +ftp@^0.3.10: + version "0.3.10" + resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" + integrity sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0= + dependencies: + readable-stream "1.1.x" + xregexp "2.0.0" + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -10389,6 +10487,18 @@ get-stream@^5.0.0, get-stream@^5.1.0: dependencies: pump "^3.0.0" +get-uri@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c" + integrity sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg== + dependencies: + "@tootallnate/once" "1" + data-uri-to-buffer "3" + debug "4" + file-uri-to-path "2" + fs-extra "^8.1.0" + ftp "^0.3.10" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -12561,6 +12671,13 @@ json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== +json-ptr@~1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/json-ptr/-/json-ptr-1.3.2.tgz#17f45b322a843b1f2fbcc9b45132bd9b3ba8cd38" + integrity sha512-tFH40YQ+lG7mgYYM1kGZOhQngO4SbOEHZJlA4W+NtetWZ20EUU3BPU+30uWRKumuAJoSo5eqrsXD2h72ioS8ew== + dependencies: + tslib "^2.0.0" + json-schema-compare@^0.2.2: version "0.2.2" resolved "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/json-schema-compare/-/json-schema-compare-0.2.2.tgz#dd601508335a90c7f4cfadb6b2e397225c908e56" @@ -12584,6 +12701,15 @@ json-schema-merge-allof@^0.6.0: json-schema-compare "^0.2.2" lodash "^4.17.4" +json-schema-merge-allof@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/json-schema-merge-allof/-/json-schema-merge-allof-0.7.0.tgz#84d3e8c3e03d3060014286958eb8834fa9d76304" + integrity sha512-kvsuSVnl1n5xnNEu5ed4o8r8ujSA4/IgRtHmpgfMfa7FOMIRAzN4F9qbuklouTn5J8bi83y6MQ11n+ERMMTXZg== + dependencies: + compute-lcm "^1.1.0" + json-schema-compare "^0.2.2" + lodash "^4.17.4" + json-schema-ref-parser@^7.1.0, json-schema-ref-parser@^7.1.3: version "7.1.4" resolved "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/json-schema-ref-parser/-/json-schema-ref-parser-7.1.4.tgz#abb3f2613911e9060dc2268477b40591753facf0" @@ -13827,6 +13953,11 @@ ms@2.1.1, ms@^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.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -13840,10 +13971,10 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -multimatch@^4.0.0: - version "4.0.0" - resolved "https://botbuilder.myget.org/F/botframework-cli/npm/multimatch/-/multimatch-4.0.0.tgz#8c3c0f6e3e8449ada0af3dd29efb491a375191b3" - integrity sha1-jDwPbj6ESa2grz3SnvtJGjdRkbM= +multimatch@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6" + integrity sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA== dependencies: "@types/minimatch" "^3.0.3" array-differ "^3.0.0" @@ -14560,6 +14691,11 @@ os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= +os@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/os/-/os-0.1.1.tgz#208845e89e193ad4d971474b93947736a56d13f3" + integrity sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M= + osenv@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" @@ -16421,6 +16557,16 @@ read-text-file@^1.1.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@1.1.x: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + readable-stream@^3.0.6, readable-stream@^3.1.1: version "3.2.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d" @@ -20148,6 +20294,11 @@ xmlhttprequest@1: resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw= +xregexp@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" + integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM= + xregexp@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50" diff --git a/azure-pipelines-static-analysis.yml b/azure-pipelines-static-analysis.yml index 32d3af9961..528ffb0fa5 100644 --- a/azure-pipelines-static-analysis.yml +++ b/azure-pipelines-static-analysis.yml @@ -10,6 +10,9 @@ schedules: # Do not run PR validation pr: none +# Do not run CI validation +trigger: none + # Semmle task only works on Windows pool: vmImage: 'windows-latest' diff --git a/extensions/azurePublish/package.json b/extensions/azurePublish/package.json index d693a038d5..2f4a501c0f 100644 --- a/extensions/azurePublish/package.json +++ b/extensions/azurePublish/package.json @@ -22,7 +22,7 @@ "@bfc/indexers": "../../Composer/packages/lib/indexers", "@bfc/shared": "../../Composer/packages/lib/shared", "@botframework-composer/types": "0.0.1", - "@microsoft/bf-lu": "^4.11.0-rc.20201030.a9f9b96", + "@microsoft/bf-lu": "4.11.0-dev.20201005.7e5e1b8", "@microsoft/bf-luis-cli": "^4.10.0-dev.20200721.8bb21ac", "@types/archiver": "3.1.0", "@types/fs-extra": "8.1.0", diff --git a/extensions/azurePublish/src/index.ts b/extensions/azurePublish/src/index.ts index ee94004024..9b5fe29f1c 100644 --- a/extensions/azurePublish/src/index.ts +++ b/extensions/azurePublish/src/index.ts @@ -127,7 +127,7 @@ export default async (composer: ExtensionRegistration): Promise => { */ private init = async (project: any, srcTemplate: string, resourcekey: string, runtime: any) => { // point to the declarative assets (possibly in remote storage) - const botFiles = project.getProject().files; + const botFiles = project.getProject().filesWithoutRecognizers; const botFolder = this.getBotFolder(resourcekey, this.mode); const runtimeFolder = this.getRuntimeFolder(resourcekey); diff --git a/extensions/azurePublish/src/luisAndQnA.ts b/extensions/azurePublish/src/luisAndQnA.ts index 8afb9db7a5..957fff270a 100644 --- a/extensions/azurePublish/src/luisAndQnA.ts +++ b/extensions/azurePublish/src/luisAndQnA.ts @@ -10,6 +10,7 @@ import { ILuisConfig, FileInfo, IQnAConfig } from '@botframework-composer/types' import { ICrossTrainConfig, createCrossTrainConfig } from './utils/crossTrainUtil'; import { BotProjectDeployLoggerType } from './botProjectLoggerType'; +import { luImportResolverGenerator } from '@bfc/shared/lib/luBuildResolver' const crossTrainer = require('@microsoft/bf-lu/lib/parser/cross-train/crossTrainer.js'); const luBuild = require('@microsoft/bf-lu/lib/parser/lubuild/builder.js'); @@ -132,16 +133,21 @@ export class LuisAndQnaPublish { for (const luFile of luFiles) { luContents.push({ content: fs.readFileSync(luFile, { encoding: 'utf-8' }), - id: luFile.substring(luFile.lastIndexOf('\\') + 1), + name: path.basename(luFile), + id: path.basename(luFile), + path: luFile, }); } for (const qnaFile of qnaFiles) { qnaContents.push({ content: fs.readFileSync(qnaFile, { encoding: 'utf-8' }), - id: qnaFile.substring(qnaFile.lastIndexOf('\\') + 1), + name: path.basename(qnaFile), + id: path.basename(qnaFile), + path: qnaFile, }); } - const result = await crossTrainer.crossTrain(luContents, qnaContents, this.crossTrainConfig); + const importResolver = luImportResolverGenerator([...qnaContents, ...luContents] as FileInfo[]); + const result = await crossTrainer.crossTrain(luContents, qnaContents, this.crossTrainConfig, importResolver); await this.writeCrossTrainFiles(result.luResult); await this.writeCrossTrainFiles(result.qnaResult); diff --git a/extensions/azurePublish/yarn.lock b/extensions/azurePublish/yarn.lock index 2116966183..980e8e9ff6 100644 --- a/extensions/azurePublish/yarn.lock +++ b/extensions/azurePublish/yarn.lock @@ -184,6 +184,7 @@ "@botframework-composer/types" "*" format-message "6.2.3" json-schema "^0.2.5" + multimatch "^5.0.0" nanoid "^3.1.3" nanoid-dictionary "^3.0.0" @@ -233,6 +234,28 @@ semver "^5.5.1" tslib "^1.10.0" +"@microsoft/bf-lu@4.11.0-dev.20201005.7e5e1b8": + version "4.11.0-dev.20201005.7e5e1b8" + resolved "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/@microsoft/bf-lu/-/@microsoft/bf-lu-4.11.0-dev.20201005.7e5e1b8.tgz#62f9a37bb8340456e41853e179f2941dab9dfc87" + integrity sha1-Yvmje7g0BFbkGFPhefKUHaud/Ic= + dependencies: + "@azure/cognitiveservices-luis-authoring" "4.0.0-preview.1" + "@azure/ms-rest-azure-js" "2.0.1" + "@types/node-fetch" "~2.5.5" + antlr4 "^4.7.2" + chalk "2.4.1" + console-stream "^0.1.1" + deep-equal "^1.0.1" + delay "^4.3.0" + fs-extra "^8.1.0" + get-stdin "^6.0.0" + globby "^10.0.1" + intercept-stdout "^0.1.2" + lodash "^4.17.19" + node-fetch "~2.6.0" + semver "^5.5.1" + tslib "^1.10.0" + "@microsoft/bf-lu@^4.11.0-rc.20201030.a9f9b96": version "4.11.0-rc.20201030.a9f9b96" resolved "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/@microsoft/bf-lu/-/@microsoft/bf-lu-4.11.0-rc.20201030.a9f9b96.tgz#9fcb2a18d66bcef74eabc98f06df4574215c402f" @@ -510,7 +533,7 @@ resolved "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/@types/mime/-/@types/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a" integrity sha1-yJO3NyHbc2mZQ7/DZTsd63+qSjo= -"@types/minimatch@*": +"@types/minimatch@*", "@types/minimatch@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== @@ -801,11 +824,21 @@ archiver@^5.0.2: tar-stream "^2.1.4" zip-stream "^4.0.0" +array-differ@^3.0.0: + version "3.0.0" + resolved "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" + integrity sha1-PLs9DzFoEOr8xHYkc0I31q7krms= + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +arrify@^2.0.1: + version "2.0.1" + resolved "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" + integrity sha1-yWVekzHgq81YjSp8rX6ZVvZnAfo= + asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" @@ -2091,6 +2124,17 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +multimatch@^5.0.0: + version "5.0.0" + resolved "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6" + integrity sha1-kyuACWPOp6MaAzMo+h4MOhh02+Y= + dependencies: + "@types/minimatch" "^3.0.3" + array-differ "^3.0.0" + array-union "^2.1.0" + arrify "^2.0.1" + minimatch "^3.0.4" + nanoid-dictionary@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/nanoid-dictionary/-/nanoid-dictionary-3.0.0.tgz#e4ad77e528792095662a7be1855ce31448d0687c" diff --git a/extensions/localPublish/src/index.ts b/extensions/localPublish/src/index.ts index b4dda5059e..ee560c1a3f 100644 --- a/extensions/localPublish/src/index.ts +++ b/extensions/localPublish/src/index.ts @@ -80,6 +80,8 @@ class LocalPublisher implements PublishPlugin { 'azurewebapp' ); } else if (project.settings.runtime.path && project.settings.runtime.command) { + const runtimePath = path.isAbsolute(project.settings.runtime.path)? project.settings.runtime.path: path.resolve(project.dataDir, project.settings.runtime.path); + await runtime.build(runtimePath, project); await runtime.setSkillManifest( project.settings.runtime.path, project.fileStorage, diff --git a/extensions/runtimes/src/index.ts b/extensions/runtimes/src/index.ts index a7b0fa5be6..a67fa2a38e 100644 --- a/extensions/runtimes/src/index.ts +++ b/extensions/runtimes/src/index.ts @@ -39,6 +39,9 @@ export default async (composer: any): Promise => { } composer.log('FINISHED BUILDING!'); }, + identifyManifest: (runtimePath: string): string => { + return path.join(runtimePath, 'azurewebapp', 'Microsoft.BotFramework.Composer.WebApp.csproj'); + }, run: async (project: any, localDisk: IFileStorage) => { composer.log('RUN THIS C# PROJECT!'); }, @@ -166,11 +169,14 @@ export default async (composer: any): Promise => { // install dev dependencies in production, make sure typescript is installed const { stderr: installErr } = await execAsync('npm install && npm install --only=dev', { cwd: runtimePath, + timeout: 120000, }); if (installErr) { // in order to not throw warning, we just log all warning and error message - composer.log(installErr); + composer.log(`npm install timeout, ${installErr}`); } + + // runtime build need typescript const { stderr: install2Err } = await execAsync('npm run build', { cwd: runtimePath, }); @@ -179,6 +185,9 @@ export default async (composer: any): Promise => { } composer.log('BUILD COMPLETE'); }, + identifyManifest: (runtimePath: string): string => { + return path.join(runtimePath, 'Microsoft.BotFramework.Composer.WebApp.csproj'); + }, run: async (project: any, localDisk: IFileStorage) => { // do stuff }, @@ -220,17 +229,6 @@ export default async (composer: any): Promise => { // used to read bot project template from source (bundled in plugin) const excludeFolder = new Set().add(path.resolve(sourcePath, 'node_modules')); await copyDir(sourcePath, localDisk, destPath, project.fileStorage, excludeFolder); - // install dev dependencies in production, make sure typescript is installed - const { stderr: initErr } = await execAsync('npm install && npm install --only=dev', { - cwd: destPath, - }); - if (initErr) { - composer.log(initErr); - } - const { stderr: initErr2 } = await execAsync('npm run build', { cwd: destPath }); - if (initErr2) { - throw new Error(initErr2); - } return destPath; } else { throw new Error(`Runtime already exists at ${destPath}`); diff --git a/extensions/samples/assets/projects/QnASample/settings/appsettings.json b/extensions/samples/assets/projects/QnASample/settings/appsettings.json deleted file mode 100644 index 356542b7d1..0000000000 --- a/extensions/samples/assets/projects/QnASample/settings/appsettings.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "feature": { - "UseShowTypingMiddleware": false, - "UseInspectionMiddleware": false, - "RemoveRecipientMention": false - }, - "MicrosoftAppPassword": "", - "MicrosoftAppId": "", - "cosmosDb": { - "authKey": "", - "collectionId": "botstate-collection", - "cosmosDBEndpoint": "", - "databaseId": "botstate-db" - }, - "applicationInsights": { - "InstrumentationKey": "" - }, - "blobStorage": { - "connectionString": "", - "container": "transcripts" - }, - "luis": { - "name": "", - "authoringKey": "", - "authoringEndpoint": "", - "endpointKey": "", - "endpoint": "", - "authoringRegion": "westus", - "defaultLanguage": "en-us", - "environment": "composer" - }, - "publishTargets": [], - "qna": { - "subscriptionKey": "", - "knowledgebaseid": "", - "endpointKey": "", - "hostname": "", - "region": "westus" - }, - "telemetry": { - "logPersonalInformation": false, - "logActivities": true - }, - "runtime": { - "customRuntime": false, - "path": "", - "command": "" - }, - "downsampling": { - "maxImbalanceRatio": 10, - "maxUtteranceAllowed": 15000 - }, - "defaultLanguage": "en-us", - "languages": [ - "en-us" - ] -} \ No newline at end of file diff --git a/extensions/samples/package.json b/extensions/samples/package.json index 9305660613..ea180714dc 100644 --- a/extensions/samples/package.json +++ b/extensions/samples/package.json @@ -11,6 +11,7 @@ "author": "", "license": "ISC", "dependencies": { + "@botframework-composer/types": "file:../../Composer/packages/types", "path": "^0.12.7", "portfinder": "^1.0.26", "rimraf": "^3.0.2", diff --git a/extensions/samples/src/index.ts b/extensions/samples/src/index.ts index 6eb91f8174..bdab71868e 100644 --- a/extensions/samples/src/index.ts +++ b/extensions/samples/src/index.ts @@ -3,6 +3,7 @@ import path from 'path'; import fs from 'fs'; +import { BotTemplate } from '@botframework-composer/types'; const samplesDir = path.resolve(__dirname, '../assets/projects'); const boilerplateDir = path.resolve(__dirname, '../assets/shared'); @@ -68,9 +69,22 @@ const samplesRegitry = { }, }; -function getSamples(): any[] { +function getRemoteSamples(): BotTemplate[] { + return [{ + id: 'conversationalcore', + name: 'Conversational Core', + description: 'A hosted template that provides a root bot extended by common .lg packages', + package: { + packageName: 'Microsoft.ConversationalCore.Template', + packageSource: 'nuget', + packageVersion: '0.0.1-preview5' + } + }] +} + +function getSamples(): BotTemplate[] { const subPaths = fs.readdirSync(samplesDir); - const samples = []; + let samples: BotTemplate[] = []; for (const subPath of subPaths) { const fullPath = samplesDir + '/' + subPath; if (!fs.statSync(fullPath).isDirectory()) { @@ -79,12 +93,15 @@ function getSamples(): any[] { // only looking for directories const dirname = subPath; - let sample = { id: dirname, name: dirname, description: dirname, path: fullPath, ...samplesRegitry['*'] }; + let sample: BotTemplate = { id: dirname, name: dirname, description: dirname, path: fullPath, ...samplesRegitry['*'] }; if (samplesRegitry[sample.id]) { sample = { ...sample, ...samplesRegitry[sample.id] }; } samples.push(sample); } + + samples = samples.concat(getRemoteSamples()); + samples.sort((a, b) => { if (a.index && b.index) { return a.index - b.index; @@ -115,11 +132,11 @@ const boilerplates = getBoilerplates(); export default async (composer: any): Promise => { // register this publishing method with Composer - for (const temlate of samples) { - await composer.addBotTemplate(temlate); + for (const template of samples) { + await composer.addBotTemplate(template); } - for (const temlate of boilerplates) { - await composer.addBaseTemplate(temlate); + for (const template of boilerplates) { + await composer.addBaseTemplate(template); } }; diff --git a/extensions/samples/yarn.lock b/extensions/samples/yarn.lock index 5e8e3282ee..17ee65b0d8 100644 --- a/extensions/samples/yarn.lock +++ b/extensions/samples/yarn.lock @@ -2,11 +2,79 @@ # yarn lockfile v1 +"@botframework-composer/types@file:../../Composer/packages/types": + version "0.0.2" + dependencies: + "@types/express" "^4.16.1" + json-schema "^0.2.5" + +"@types/body-parser@*": + version "1.19.0" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" + integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.33" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546" + integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@*": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.13.tgz#d9af025e925fc8b089be37423b8d1eac781be084" + integrity sha512-RgDi5a4nuzam073lRGKTUIaL3eF2+H7LJvJ8eUnCI0wA6SNjXc44DCmWNiTLs/AZ7QlsFWZiw/gTG3nSQGL0fA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@^4.16.1": + version "4.17.8" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.8.tgz#3df4293293317e61c60137d273a2e96cd8d5f27a" + integrity sha512-wLhcKh3PMlyA2cNAB9sjM1BntnhPMiM0JOBwPBqttjHev2428MLEB4AYVN+d8s2iyCVZac+o41Pflm/ZH5vLXQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "*" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/mime@*": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a" + integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q== + +"@types/node@*": + version "14.14.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.3.tgz#e1c09064121f894baaad2bd9f12ce4a41bffb274" + integrity sha512-33/L34xS7HVUx23e0wOT2V1qPF1IrHgQccdJVm9uXGTB9vFBrrzBtkQymT8VskeKOxjz55MSqMv0xuLq+u98WQ== + "@types/node@^14.11.8": version "14.11.8" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.8.tgz#fe2012f2355e4ce08bca44aeb3abbb21cf88d33f" integrity sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw== +"@types/qs@*": + version "6.9.5" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b" + integrity sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ== + +"@types/range-parser@*": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" + integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== + +"@types/serve-static@*": + version "1.13.6" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.6.tgz#866b1b8dec41c36e28c7be40ac725b88be43c5c1" + integrity sha512-nuRJmv7jW7VmCVTn+IgYDkkbbDGyIINOeu/G0d74X3lm6E5KfMeQPJhxIt1ayQeQB3cSxvYs1RA/wipYoFB4EA== + dependencies: + "@types/mime" "*" + "@types/node" "*" + async@^2.6.2: version "2.6.3" resolved "https://registry.npmjs.org/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -74,6 +142,11 @@ inherits@2.0.3: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +json-schema@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.5.tgz#97997f50972dd0500214e208c407efa4b5d7063b" + integrity sha512-gWJOWYFrhQ8j7pVm0EM8Slr+EPVq1Phf6lvzvD/WCeqkrx/f2xBI0xOsRRS9xCn3I4vKtP519dvs3TP09r24wQ== + lodash@^4.17.14: version "4.17.17" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.17.tgz#d9018b3acc57a95c9dcf4a45c6b63b877b6c2d45" diff --git a/runtime/dotnet/azurefunctions/Microsoft.BotFramework.Composer.Functions.csproj b/runtime/dotnet/azurefunctions/Microsoft.BotFramework.Composer.Functions.csproj index 9e7914cf92..43d38999cb 100644 --- a/runtime/dotnet/azurefunctions/Microsoft.BotFramework.Composer.Functions.csproj +++ b/runtime/dotnet/azurefunctions/Microsoft.BotFramework.Composer.Functions.csproj @@ -13,18 +13,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/runtime/dotnet/azurefunctions/Startup.cs b/runtime/dotnet/azurefunctions/Startup.cs index e390ec2ff7..63606c1f91 100644 --- a/runtime/dotnet/azurefunctions/Startup.cs +++ b/runtime/dotnet/azurefunctions/Startup.cs @@ -9,6 +9,7 @@ using Microsoft.Bot.Builder.AI.QnA; using Microsoft.Bot.Builder.ApplicationInsights; using Microsoft.Bot.Builder.Azure; +using Microsoft.Bot.Builder.Azure.Blobs; using Microsoft.Bot.Builder.BotFramework; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Builder.Dialogs.Adaptive; @@ -176,7 +177,7 @@ public void ConfigureTranscriptLoggerMiddleware(BotFrameworkHttpAdapter adapter, { if (ConfigSectionValid(settings?.BlobStorage?.ConnectionString) && ConfigSectionValid(settings?.BlobStorage?.Container)) { - adapter.Use(new TranscriptLoggerMiddleware(new AzureBlobTranscriptStore(settings?.BlobStorage?.ConnectionString, settings?.BlobStorage?.Container))); + adapter.Use(new TranscriptLoggerMiddleware(new BlobsTranscriptStore(settings?.BlobStorage?.ConnectionString, settings?.BlobStorage?.Container))); } } diff --git a/runtime/dotnet/azurewebapp/Microsoft.BotFramework.Composer.WebApp.csproj b/runtime/dotnet/azurewebapp/Microsoft.BotFramework.Composer.WebApp.csproj index 58b7e39c01..e1c23b6d09 100644 --- a/runtime/dotnet/azurewebapp/Microsoft.BotFramework.Composer.WebApp.csproj +++ b/runtime/dotnet/azurewebapp/Microsoft.BotFramework.Composer.WebApp.csproj @@ -17,18 +17,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + all diff --git a/runtime/dotnet/azurewebapp/Schemas/sdk.schema b/runtime/dotnet/azurewebapp/Schemas/sdk.schema index 77500788d1..a1f0436797 100644 --- a/runtime/dotnet/azurewebapp/Schemas/sdk.schema +++ b/runtime/dotnet/azurewebapp/Schemas/sdk.schema @@ -34,6 +34,9 @@ { "$ref": "#/definitions/Microsoft.CancelDialog" }, + { + "$ref": "#/definitions/Microsoft.ChannelMentionEntityRecognizer" + }, { "$ref": "#/definitions/Microsoft.ChoiceInput" }, @@ -46,6 +49,9 @@ { "$ref": "#/definitions/Microsoft.ConfirmationEntityRecognizer" }, + { + "$ref": "#/definitions/Microsoft.ContinueConversationLater" + }, { "$ref": "#/definitions/Microsoft.ContinueLoop" }, @@ -202,6 +208,9 @@ { "$ref": "#/definitions/Microsoft.OnHandoffActivity" }, + { + "$ref": "#/definitions/Microsoft.OnInstallationUpdateActivity" + }, { "$ref": "#/definitions/Microsoft.OnIntent" }, @@ -301,6 +310,9 @@ { "$ref": "#/definitions/Microsoft.TextTemplate" }, + { + "$ref": "#/definitions/Microsoft.ThrowException" + }, { "$ref": "#/definitions/Microsoft.TraceActivity" }, @@ -317,7 +329,7 @@ "definitions": { "Microsoft.ActivityTemplate": { "$role": "implements(Microsoft.IActivityTemplate)", - "title": "Microsoft ActivityTemplate", + "title": "Microsoft activity template", "type": "object", "required": [ "template", @@ -325,7 +337,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -356,12 +368,12 @@ }, "Microsoft.AdaptiveDialog": { "$role": "implements(Microsoft.IDialog)", - "title": "Adaptive Dialog", + "title": "Adaptive dialog", "description": "Flexible, data driven dialog that can adapt to the conversation.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -399,7 +411,7 @@ }, "generator": { "$kind": "Microsoft.ILanguageGenerator", - "title": "Language Generator", + "title": "Language generator", "description": "Language generator that generates bot responses.", "$ref": "#/definitions/Microsoft.ILanguageGenerator" }, @@ -681,13 +693,16 @@ } }, "Microsoft.AgeEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Age Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Age entity recognizer", "description": "Recognizer which recognizes age.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -719,12 +734,12 @@ "implements(Microsoft.IDialog)", "extends(Microsoft.SendActivity)" ], - "title": "Send Activity to Ask a question", + "title": "Send activity to ask a question", "description": "This is an action which sends an activity to the user when a response is expected", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -739,7 +754,7 @@ "properties": { "expectedProperties": { "$ref": "#/definitions/arrayExpression", - "title": "Expected Properties", + "title": "Expected properties", "description": "Properties expected from the user.", "examples": [ [ @@ -755,7 +770,7 @@ }, "defaultOperation": { "$ref": "#/definitions/stringExpression", - "title": "Default Operation", + "title": "Default operation", "description": "Sets the default operation that will be used when no operation is recognized in the response to this Ask.", "examples": [ "Add()", @@ -768,7 +783,7 @@ "description": "Optional id for the dialog" }, "disabled": { - "$ref": "#/definitions/stringExpression", + "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", "examples": [ @@ -804,7 +819,7 @@ "description": "Collect information - Ask for a file or image.", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "type": "object", "required": [ @@ -994,7 +1009,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -1055,7 +1070,7 @@ }, "activityProcessed": { "$ref": "#/definitions/booleanExpression", - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the dialog that is called can process the current activity.", "default": true }, @@ -1088,7 +1103,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -1117,7 +1132,7 @@ }, "activityProcessed": { "$ref": "#/definitions/booleanExpression", - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the skill will be started using the activity in the current turn context instead of the activity in the Activity property.", "default": true, "examples": [ @@ -1150,13 +1165,13 @@ }, "connectionName": { "$ref": "#/definitions/stringExpression", - "title": "OAuth Connection Name (SSO)", + "title": "OAuth connection name (SSO)", "description": "The OAuth Connection Name, that would be used to perform Single SignOn with a skill.", "default": "=settings.connectionName" }, "skillAppId": { "$ref": "#/definitions/stringExpression", - "title": "Skill App ID", + "title": "Skill App Id", "description": "The Microsoft App ID for the skill." }, "skillEndpoint": { @@ -1175,7 +1190,7 @@ }, "allowInterruptions": { "$ref": "#/definitions/booleanExpression", - "title": "Allow Interruptions", + "title": "Allow interruptions", "description": "A boolean expression that determines whether the parent should be allowed to interrupt the skill.", "default": true, "examples": [ @@ -1199,7 +1214,7 @@ }, "Microsoft.BreakLoop": { "$role": "implements(Microsoft.IDialog)", - "title": "Break Loop", + "title": "Break loop", "description": "Stop executing this loop", "type": "object", "required": [ @@ -1207,7 +1222,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -1251,7 +1266,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -1279,7 +1294,7 @@ }, "activityProcessed": { "$ref": "#/definitions/booleanExpression", - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the caller dialog is told it should process the current activity.", "default": true }, @@ -1315,7 +1330,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -1343,7 +1358,7 @@ }, "activityProcessed": { "$ref": "#/definitions/booleanExpression", - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the caller dialog is told it should process the current activity.", "default": true }, @@ -1372,6 +1387,42 @@ } } }, + "Microsoft.ChannelMentionEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)" + ], + "title": "Channel mention entity recognizer", + "description": "Promotes mention entities passed by a channel via the activity.entities into recognizer result.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.11.0-rc2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ChannelMentionEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, "Microsoft.ChoiceInput": { "$role": [ "implements(Microsoft.IDialog)", @@ -1382,7 +1433,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -1725,7 +1776,7 @@ }, "Microsoft.ConditionalSelector": { "$role": "implements(Microsoft.ITriggerSelector)", - "title": "Conditional Trigger Selector", + "title": "Conditional trigger selector", "description": "Use a rule selector based on a condition", "type": "object", "required": [ @@ -1736,7 +1787,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -1783,7 +1834,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -1838,7 +1889,7 @@ ] }, "choiceOptions": { - "title": "Choice Options", + "title": "Choice options", "description": "Choice Options or expression which provides Choice Options to control display choices to the user.", "oneOf": [ { @@ -2071,13 +2122,16 @@ } }, "Microsoft.ConfirmationEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Confirmation Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Confirmation entity recognizer", "description": "Recognizer which recognizes confirmation choices (yes/no).", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2104,9 +2158,71 @@ } } }, + "Microsoft.ContinueConversationLater": { + "$role": "implements(Microsoft.IDialog)", + "title": "Continue conversation later (Queue)", + "description": "Continue conversation at later time (via Azure Storage Queue).", + "type": "object", + "required": [ + "date", + "connectionString", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.11.0-rc2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "date": { + "$ref": "#/definitions/stringExpression", + "title": "Date", + "description": "Date in the future as a ISO string when the conversation should continue.", + "examples": [ + "=addHours(utcNow(), 1)" + ] + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "Value to send in the activity.value." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ContinueConversationLater" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, "Microsoft.ContinueLoop": { "$role": "implements(Microsoft.IDialog)", - "title": "Continue Loop", + "title": "Continue loop", "description": "Stop executing this template and continue with the next iteration of the loop.", "type": "object", "required": [ @@ -2114,7 +2230,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -2153,7 +2269,7 @@ }, "Microsoft.CrossTrainedRecognizerSet": { "$role": "implements(Microsoft.IRecognizer)", - "title": "Cross-trained Recognizer Set", + "title": "Cross-trained recognizer set", "description": "Recognizer for selecting between cross trained recognizers.", "type": "object", "required": [ @@ -2162,7 +2278,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -2201,13 +2317,16 @@ } }, "Microsoft.CurrencyEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Currency Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Currency entity recognizer", "description": "Recognizer which recognizes currency.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2235,13 +2354,16 @@ } }, "Microsoft.DateTimeEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "DateTime Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Date and time entity recognizer", "description": "Recognizer which recognizes dates and time fragments.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2284,7 +2406,7 @@ }, "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2300,7 +2422,7 @@ "defaultValue": { "$ref": "#/definitions/stringExpression", "format": "date-time", - "title": "Default Date", + "title": "Default date", "description": "'Property' will be set to the value or the result of the expression when max turn count is exceeded.", "examples": [ "=user.birthday" @@ -2449,7 +2571,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2500,7 +2622,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -2556,7 +2678,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -2605,7 +2727,7 @@ }, "Microsoft.DeleteProperty": { "$role": "implements(Microsoft.IDialog)", - "title": "Delete Property", + "title": "Delete property", "description": "Delete a property and any value it holds.", "type": "object", "required": [ @@ -2614,7 +2736,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -2657,13 +2779,16 @@ } }, "Microsoft.DimensionEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Dimension Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Dimension entity recognizer", "description": "Recognizer which recognizes dimension.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2702,7 +2827,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -2780,7 +2905,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -2801,7 +2926,7 @@ "oneOf": [ { "type": "string", - "title": "Enum", + "title": "Change type", "description": "Standard change type.", "enum": [ "push", @@ -2831,7 +2956,7 @@ }, "resultProperty": { "$ref": "#/definitions/stringExpression", - "title": "Result Property", + "title": "Result property", "description": "Property to store the result of this action." }, "value": { @@ -2859,13 +2984,16 @@ } }, "Microsoft.EmailEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Email Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Email entity recognizer", "description": "Recognizer which recognizes email.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -2903,7 +3031,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -2992,7 +3120,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -3048,7 +3176,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -3090,12 +3218,12 @@ }, "Microsoft.FirstSelector": { "$role": "implements(Microsoft.ITriggerSelector)", - "title": "First Trigger Selector", + "title": "First trigger selector", "description": "Selector for first true rule", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -3134,7 +3262,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -3212,7 +3340,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -3286,12 +3414,12 @@ }, "Microsoft.GetActivityMembers": { "$role": "implements(Microsoft.IDialog)", - "title": "Get Activity Members", + "title": "Get activity members", "description": "Get the members who are participating in an activity. (BotFrameworkAdapter only)", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -3319,7 +3447,7 @@ }, "activityId": { "$ref": "#/definitions/stringExpression", - "title": "ActivityId", + "title": "Activity Id", "description": "Activity ID or expression to an activityId to use to get the members. If none is defined then the current activity id will be used.", "examples": [ "$lastActivity" @@ -3349,12 +3477,12 @@ }, "Microsoft.GetConversationMembers": { "$role": "implements(Microsoft.IDialog)", - "title": "Get Converation Members", + "title": "Get conversation members", "description": "Get the members who are participating in an conversation. (BotFrameworkAdapter only)", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -3404,7 +3532,7 @@ }, "Microsoft.GotoAction": { "$role": "implements(Microsoft.IDialog)", - "title": "Go to Action", + "title": "Go to action", "description": "Go to an an action by id.", "type": "object", "required": [ @@ -3413,7 +3541,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -3456,13 +3584,16 @@ } }, "Microsoft.GuidEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Guid Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Guid entity recognizer", "description": "Recognizer which recognizes guids.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -3490,13 +3621,16 @@ } }, "Microsoft.HashtagEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Hashtag Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Hashtag entity recognizer", "description": "Recognizer which recognizes Hashtags.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -3535,7 +3669,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -4176,11 +4310,11 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Declarative", - "version": "4.10.5" + "version": "4.11.0-rc2" } }, "Microsoft.IDialog": { - "title": "Microsoft Dialogs", + "title": "Microsoft dialogs", "description": "Components which derive from Dialog", "$role": "interface", "oneOf": [ @@ -4193,30 +4327,6 @@ { "$ref": "#/definitions/Microsoft.AdaptiveDialog" }, - { - "$ref": "#/definitions/Microsoft.Ask" - }, - { - "$ref": "#/definitions/Microsoft.AttachmentInput" - }, - { - "$ref": "#/definitions/Microsoft.ChoiceInput" - }, - { - "$ref": "#/definitions/Microsoft.ConfirmInput" - }, - { - "$ref": "#/definitions/Microsoft.DateTimeInput" - }, - { - "$ref": "#/definitions/Microsoft.NumberInput" - }, - { - "$ref": "#/definitions/Microsoft.OAuthInput" - }, - { - "$ref": "#/definitions/Microsoft.TextInput" - }, { "$ref": "#/definitions/Microsoft.BeginDialog" }, @@ -4232,6 +4342,9 @@ { "$ref": "#/definitions/Microsoft.CancelDialog" }, + { + "$ref": "#/definitions/Microsoft.ContinueConversationLater" + }, { "$ref": "#/definitions/Microsoft.ContinueLoop" }, @@ -4310,26 +4423,53 @@ { "$ref": "#/definitions/Microsoft.TelemetryTrackEvent" }, + { + "$ref": "#/definitions/Microsoft.ThrowException" + }, { "$ref": "#/definitions/Microsoft.TraceActivity" }, { "$ref": "#/definitions/Microsoft.UpdateActivity" + }, + { + "$ref": "#/definitions/Microsoft.Ask" + }, + { + "$ref": "#/definitions/Microsoft.AttachmentInput" + }, + { + "$ref": "#/definitions/Microsoft.ChoiceInput" + }, + { + "$ref": "#/definitions/Microsoft.ConfirmInput" + }, + { + "$ref": "#/definitions/Microsoft.DateTimeInput" + }, + { + "$ref": "#/definitions/Microsoft.NumberInput" + }, + { + "$ref": "#/definitions/Microsoft.OAuthInput" + }, + { + "$ref": "#/definitions/Microsoft.TextInput" } ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Declarative", - "version": "4.10.5" + "version": "4.11.0-rc2" } }, "Microsoft.IEntityRecognizer": { "$role": "interface", - "title": "Entity Recognizers", + "title": "Entity recognizers", "description": "Components which derive from EntityRecognizer.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "oneOf": [ { @@ -4410,11 +4550,11 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" } }, "Microsoft.IRecognizer": { - "title": "Microsoft Recognizer", + "title": "Microsoft recognizer", "description": "Components which derive from Recognizer class", "$role": "interface", "oneOf": [ @@ -4438,11 +4578,68 @@ }, { "$ref": "#/definitions/Microsoft.RegexRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.AgeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.ChannelMentionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.ConfirmationEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.CurrencyEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DateTimeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DimensionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.EmailEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.GuidEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.HashtagEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.IpEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.MentionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberRangeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.OrdinalEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PercentageEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PhoneNumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.RegexEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.TemperatureEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.UrlEntityRecognizer" } ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Declarative", - "version": "4.10.5" + "version": "4.11.0-rc2" } }, "Microsoft.ITextTemplate": { @@ -4459,7 +4656,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Declarative", - "version": "4.10.5" + "version": "4.11.0-rc2" } }, "Microsoft.ITrigger": { @@ -4468,7 +4665,7 @@ "description": "Components which derive from OnCondition class.", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "oneOf": [ { @@ -4524,6 +4721,9 @@ { "$ref": "#/definitions/Microsoft.OnHandoffActivity" }, + { + "$ref": "#/definitions/Microsoft.OnInstallationUpdateActivity" + }, { "$ref": "#/definitions/Microsoft.OnIntent" }, @@ -4562,7 +4762,7 @@ "description": "Components which derive from TriggerSelector class.", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "oneOf": [ { @@ -4599,7 +4799,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -4667,7 +4867,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -4800,13 +5000,16 @@ } }, "Microsoft.IpEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Ip Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "IP entity recognizer", "description": "Recognizer which recognizes internet IP patterns (like 192.1.1.1).", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -4834,12 +5037,12 @@ } }, "Microsoft.LanguagePolicy": { - "title": "Language Policy", + "title": "Language policy", "description": "This represents a policy map for locales lookups to use for language", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -4886,7 +5089,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -4922,7 +5125,7 @@ }, "traceActivity": { "$ref": "#/definitions/booleanExpression", - "title": "Send Trace Activity", + "title": "Send trace activity", "description": "If true, automatically sends a TraceActivity (view in Bot Framework Emulator)." }, "$kind": { @@ -4952,7 +5155,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.AI.Luis", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -4969,12 +5172,17 @@ }, "applicationId": { "$ref": "#/definitions/stringExpression", - "title": "LUIS Application ID", + "title": "LUIS application id", "description": "Application ID for your model from the LUIS service." }, + "version": { + "$ref": "#/definitions/stringExpression", + "title": "LUIS version", + "description": "Optional version to target. If null then predictionOptions.Slot is used." + }, "endpoint": { "$ref": "#/definitions/stringExpression", - "title": "LUIS Endpoint", + "title": "LUIS endpoint", "description": "Endpoint to use for LUIS service like https://westus.api.cognitive.microsoft.com." }, "endpointKey": { @@ -4984,7 +5192,7 @@ }, "externalEntityRecognizer": { "$kind": "Microsoft.IRecognizer", - "title": "External Entity Recognizer", + "title": "External entity recognizer", "description": "Entities recognized by this recognizer will be passed to LUIS as external entities.", "$ref": "#/definitions/Microsoft.IRecognizer" }, @@ -5038,34 +5246,29 @@ "description": "Options to control LUIS prediction behavior.", "properties": { "includeAllIntents": { - "type": "boolean", + "$ref": "#/definitions/booleanExpression", "title": "Include all intents", "description": "True for all intents, false for only top intent." }, "includeInstanceData": { - "type": "boolean", + "$ref": "#/definitions/booleanExpression", "title": "Include $instance", "description": "True to include $instance metadata in the LUIS response." }, "log": { - "type": "boolean", + "$ref": "#/definitions/booleanExpression", "title": "Log utterances", "description": "True to log utterances on LUIS service." }, "preferExternalEntities": { - "type": "boolean", - "title": "Prefer External Entities", + "$ref": "#/definitions/booleanExpression", + "title": "Prefer external entities", "description": "True to prefer external entities to those generated by LUIS models." }, "slot": { - "type": "string", + "$ref": "#/definitions/stringExpression", "title": "Slot", "description": "Slot to use for talking to LUIS service like production or staging." - }, - "version": { - "type": "string", - "title": "Version", - "description": "LUIS application version to use." } } }, @@ -5084,13 +5287,16 @@ } }, "Microsoft.MentionEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Mentions Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Mentions entity recognizer", "description": "Recognizer which recognizes @Mentions", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -5119,12 +5325,12 @@ }, "Microsoft.MostSpecificSelector": { "$role": "implements(Microsoft.ITriggerSelector)", - "title": "Most Specific Trigger Selector", + "title": "Most specific trigger selector", "description": "Select most specific true events with optional additional selector", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -5166,7 +5372,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -5212,13 +5418,16 @@ } }, "Microsoft.NumberEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Number Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Number entity recognizer", "description": "Recognizer which recognizes numbers.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -5255,7 +5464,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -5421,13 +5630,16 @@ } }, "Microsoft.NumberRangeEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "NumberRange Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Number range entity recognizer", "description": "Recognizer which recognizes ranges of numbers (Example:2 to 5).", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -5465,7 +5677,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -5560,7 +5772,7 @@ }, "allowInterruptions": { "$ref": "#/definitions/booleanExpression", - "title": "Allow Interruptions", + "title": "Allow interruptions", "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", "default": true, "examples": [ @@ -5606,7 +5818,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -5676,7 +5888,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "actions", @@ -5760,7 +5972,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "actions", @@ -5829,7 +6041,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "actions", @@ -5898,7 +6110,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "actions", @@ -5981,7 +6193,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6056,7 +6268,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "actions", @@ -6151,7 +6363,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6211,7 +6423,7 @@ "implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)" ], - "title": "On Continue Conversation", + "title": "On continue conversation", "description": "Actions to perform when a conversation is started up again from a ContinueConversationLater action.", "type": "object", "required": [ @@ -6220,7 +6432,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6289,7 +6501,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6359,7 +6571,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6428,7 +6640,7 @@ "description": "Actions to take when there are no more actions in the current dialog.", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "type": "object", "required": [ @@ -6502,7 +6714,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6562,12 +6774,12 @@ "implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)" ], - "title": "On Error", + "title": "On error", "description": "Action to perform when an 'Error' dialog event occurs.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "actions", @@ -6640,7 +6852,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6709,7 +6921,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6764,6 +6976,75 @@ } } }, + "Microsoft.OnInstallationUpdateActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On InstallationUpdate activity", + "description": "Actions to perform on receipt of an activity with type 'InstallationUpdate'.", + "type": "object", + "required": [ + "actions", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.11.0-rc2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/integerExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnInstallationUpdateActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, "Microsoft.OnIntent": { "$role": [ "implements(Microsoft.ITrigger)", @@ -6778,7 +7059,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6862,7 +7143,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -6931,7 +7212,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7000,7 +7281,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7069,7 +7350,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7138,7 +7419,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7198,7 +7479,7 @@ "implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)" ], - "title": "On QnAMaker Match", + "title": "On QnAMaker match", "description": "Actions to perform on when an match from QnAMaker is found.", "type": "object", "required": [ @@ -7207,7 +7488,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7272,7 +7553,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "actions", @@ -7345,7 +7626,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7414,7 +7695,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7470,13 +7751,16 @@ } }, "Microsoft.OrdinalEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Ordinal Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Ordinal entity recognizer", "description": "Recognizer which recognizes ordinals (example: first, second, 3rd).", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -7504,13 +7788,16 @@ } }, "Microsoft.PercentageEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Percentage Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Percentage entity recognizer", "description": "Recognizer which recognizes percentages.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -7538,13 +7825,16 @@ } }, "Microsoft.PhoneNumberEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Phone Number Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Phone number entity recognizer", "description": "Recognizer which recognizes phone numbers.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -7573,7 +7863,7 @@ }, "Microsoft.QnAMakerDialog": { "$role": "implements(Microsoft.IDialog)", - "title": "QnAMaker Dialog", + "title": "QnAMaker dialog", "description": "Dialog which uses QnAMAker knowledge base to answer questions.", "type": "object", "required": [ @@ -7584,7 +7874,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.AI.QnA", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7602,7 +7892,7 @@ }, "endpointKey": { "$ref": "#/definitions/stringExpression", - "title": "Endpoint Key", + "title": "Endpoint key", "description": "Endpoint key for the QnA Maker KB.", "default": "=settings.qna.endpointkey" }, @@ -7649,7 +7939,7 @@ }, "strictFilters": { "$ref": "#/definitions/arrayExpression", - "title": "Strict Filters", + "title": "Strict filters", "description": "Metadata filters to use when calling the QnA Maker KB.", "items": { "type": "object", @@ -7685,7 +7975,7 @@ }, "rankerType": { "$ref": "#/definitions/stringExpression", - "title": "Ranker Type", + "title": "Ranker type", "description": "Type of Ranker.", "oneOf": [ { @@ -7703,6 +7993,25 @@ } ] }, + "strictFiltersJoinOperator": { + "$ref": "#/definitions/stringExpression", + "title": "StrictFiltersJoinOperator", + "description": "Join operator for Strict Filters.", + "oneOf": [ + { + "title": "Join operator", + "description": "Value of Join Operator to be used as conjunction with Strict Filter values.", + "enum": [ + "AND", + "OR" + ], + "default": "AND" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, "$kind": { "title": "Kind of dialog object", "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", @@ -7719,7 +8028,7 @@ }, "Microsoft.QnAMakerRecognizer": { "$role": "implements(Microsoft.IRecognizer)", - "title": "QnAMaker Recognizer", + "title": "QnAMaker recognizer", "description": "Recognizer for generating QnAMatch intents from a KB.", "type": "object", "required": [ @@ -7730,7 +8039,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.AI.QnA", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7748,20 +8057,20 @@ "knowledgeBaseId": { "$ref": "#/definitions/stringExpression", "title": "KnowledgeBase Id", - "description": "KnowledgeBase Id of your QnA Maker KnowledgeBase.", - "default": "settings.qna.knowledgebaseid" + "description": "Knowledge base Id of your QnA Maker knowledge base.", + "default": "=settings.qna.knowledgebaseid" }, "endpointKey": { "$ref": "#/definitions/stringExpression", - "title": "Endpoint Key", + "title": "Endpoint key", "description": "Endpoint key for the QnA Maker KB.", - "default": "settings.qna.endpointkey" + "default": "=settings.qna.endpointkey" }, "hostname": { "$ref": "#/definitions/stringExpression", "title": "Hostname", "description": "Hostname for your QnA Maker service.", - "default": "settings.qna.hostname", + "default": "=settings.qna.hostname", "examples": [ "https://yourserver.azurewebsites.net/qnamaker" ] @@ -7774,7 +8083,7 @@ }, "strictFilters": { "$ref": "#/definitions/arrayExpression", - "title": "Strict Filters", + "title": "Strict filters", "description": "Metadata filters to use when calling the QnA Maker KB.", "items": { "type": "object", @@ -7804,7 +8113,7 @@ }, "isTest": { "$ref": "#/definitions/booleanExpression", - "title": "IsTest", + "title": "Use test environment", "description": "True, if pointing to Test environment, else false.", "examples": [ true, @@ -7812,7 +8121,7 @@ ] }, "rankerType": { - "title": "Ranker Type", + "title": "Ranker type", "description": "Type of Ranker.", "oneOf": [ { @@ -7831,9 +8140,28 @@ } ] }, + "strictFiltersJoinOperator": { + "$ref": "#/definitions/stringExpression", + "title": "StrictFiltersJoinOperator", + "description": "Join operator for Strict Filters.", + "oneOf": [ + { + "title": "Join operator", + "description": "Value of Join Operator to be used as onjuction with Strict Filter values.", + "enum": [ + "AND", + "OR" + ], + "default": "AND" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, "includeDialogNameInMetadata": { "$ref": "#/definitions/booleanExpression", - "title": "Include Dialog Name", + "title": "Include dialog name", "description": "When set to false, the dialog name will not be passed to QnAMaker. (default) is true", "default": true, "examples": [ @@ -7865,12 +8193,12 @@ }, "context": { "$ref": "#/definitions/objectExpression", - "title": "QnARequestContext", + "title": "QnA request context", "description": "Context to use for ranking." }, "qnaId": { "$ref": "#/definitions/integerExpression", - "title": "QnAId", + "title": "QnA Id", "description": "A number or expression which is the QnAId to paass to QnAMaker API." }, "$kind": { @@ -7894,7 +8222,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -7928,7 +8256,7 @@ }, "Microsoft.RecognizerSet": { "$role": "implements(Microsoft.IRecognizer)", - "title": "Recognizer Set", + "title": "Recognizer set", "description": "Creates the union of the intents and entities of the recognizers in the set.", "type": "object", "required": [ @@ -7937,7 +8265,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -7976,8 +8304,11 @@ } }, "Microsoft.RegexEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Regex Entity Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Regex entity recognizer", "description": "Recognizer which recognizes patterns of input based on regex.", "type": "object", "required": [ @@ -7987,7 +8318,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -8028,7 +8359,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -8098,7 +8429,7 @@ "description": "Repeat current dialog.", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -8144,7 +8475,7 @@ }, "activityProcessed": { "$ref": "#/definitions/booleanExpression", - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the dialog that is called can process the current activity.", "default": true }, @@ -8169,7 +8500,7 @@ "description": "Replace current dialog with another dialog.", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -8225,7 +8556,7 @@ }, "activityProcessed": { "$ref": "#/definitions/booleanExpression", - "title": "Activity Processed", + "title": "Activity processed", "description": "When set to false, the dialog that is called can process the current activity.", "default": true }, @@ -8245,12 +8576,12 @@ }, "Microsoft.ResourceMultiLanguageGenerator": { "$role": "implements(Microsoft.ILanguageGenerator)", - "title": "Resource Multi-Language Generator", + "title": "Resource multi-language generator", "description": "MultiLanguage Generator which is bound to resource by resource Id.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -8276,7 +8607,7 @@ }, "languagePolicy": { "type": "object", - "title": "Language Policy", + "title": "Language policy", "description": "Set alternate language policy for this generator. If not set, the global language policy will be used." }, "$kind": { @@ -8300,7 +8631,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -8319,7 +8650,7 @@ "description": "Optional id for the dialog" }, "disabled": { - "$ref": "#/definitions/stringExpression", + "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", "examples": [ @@ -8357,7 +8688,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -8437,7 +8768,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -8495,12 +8826,12 @@ }, "Microsoft.SignOutUser": { "$role": "implements(Microsoft.IDialog)", - "title": "Sign Out User", + "title": "Sign out user", "description": "Sign a user out that was logged in previously using OAuthInput.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -8526,7 +8857,7 @@ }, "connectionName": { "$ref": "#/definitions/stringExpression", - "title": "Connection Name", + "title": "Connection name", "description": "Connection name that was used with OAuthInput to log a user in." }, "disabled": { @@ -8554,7 +8885,7 @@ }, "Microsoft.StaticActivityTemplate": { "$role": "implements(Microsoft.IActivityTemplate)", - "title": "Microsoft Static Activity Template", + "title": "Microsoft static activity template", "description": "This allows you to define a static Activity object", "type": "object", "required": [ @@ -8563,7 +8894,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -8606,7 +8937,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -8704,7 +9035,7 @@ "Microsoft.TelemetryTrackEvent": { "$role": "implements(Microsoft.IDialog)", "type": "object", - "title": "Telemetry - Track Event", + "title": "Telemetry - track event", "description": "Track a custom event using the registered Telemetry Client.", "required": [ "url", @@ -8713,7 +9044,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -8738,7 +9069,7 @@ }, "eventName": { "$ref": "#/definitions/stringExpression", - "title": "Event Name", + "title": "Event name", "description": "The name of the event to track.", "examples": [ "MyEventStarted", @@ -8768,13 +9099,16 @@ } }, "Microsoft.TemperatureEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Temperature Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Temperature recognizer", "description": "Recognizer which recognizes temperatures.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -8803,12 +9137,12 @@ }, "Microsoft.TemplateEngineLanguageGenerator": { "$role": "implements(Microsoft.ILanguageGenerator)", - "title": "Template Multi-Language Generator", + "title": "Template multi-language generator", "description": "Template Generator which allows only inline evaluation of templates.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -8850,7 +9184,7 @@ "description": "Collection information - Ask for a word or sentence.", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -9022,7 +9356,7 @@ ], "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "additionalProperties": false, "patternProperties": { @@ -9051,6 +9385,59 @@ } } }, + "Microsoft.ThrowException": { + "$role": "implements(Microsoft.IDialog)", + "title": "Throw an exception", + "description": "Throw an exception. Capture this exception with OnError trigger.", + "type": "object", + "required": [ + "errorValue", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.11.0-rc2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "errorValue": { + "$ref": "#/definitions/valueExpression", + "title": "Error value", + "description": "Error value to throw." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ThrowException" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, "Microsoft.TraceActivity": { "$role": "implements(Microsoft.IDialog)", "title": "Send a TraceActivity", @@ -9058,7 +9445,7 @@ "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -9121,12 +9508,12 @@ }, "Microsoft.TrueSelector": { "$role": "implements(Microsoft.ITriggerSelector)", - "title": "True Trigger Selector", + "title": "True trigger selector", "description": "Selector for all true events", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -9155,12 +9542,12 @@ }, "Microsoft.UpdateActivity": { "$role": "implements(Microsoft.IDialog)", - "title": "Send an activity", + "title": "Update an activity", "description": "Respond with an activity.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" @@ -9216,13 +9603,16 @@ } }, "Microsoft.UrlEntityRecognizer": { - "$role": "implements(Microsoft.IEntityRecognizer)", - "title": "Confirmation Url Recognizer", + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Url recognizer", "description": "Recognizer which recognizes urls.", "type": "object", "$package": { "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", - "version": "4.10.5" + "version": "4.11.0-rc2" }, "required": [ "$kind" diff --git a/runtime/dotnet/azurewebapp/Schemas/sdk.uischema b/runtime/dotnet/azurewebapp/Schemas/sdk.uischema index a930484e91..3a96f1fd4a 100644 --- a/runtime/dotnet/azurewebapp/Schemas/sdk.uischema +++ b/runtime/dotnet/azurewebapp/Schemas/sdk.uischema @@ -23,6 +23,112 @@ } } }, + "Microsoft.Ask": { + "form": { + "helpLink": "https://aka.ms/bfc-send-activity", + "label": "Send a response to ask a question", + "order": [ + "activity", + "*" + ], + "subtitle": "Ask Activity" + } + }, + "Microsoft.AttachmentInput": { + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for a file or an attachment", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Attachment Input" + } + }, + "Microsoft.ChoiceInput": { + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt with multi-choice", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Choice Input" + } + }, + "Microsoft.ConfirmInput": { + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for confirmation", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Confirm Input" + } + }, + "Microsoft.DateTimeInput": { + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for a date or a time", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Date Time Input" + } + }, + "Microsoft.NumberInput": { + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for a number", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Number Input" + } + }, + "Microsoft.OAuthInput": { + "form": { + "helpLink": "https://aka.ms/bfc-using-oauth", + "label": "OAuth login", + "order": [ + "connectionName", + "*" + ], + "subtitle": "OAuth Input" + } + }, + "Microsoft.TextInput": { + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for text", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Text Input" + } + }, "Microsoft.BeginDialog": { "form": { "helpLink": "https://aka.ms/bfc-understanding-dialogs", @@ -252,7 +358,7 @@ }, "Microsoft.LogAction": { "form": { - "helpLink": "https://aka.ms/bfc-debugging-bots", + "helpLink": "https://aka.ms/composer-telemetry", "label": "Log to console", "subtitle": "Log Action" } @@ -351,106 +457,17 @@ "subtitle": "Switch Condition" } }, - "Microsoft.TraceActivity": { + "Microsoft.ThrowException": { "form": { - "helpLink": "https://aka.ms/bfc-debugging-bots", - "label": "Emit a trace event", - "subtitle": "Trace Activity" + "label": "Throw an exception", + "subtitle": "Throw an exception" } }, - "Microsoft.AttachmentInput": { - "form": { - "helpLink": "https://aka.ms/bfc-ask-for-user-input", - "label": "Prompt for a file or an attachment", - "properties": { - "property": { - "intellisenseScopes": [ - "variable-scopes" - ] - } - }, - "subtitle": "Attachment Input" - } - }, - "Microsoft.ChoiceInput": { - "form": { - "helpLink": "https://aka.ms/bfc-ask-for-user-input", - "label": "Prompt with multi-choice", - "properties": { - "property": { - "intellisenseScopes": [ - "variable-scopes" - ] - } - }, - "subtitle": "Choice Input" - } - }, - "Microsoft.ConfirmInput": { - "form": { - "helpLink": "https://aka.ms/bfc-ask-for-user-input", - "label": "Prompt for confirmation", - "properties": { - "property": { - "intellisenseScopes": [ - "variable-scopes" - ] - } - }, - "subtitle": "Confirm Input" - } - }, - "Microsoft.DateTimeInput": { - "form": { - "helpLink": "https://aka.ms/bfc-ask-for-user-input", - "label": "Prompt for a date or a time", - "properties": { - "property": { - "intellisenseScopes": [ - "variable-scopes" - ] - } - }, - "subtitle": "Date Time Input" - } - }, - "Microsoft.NumberInput": { - "form": { - "helpLink": "https://aka.ms/bfc-ask-for-user-input", - "label": "Prompt for a number", - "properties": { - "property": { - "intellisenseScopes": [ - "variable-scopes" - ] - } - }, - "subtitle": "Number Input" - } - }, - "Microsoft.OAuthInput": { - "form": { - "helpLink": "https://aka.ms/bfc-using-oauth", - "label": "OAuth login", - "order": [ - "connectionName", - "*" - ], - "subtitle": "OAuth Input" - } - }, - "Microsoft.TextInput": { + "Microsoft.TraceActivity": { "form": { - "helpLink": "https://aka.ms/bfc-ask-for-user-input", - "label": "Prompt for text", - "properties": { - "property": { - "intellisenseScopes": [ - "variable-scopes" - ] - } - }, - "subtitle": "Text Input" + "helpLink": "https://aka.ms/composer-telemetry", + "label": "Emit a trace event", + "subtitle": "Trace Activity" } }, "Microsoft.RegexRecognizer": { @@ -473,6 +490,19 @@ "subtitle": "Activity received" } }, + "Microsoft.OnAssignEntity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Handle a condition when an entity is assigned", + "order": [ + "condition", + "*" + ], + "subtitle": "EntityAssigned activity" + } + }, "Microsoft.OnBeginDialog": { "form": { "hidden": [ @@ -499,6 +529,17 @@ "subtitle": "Cancel dialog event" } }, + "Microsoft.OnChooseIntent": { + "form": { + "hidden": [ + "actions" + ], + "order": [ + "condition", + "*" + ] + } + }, "Microsoft.OnCondition": { "form": { "hidden": [ @@ -540,6 +581,19 @@ "subtitle": "Dialog event" } }, + "Microsoft.OnEndOfActions": { + "form": { + "hidden": [ + "actions" + ], + "label": "Handle a condition when actions have ended", + "order": [ + "condition", + "*" + ], + "subtitle": "EndOfActions activity" + } + }, "Microsoft.OnEndOfConversationActivity": { "form": { "hidden": [ diff --git a/runtime/dotnet/azurewebapp/Startup.cs b/runtime/dotnet/azurewebapp/Startup.cs index c7a52195cc..7cfb1bddb8 100644 --- a/runtime/dotnet/azurewebapp/Startup.cs +++ b/runtime/dotnet/azurewebapp/Startup.cs @@ -13,6 +13,7 @@ using Microsoft.Bot.Builder.AI.QnA; using Microsoft.Bot.Builder.ApplicationInsights; using Microsoft.Bot.Builder.Azure; +using Microsoft.Bot.Builder.Azure.Blobs; using Microsoft.Bot.Builder.BotFramework; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Builder.Dialogs.Adaptive; @@ -26,9 +27,9 @@ using Microsoft.Bot.Connector.Authentication; using Microsoft.BotFramework.Composer.Core; using Microsoft.BotFramework.Composer.Core.Settings; -using Microsoft.BotFramework.Composer.WebAppTemplates.Authorization; //using Microsoft.BotFramework.Composer.CustomAction; +using Microsoft.BotFramework.Composer.WebAppTemplates.Authorization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -50,7 +51,7 @@ public void ConfigureTranscriptLoggerMiddleware(BotFrameworkHttpAdapter adapter, { if (ConfigSectionValid(settings?.BlobStorage?.ConnectionString) && ConfigSectionValid(settings?.BlobStorage?.Container)) { - adapter.Use(new TranscriptLoggerMiddleware(new AzureBlobTranscriptStore(settings?.BlobStorage?.ConnectionString, settings?.BlobStorage?.Container))); + adapter.Use(new TranscriptLoggerMiddleware(new BlobsTranscriptStore(settings?.BlobStorage?.ConnectionString, settings?.BlobStorage?.Container))); } } @@ -130,14 +131,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(sp => (BotFrameworkHttpAdapter)sp.GetService()); // Register AuthConfiguration to enable custom claim validation for skills. - if (IsSkill(settings)) - { - services.AddSingleton(sp => new AuthenticationConfiguration { ClaimsValidator = new AllowedCallersClaimsValidator(settings.SkillConfiguration) }); - } - else - { - services.AddSingleton(sp => new AuthenticationConfiguration()); - } + services.AddSingleton(sp => new AuthenticationConfiguration { ClaimsValidator = new AllowedCallersClaimsValidator(settings.SkillConfiguration) }); // register components. ComponentRegistration.Add(new DialogsComponentRegistration()); diff --git a/runtime/dotnet/core/Microsoft.BotFramework.Composer.Core.csproj b/runtime/dotnet/core/Microsoft.BotFramework.Composer.Core.csproj index 397bc4a18a..7e6a15c899 100644 --- a/runtime/dotnet/core/Microsoft.BotFramework.Composer.Core.csproj +++ b/runtime/dotnet/core/Microsoft.BotFramework.Composer.Core.csproj @@ -13,18 +13,19 @@ - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/runtime/dotnet/customaction/Microsoft.BotFramework.Composer.CustomAction.csproj b/runtime/dotnet/customaction/Microsoft.BotFramework.Composer.CustomAction.csproj index da040a1bb4..1911ef5ffe 100644 --- a/runtime/dotnet/customaction/Microsoft.BotFramework.Composer.CustomAction.csproj +++ b/runtime/dotnet/customaction/Microsoft.BotFramework.Composer.CustomAction.csproj @@ -11,7 +11,7 @@ - + diff --git a/runtime/dotnet/tests/ActionsTests.cs b/runtime/dotnet/tests/ActionsTests.cs index 5f47f30508..c75542a179 100644 --- a/runtime/dotnet/tests/ActionsTests.cs +++ b/runtime/dotnet/tests/ActionsTests.cs @@ -195,6 +195,9 @@ await BuildTestFlow(getFolderPath("ActionsSample")) .AssertReply("Why did the chicken cross the road?") .Send("Why?") .AssertReply("To get to the other side!") + .Send("future") + .AssertReply(String.Format("I can show you examples on how to use actions. Enter the number next to the entity that you with to see in action.{0}01 - Actions{0}02 - EndTurn{0}03 - IfCondiftion{0}04 - EditArray, Foreach{0}05 - EndDialog{0}06 - HttpRequest{0}07 - SwitchCondition{0}08 - RepeatDialog{0}09 - TraceAndLog{0}10 - EditActions{0}11 - ReplaceDialog{0}12 - EmitEvent{0}13 - QnAMaker", Environment.NewLine)).Send("11") + .AssertReply("Hello luhan, nice to talk to you! Please either enter 'joke' or 'fortune' to replace the dialog you want.") .Send("future") .AssertReply("Seeing into your future...") .AssertReply("I see great things in your future!")