From 1eefb2e596e99af08b292e4b481a5d10840e1a43 Mon Sep 17 00:00:00 2001 From: Ben Brown Date: Wed, 5 May 2021 19:35:00 -0500 Subject: [PATCH] feat: detect "old" bots and migrate them to new runtime (#6526) * introduce a migration system for updating legacy projcts to the 2.0 runtime * updated * ensure root dialog gets updated appropriately * adjust method for updating root dialog * remove comments * fire migration warning when loading bot * use original project name for new project * close modal on submit * address feedback * add confirm modal * Rename azureFunctionsPublish publish targets to azurePublish (new shared id) * Delete a.en-us.lu * Delete a.en-us.qna * Delete b.en-us.lu * Delete b.en-us.qna * Delete bot1.en-us.lu * Delete bot1.en-us.qna * do not require pva to migrate * Add missing parameters * clarify types * Fix tests reduce redundant code * Fixes #6844: migrate appinsights key to new location * fix migrate lint issue * plumb through the yeomanOptions parameter, required to set the location of the settings folder when migrating * feat: new validation pipeline - schema existence validation (#7001) * add placeholder for schema validator * add schema validator pipeline with mocked fn * add schema visitor * display diagnostics data in debug panel * revert sdk.ts * decrease schema diagnostic severity to 'Warning' * optmize path join logic * impl a unified walker covers SwitchCondition * fix lint error: use BaseSchema * feat: disable actions without schema * wrap in useEffect * optimization: avoid frequent recoil submission * optimization: aggregate paths rather than updatedDialog to reduce time complexity * chore: comments & var name * lint * add comments * defense undefined skip-level 'actions' * defense potential exceptions * get sdk.schema content correctly * fix lint * fix folder name case problem * Do not specify the luis endpoint key as a parameter to the runtime if no vlaue is present (#7240) (leaving this paramter blank causes issues on windows) * disable telemetry calls in the provision dialog while we investigate why telemetryclient is null (#7256) Co-authored-by: Geoff Cox (Microsoft) * prefer the botName field instead of the name field when managing connections (#7262) * fix: Empty Webchat inspector text and Disabling items in PVA context (#7241) * show floating notifications over eveything (#7269) Co-authored-by: Soroush * Region for Microsoft Bot Channels Registration is now global (#7270) Co-authored-by: Ben Brown Co-authored-by: Soroush * fix: adjust package manager feeds (#7243) * Fix #7092: set default page size to 100 items * Fixes #6854: merge community feeds into main feed, sort by downloads * Fixes #7043: include any component tagged msbot-component * improve error handling * restore different checks for declarative only vs code driven components * refactor to use includes instead of indexOf Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com> * fix: remodel About page (#7191) * remodel About page * add SHA to version * fixes from suggestion * get info from Electron and use it for Release field Co-authored-by: Chris Whitten Co-authored-by: Chris Whitten Co-authored-by: Ben Brown Co-authored-by: Geoff Cox (Microsoft) Co-authored-by: Srinaath Ravichandran Co-authored-by: Soroush Co-authored-by: Soroush Co-authored-by: Vamsi Modem <12182973+VamsiModem@users.noreply.github.com> Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com> * fix mapping of schema files during migration * fix lint * fix default name of migrated project * run schema merge as part of the migration * do we need to migrate teh schema? or not? * do not migrate schema * - Adding field for runtime language - Fixing field population for runtime type * Passing runtime lang and type down to yeoman calls * Fixing adaptive runtime name * Hardcoding runtime version and fixing settings page generation on migrate * Fixing check for 'inBotMigration' to be based on CreationStatusState > path substring * Fix default naming * revert preload.js * Resolving PR comments and fixing errors from merge * Change schema diagnostics severity to 'Error' * Fetch @latest version from npm before migration Co-authored-by: Dong Lei Co-authored-by: leilzh Co-authored-by: zhixzhan Co-authored-by: zeye Co-authored-by: Chris Whitten Co-authored-by: Geoff Cox (Microsoft) Co-authored-by: Srinaath Ravichandran Co-authored-by: Soroush Co-authored-by: Soroush Co-authored-by: Vamsi Modem <12182973+VamsiModem@users.noreply.github.com> Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com> Co-authored-by: Patrick Volum Co-authored-by: Chris Whitten --- .../CreationFlow/v2/CreationFlow.tsx | 31 +- .../CreationFlow/v2/DefineConversation.tsx | 59 +- Composer/packages/client/src/constants.tsx | 1 + .../DiagnosticsTab/DiagnosticsTabHeader.tsx | 2 + .../DiagnosticsTab/useAutoFix.ts | 79 + .../client/src/pages/diagnostics/types.ts | 5 + .../__tests__/mocks/mockProjectResponse.json | 1783 ++++------------- .../src/recoilModel/dispatchers/project.ts | 85 +- .../recoilModel/dispatchers/utils/project.ts | 65 +- .../selectors/diagnosticsPageSelector.ts | 34 +- .../schemaValidation/__mocks__/dialogMocks.ts | 50 + .../schemaValidation/__mocks__/sdkSchema.ts | 20 + .../__mocks__/sdkSchemaMocks.ts | 269 +++ .../schemaValidation/schemaUtils.test.ts | 48 + .../walkAdaptiveDialog.test.ts | 28 + .../lib/indexers/src/validations/index.ts | 2 + .../src/validations/schemaValidation/index.ts | 25 + .../schemaValidation/schemaUtils.ts | 121 ++ .../schemaValidation/walkAdaptiveDialog.ts | 84 + .../workers/templateInstallation.worker.ts | 22 +- .../packages/server/src/controllers/asset.ts | 12 + .../server/src/controllers/project.ts | 9 + .../packages/server/src/locales/en-US.json | 18 + .../server/src/models/asset/assetManager.ts | 2 + .../server/src/models/bot/botProject.ts | 20 + .../server/src/models/bot/botStructure.ts | 19 +- Composer/packages/server/src/router/api.ts | 1 + .../packages/server/src/services/project.ts | 165 +- 28 files changed, 1625 insertions(+), 1434 deletions(-) create mode 100644 Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/DiagnosticsTab/useAutoFix.ts create mode 100644 Composer/packages/lib/indexers/__tests__/validations/schemaValidation/__mocks__/dialogMocks.ts create mode 100644 Composer/packages/lib/indexers/__tests__/validations/schemaValidation/__mocks__/sdkSchema.ts create mode 100644 Composer/packages/lib/indexers/__tests__/validations/schemaValidation/__mocks__/sdkSchemaMocks.ts create mode 100644 Composer/packages/lib/indexers/__tests__/validations/schemaValidation/schemaUtils.test.ts create mode 100644 Composer/packages/lib/indexers/__tests__/validations/schemaValidation/walkAdaptiveDialog.test.ts create mode 100644 Composer/packages/lib/indexers/src/validations/schemaValidation/index.ts create mode 100644 Composer/packages/lib/indexers/src/validations/schemaValidation/schemaUtils.ts create mode 100644 Composer/packages/lib/indexers/src/validations/schemaValidation/walkAdaptiveDialog.ts diff --git a/Composer/packages/client/src/components/CreationFlow/v2/CreationFlow.tsx b/Composer/packages/client/src/components/CreationFlow/v2/CreationFlow.tsx index 93d20d6a32..5e09f80725 100644 --- a/Composer/packages/client/src/components/CreationFlow/v2/CreationFlow.tsx +++ b/Composer/packages/client/src/components/CreationFlow/v2/CreationFlow.tsx @@ -18,6 +18,7 @@ import { userSettingsState, templateProjectsState, } from '../../../recoilModel'; +import { localBotsDataSelector } from '../../../recoilModel/selectors/project'; import Home from '../../../pages/home/Home'; import { useProjectIdCache } from '../../../utils/hooks'; import { ImportModal } from '../../ImportModal/ImportModal'; @@ -43,6 +44,7 @@ const CreationFlowV2: React.FC = () => { fetchFeed, openProject, saveProjectAs, + migrateProjectTo, fetchProjectById, createNewBotV2, fetchReadMe, @@ -52,6 +54,8 @@ const CreationFlowV2: React.FC = () => { const creationFlowStatus = useRecoilValue(creationFlowStatusState); const projectId = useRecoilValue(currentProjectIdState); const storages = useRecoilValue(storagesState); + const botProjects = useRecoilValue(localBotsDataSelector); + const botProject = botProjects.find((b) => b.projectId === projectId); const focusedStorageFolder = useRecoilValue(focusedStorageFolderState); const { appLocale } = useRecoilValue(userSettingsState); const cachedProjectId = useProjectIdCache(); @@ -150,13 +154,28 @@ const CreationFlowV2: React.FC = () => { saveProjectAs(projectId, formData.name, formData.description, formData.location); }; + const handleMigrate = (formData) => { + handleDismiss(); + setCreationFlowStatus(CreationFlowStatus.MIGRATE); + migrateProjectTo( + projectId, + formData.name, + formData.description, + formData.location, + formData.runtimeLanguage, + formData.runtimeType + ); + }; + const handleSubmit = async (formData, templateId: string) => { handleDismiss(); switch (creationFlowStatus) { case CreationFlowStatus.SAVEAS: handleSaveAs(formData); break; - + case CreationFlowStatus.MIGRATE: + handleMigrate(formData); + break; default: saveTemplateId(templateId); await handleCreateNew(formData, templateId); @@ -226,6 +245,16 @@ const CreationFlowV2: React.FC = () => { onDismiss={handleDismiss} onOpen={openBot} /> + diff --git a/Composer/packages/client/src/components/CreationFlow/v2/DefineConversation.tsx b/Composer/packages/client/src/components/CreationFlow/v2/DefineConversation.tsx index f0ed5373bc..cb18f2ca36 100644 --- a/Composer/packages/client/src/components/CreationFlow/v2/DefineConversation.tsx +++ b/Composer/packages/client/src/components/CreationFlow/v2/DefineConversation.tsx @@ -21,12 +21,12 @@ import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; import camelCase from 'lodash/camelCase'; import upperFirst from 'lodash/upperFirst'; -import { DialogCreationCopy, nameRegexV2, nameRegex } from '../../../constants'; +import { CreationFlowStatus, DialogCreationCopy, nameRegex, nameRegexV2 } from '../../../constants'; import { FieldConfig, useForm } from '../../../hooks/useForm'; import { StorageFolder } from '../../../recoilModel/types'; import { createNotification } from '../../../recoilModel/dispatchers/notification'; import { ImportSuccessNotificationWrapper } from '../../ImportModal/ImportSuccessNotification'; -import { dispatcherState, templateProjectsState } from '../../../recoilModel'; +import { creationFlowStatusState, dispatcherState, templateProjectsState } from '../../../recoilModel'; import { LocationSelectContent } from '../LocationSelectContent'; import { getAliasFromPayload, Profile } from '../../../utils/electronUtil'; import TelemetryClient from '../../../telemetry/TelemetryClient'; @@ -100,6 +100,7 @@ type DefineConversationProps = { focusedStorageFolder: StorageFolder; } & RouteComponentProps<{ templateId: string; + defaultName: string; runtimeLanguage: string; location: string; }>; @@ -118,12 +119,16 @@ const DefineConversationV2: React.FC = (props) => { const writable = focusedStorageFolder.writable; const runtimeLanguage = props.runtimeLanguage ? props.runtimeLanguage : csharpFeedKey; const templateProjects = useRecoilValue(templateProjectsState); + const creationFlowStatus = useRecoilValue(creationFlowStatusState); + const currentTemplate = templateProjects.find((t) => { if (t?.id) { return t.id === templateId; } }); + const inBotMigration = creationFlowStatus === CreationFlowStatus.MIGRATE; + // template ID is populated by npm package name which needs to be formatted const normalizeTemplateId = () => { if (currentTemplate) { @@ -135,6 +140,10 @@ const DefineConversationV2: React.FC = (props) => { .replace(/-/g, ' ') ); return upperFirst(camelCasedName); + } else if (templateId && inBotMigration) { + return templateId.trim().replace(/[-\s]/g, '_').toLocaleLowerCase(); + } else { + return templateId; } }; @@ -311,8 +320,10 @@ const DefineConversationV2: React.FC = (props) => { const getSupportedRuntimesForTemplate = (): IDropdownOption[] => { const result: IDropdownOption[] = []; - - if (currentTemplate) { + if (inBotMigration) { + result.push({ key: webAppRuntimeKey, text: formatMessage('Azure Web App') }); + result.push({ key: functionsRuntimeKey, text: formatMessage('Azure Functions') }); + } else if (currentTemplate) { if (runtimeLanguage === csharpFeedKey) { currentTemplate.dotnetSupport?.functionsSupported && result.push({ key: functionsRuntimeKey, text: formatMessage('Azure Functions') }); @@ -325,9 +336,17 @@ const DefineConversationV2: React.FC = (props) => { result.push({ key: webAppRuntimeKey, text: formatMessage('Azure Web App') }); } } + return result; }; + const getRuntimeLanguageOptions = (): IDropdownOption[] => { + return [ + { key: csharpFeedKey, text: 'C#' }, + { key: nodeFeedKey, text: formatMessage('Node (Preview)') }, + ]; + }; + useEffect(() => { const location = focusedStorageFolder !== null && Object.keys(focusedStorageFolder as Record).length @@ -368,14 +387,30 @@ const DefineConversationV2: React.FC = (props) => { {!isImported && ( - updateField('runtimeType', option?.key.toString())} - /> + + + updateField('runtimeType', option?.key.toString())} + /> + + {inBotMigration && ( + + updateField('runtimeLanguage', option?.key.toString())} + /> + + )} + )} diff --git a/Composer/packages/client/src/constants.tsx b/Composer/packages/client/src/constants.tsx index 436a8ba723..9aa333d208 100644 --- a/Composer/packages/client/src/constants.tsx +++ b/Composer/packages/client/src/constants.tsx @@ -149,6 +149,7 @@ export enum CreationFlowStatus { NEW_FROM_SCRATCH = 'Scratch', NEW_FROM_TEMPLATE = 'Template', SAVEAS = 'Save as', + MIGRATE = 'Migrate', OPEN = 'Open', CLOSE = 'Close', NEW_SKILL = 'New Skill', diff --git a/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/DiagnosticsTab/DiagnosticsTabHeader.tsx b/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/DiagnosticsTab/DiagnosticsTabHeader.tsx index 3415f3d786..e75c51ed1e 100644 --- a/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/DiagnosticsTab/DiagnosticsTabHeader.tsx +++ b/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/DiagnosticsTab/DiagnosticsTabHeader.tsx @@ -5,10 +5,12 @@ import { jsx, css } from '@emotion/core'; import formatMessage from 'format-message'; +import { useAutoFix } from './useAutoFix'; import { useDiagnosticsStatistics } from './useDiagnostics'; export const DiagnosticsHeader = () => { const { hasError, hasWarning } = useDiagnosticsStatistics(); + useAutoFix(); return (
{ + const diagnostics = useDiagnosticsData(); + const botProjectSpace = useRecoilValue(botProjectSpaceSelector); + const { updateDialog } = useRecoilValue(dispatcherState); + + // Auto fix schema absence by setting 'disabled' to true. + useEffect(() => { + const schemaDiagnostics = diagnostics.filter((d) => d.type === DiagnosticType.SCHEMA) as SchemaDiagnostic[]; + /** + * Aggregated diagnostic paths where contains schema problem + * + * Example: + * { + * // projectId + * '2096.637': { + * // dialogId + * 'dialog-1': [ + * 'triggers[0].actions[1]', // diagnostics.dialogPath + * 'triggers[2].actions[3]' + * ] + * } + * } + */ + const aggregatedPaths: { [projectId: string]: { [dialogId: string]: string[] } } = {}; + + // Aggregates schema diagnostics by projectId, dialogId + schemaDiagnostics.forEach((d) => { + const { projectId, id: dialogId, dialogPath } = d; + if (!dialogPath) return; + const currentPaths = get(aggregatedPaths, [projectId, dialogId]); + if (currentPaths) { + currentPaths.push(dialogPath); + } else { + set(aggregatedPaths, [projectId, dialogId], [dialogPath]); + } + }); + + // Consumes aggregatedPaths to update dialogs in recoil store + for (const [projectId, pathsByDialogId] of Object.entries(aggregatedPaths)) { + // Locates dialogs in current project + const dialogsInProject = botProjectSpace.find((bot) => bot.projectId === projectId)?.dialogs; + if (!Array.isArray(dialogsInProject)) continue; + + for (const [dialogId, paths] of Object.entries(pathsByDialogId)) { + // Queries out current dialog data + const dialogData = dialogsInProject.find((dialog) => dialog.id === dialogId)?.content; + if (!dialogData) continue; + + // Filters out those paths where action exists and action.disabled !== true + const pathsToUpdate = paths.filter((p) => { + const data = get(dialogData, p); + return data && !get(data, 'disabled'); + }); + if (!pathsToUpdate.length) continue; + + // Manipulates the 'disabled' property and then submit to Recoil store. + const copy = cloneDeep(dialogData); + for (const p of pathsToUpdate) { + set(copy, `${p}.disabled`, true); + } + updateDialog({ id: dialogId, projectId, content: copy }); + } + } + }, [diagnostics]); +}; diff --git a/Composer/packages/client/src/pages/diagnostics/types.ts b/Composer/packages/client/src/pages/diagnostics/types.ts index 2f615ac6f1..7f1227b949 100644 --- a/Composer/packages/client/src/pages/diagnostics/types.ts +++ b/Composer/packages/client/src/pages/diagnostics/types.ts @@ -17,6 +17,7 @@ export enum DiagnosticType { SKILL, SETTING, GENERAL, + SCHEMA, } export interface IDiagnosticInfo { @@ -94,6 +95,10 @@ export class DialogDiagnostic extends DiagnosticInfo { }; } +export class SchemaDiagnostic extends DialogDiagnostic { + type = DiagnosticType.SCHEMA; +} + export class SkillSettingDiagnostic extends DiagnosticInfo { type = DiagnosticType.SKILL; constructor(rootProjectId: string, projectId: string, id: string, location: string, diagnostic: Diagnostic) { diff --git a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/mocks/mockProjectResponse.json b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/mocks/mockProjectResponse.json index 30132568dc..4843c075c6 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/mocks/mockProjectResponse.json +++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/mocks/mockProjectResponse.json @@ -23,9 +23,10 @@ "path": "/Users/tester/Desktop/EmptyBot-1/dialogs/additem/language-understanding/en-us/additem.en-us.lu", "relativePath": "dialogs/additem/language-understanding/en-us/additem.en-us.lu", "lastModified": "Thu Jul 09 2020 10:19:09 GMT-0700 (Pacific Daylight Time)" - },{ + }, + { "name": "EmptyBot-1.botproj", - "content": "{\"$schema\":\"https:\/\/schemas.botframework.com\/schemas\/botprojects\/v0.1\/botproject-schema.json\",\"name\":\"echobot-0\",\"workspace\":\"\/Users\/tester\/Desktop\/samples\/EchoBot-0\",\"skills\":{}}", + "content": "{\"$schema\":\"https://schemas.botframework.com/schemas/botprojects/v0.1/botproject-schema.json\",\"name\":\"echobot-0\",\"workspace\":\"/Users/tester/Desktop/samples/EchoBot-0\",\"skills\":{}}", "path": "/Users/tester/Desktop/EmptyBot-1/EmptyBot-1.botproj", "relativePath": "dialogs/additem/language-understanding/en-us/additem.en-us.lu", "lastModified": "Thu Jul 09 2020 10:19:09 GMT-0700 (Pacific Daylight Time)" @@ -45,10 +46,7 @@ "$role": "implements(Microsoft.IActivityTemplate)", "title": "Microsoft ActivityTemplate", "type": "object", - "required": [ - "template", - "$kind" - ], + "required": ["template", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -84,18 +82,17 @@ "type": "object", "title": "Component kinds", "description": "These are all of the kinds that can be created by the loader.", - "oneOf": [ { - "$ref": "#/definitions/Microsoft.ActivityTemplate" - }], + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.ActivityTemplate" + } + ], "definitions": { "Microsoft.ActivityTemplate": { "$role": "implements(Microsoft.IActivityTemplate)", "title": "Microsoft ActivityTemplate", "type": "object", - "required": [ - "template", - "$kind" - ], + "required": ["template", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -128,9 +125,7 @@ "title": "Adaptive Dialog", "description": "Flexible, data driven dialog that can adapt to the conversation.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -210,15 +205,7 @@ "default": 0 }, "simpleTypes": { - "enum": [ - "array", - "boolean", - "integer", - "null", - "number", - "object", - "string" - ] + "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] }, "stringArray": { "type": "array", @@ -229,10 +216,7 @@ } } }, - "type": [ - "object", - "boolean" - ], + "type": ["object", "boolean"], "properties": { "$schema": { "type": "string", @@ -450,9 +434,7 @@ "title": "Age Entity Recognizer", "description": "Recognizer which recognizes age.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -476,16 +458,11 @@ } }, "Microsoft.Ask": { - "$role": [ - "implements(Microsoft.IDialog)", - "extends(Microsoft.SendActivity)" - ], + "$role": ["implements(Microsoft.IDialog)", "extends(Microsoft.SendActivity)"], "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", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -499,12 +476,7 @@ "title": "Expected Properties", "description": "Properties expected from the user.", "type": "array", - "examples": [ - [ - "age", - "name" - ] - ], + "examples": [["age", "name"]], "items": { "type": "string", "title": "Name", @@ -515,9 +487,7 @@ "$ref": "#/definitions/stringExpression", "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" - ] + "examples": ["add"] }, "id": { "type": "string", @@ -528,9 +498,7 @@ "$ref": "#/definitions/stringExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "activity": { "$kind": "Microsoft.IActivityTemplate", @@ -553,16 +521,11 @@ } }, "Microsoft.AttachmentInput": { - "$role": [ - "implements(Microsoft.IDialog)", - "extends(Microsoft.InputDialog)" - ], + "$role": ["implements(Microsoft.IDialog)", "extends(Microsoft.InputDialog)"], "title": "Attachment input dialog", "description": "Collect information - Ask for a file or image.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -608,10 +571,7 @@ "type": "string", "title": "Standard format", "description": "Standard output formats.", - "enum": [ - "all", - "first" - ], + "enum": ["all", "first"], "default": "first" }, { @@ -629,18 +589,13 @@ "title": "Disabled", "description": "Optional condition which if true will disable this action.", "default": false, - "examples": [ - false, - "=user.isVip" - ] + "examples": [false, "=user.isVip"] }, "prompt": { "$kind": "Microsoft.IActivityTemplate", "title": "Initial prompt", "description": "Message to send to collect information.", - "examples": [ - "What is your birth date?" - ], + "examples": ["What is your birth date?"], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "unrecognizedPrompt": { @@ -656,9 +611,7 @@ "$kind": "Microsoft.IActivityTemplate", "title": "Invalid prompt", "description": "Message to send when the user input does not meet any validation expression.", - "examples": [ - "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" - ], + "examples": ["Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?"], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "defaultValueResponse": { @@ -675,10 +628,7 @@ "title": "Max turn count", "description": "Maximum number of re-prompt attempts to collect information.", "default": 3, - "examples": [ - 3, - "=settings.xyz" - ] + "examples": [3, "=settings.xyz"] }, "validations": { "type": "array", @@ -688,41 +638,28 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Expression which needs to met for the input to be considered valid", - "examples": [ - "int(this.value) > 1 && int(this.value) <= 150", - "count(this.value) < 300" - ] + "examples": ["int(this.value) > 1 && int(this.value) <= 150", "count(this.value) < 300"] } }, "property": { "$ref": "#/definitions/stringExpression", "title": "Property", "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", - "examples": [ - "$birthday", - "dialog.${user.name}", - "=f(x)" - ] + "examples": ["$birthday", "dialog.${user.name}", "=f(x)"] }, "alwaysPrompt": { "$ref": "#/definitions/booleanExpression", "title": "Always prompt", "description": "Collect information even if the specified 'property' is not empty.", "default": false, - "examples": [ - false, - "=$val" - ] + "examples": [false, "=$val"] }, "allowInterruptions": { "$ref": "#/definitions/booleanExpression", "title": "Allow Interruptions", "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", "default": true, - "examples": [ - true, - "=user.xyz" - ] + "examples": [true, "=user.xyz"] }, "$kind": { "title": "Kind of dialog object", @@ -743,9 +680,7 @@ "title": "Begin a dialog", "description": "Begin another dialog.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -763,9 +698,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "dialog": { "oneOf": [ @@ -777,9 +710,7 @@ }, { "$ref": "#/definitions/equalsExpression", - "examples": [ - "=settings.dialogId" - ] + "examples": ["=settings.dialogId"] } ], "title": "Dialog name", @@ -810,9 +741,7 @@ "$ref": "#/definitions/stringExpression", "title": "Property", "description": "Property to store any value returned by the dialog that is called.", - "examples": [ - "dialog.userName" - ] + "examples": ["dialog.userName"] }, "$kind": { "title": "Kind of dialog object", @@ -833,9 +762,7 @@ "title": "Begin a skill", "description": "Begin a remote skill.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -853,28 +780,20 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "activityProcessed": { "$ref": "#/definitions/booleanExpression", "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": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "resultProperty": { "$ref": "#/definitions/stringExpression", "title": "Property", "description": "Property to store any value returned by the dialog that is called.", - "examples": [ - "dialog.userName" - ] + "examples": ["dialog.userName"] }, "botId": { "$ref": "#/definitions/stringExpression", @@ -887,9 +806,7 @@ "title": "Skill host", "description": "The callback Url for the skill host.", "default": "=settings.skillHostEndpoint", - "examples": [ - "https://mybot.contoso.com/api/skills/" - ] + "examples": ["https://mybot.contoso.com/api/skills/"] }, "connectionName": { "$ref": "#/definitions/stringExpression", @@ -906,9 +823,7 @@ "$ref": "#/definitions/stringExpression", "title": "Skill endpoint ", "description": "The /api/messages endpoint for the skill.", - "examples": [ - "https://myskill.contoso.com/api/messages/" - ] + "examples": ["https://myskill.contoso.com/api/messages/"] }, "activity": { "$kind": "Microsoft.IActivityTemplate", @@ -935,9 +850,7 @@ "title": "Break Loop", "description": "Stop executing this loop", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -955,9 +868,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "$kind": { "title": "Kind of dialog object", @@ -978,9 +889,7 @@ "title": "Cancel all dialogs", "description": "Cancel all active dialogs. All dialogs in the dialog chain will need a trigger to capture the event configured in this action.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -998,9 +907,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "activityProcessed": { "$ref": "#/definitions/booleanExpression", @@ -1038,9 +945,7 @@ "title": "Cancel all dialogs", "description": "Cancel all active dialogs. All dialogs in the dialog chain will need a trigger to capture the event configured in this action.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -1058,9 +963,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "activityProcessed": { "$ref": "#/definitions/booleanExpression", @@ -1094,16 +997,11 @@ } }, "Microsoft.ChoiceInput": { - "$role": [ - "implements(Microsoft.IDialog)", - "extends(Microsoft.InputDialog)" - ], + "$role": ["implements(Microsoft.IDialog)", "extends(Microsoft.InputDialog)"], "title": "Choice input dialog", "description": "Collect information - Pick from a list of choices", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -1116,21 +1014,13 @@ "$ref": "#/definitions/stringExpression", "title": "Default value", "description": "'Property' will be set to the value of this expression when max turn count is exceeded.", - "examples": [ - "hello world", - "Hello ${user.name}", - "=concat(user.firstname, user.lastName)" - ] + "examples": ["hello world", "Hello ${user.name}", "=concat(user.firstname, user.lastName)"] }, "value": { "$ref": "#/definitions/stringExpression", "title": "Value", "description": "'Property' will be set to the value of this expression unless it evaluates to null.", - "examples": [ - "hello world", - "Hello ${user.name}", - "=concat(user.firstname, user.lastName)" - ] + "examples": ["hello world", "Hello ${user.name}", "=concat(user.firstname, user.lastName)"] }, "outputFormat": { "$role": "expression", @@ -1141,10 +1031,7 @@ "type": "string", "title": "Standard", "description": "Standard output format.", - "enum": [ - "value", - "index" - ], + "enum": ["value", "index"], "default": "value" }, { @@ -1213,9 +1100,7 @@ "title": "Default locale", "description": "The default locale to use to parse confirmation choices if there is not one passed by the caller.", "default": "en-us", - "examples": [ - "en-us" - ] + "examples": ["en-us"] }, "style": { "$role": "expression", @@ -1226,14 +1111,7 @@ "type": "string", "title": "List style", "description": "Standard list style.", - "enum": [ - "none", - "auto", - "inline", - "list", - "suggestedAction", - "heroCard" - ], + "enum": ["none", "auto", "inline", "list", "suggestedAction", "heroCard"], "default": "auto" }, { @@ -1331,18 +1209,13 @@ "title": "Disabled", "description": "Optional condition which if true will disable this action.", "default": false, - "examples": [ - false, - "=user.isVip" - ] + "examples": [false, "=user.isVip"] }, "prompt": { "$kind": "Microsoft.IActivityTemplate", "title": "Initial prompt", "description": "Message to send to collect information.", - "examples": [ - "What is your birth date?" - ], + "examples": ["What is your birth date?"], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "unrecognizedPrompt": { @@ -1358,9 +1231,7 @@ "$kind": "Microsoft.IActivityTemplate", "title": "Invalid prompt", "description": "Message to send when the user input does not meet any validation expression.", - "examples": [ - "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" - ], + "examples": ["Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?"], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "defaultValueResponse": { @@ -1377,10 +1248,7 @@ "title": "Max turn count", "description": "Maximum number of re-prompt attempts to collect information.", "default": 3, - "examples": [ - 3, - "=settings.xyz" - ] + "examples": [3, "=settings.xyz"] }, "validations": { "type": "array", @@ -1390,41 +1258,28 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Expression which needs to met for the input to be considered valid", - "examples": [ - "int(this.value) > 1 && int(this.value) <= 150", - "count(this.value) < 300" - ] + "examples": ["int(this.value) > 1 && int(this.value) <= 150", "count(this.value) < 300"] } }, "property": { "$ref": "#/definitions/stringExpression", "title": "Property", "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", - "examples": [ - "$birthday", - "dialog.${user.name}", - "=f(x)" - ] + "examples": ["$birthday", "dialog.${user.name}", "=f(x)"] }, "alwaysPrompt": { "$ref": "#/definitions/booleanExpression", "title": "Always prompt", "description": "Collect information even if the specified 'property' is not empty.", "default": false, - "examples": [ - false, - "=$val" - ] + "examples": [false, "=$val"] }, "allowInterruptions": { "$ref": "#/definitions/booleanExpression", "title": "Allow Interruptions", "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", "default": true, - "examples": [ - true, - "=user.xyz" - ] + "examples": [true, "=user.xyz"] }, "$kind": { "title": "Kind of dialog object", @@ -1445,12 +1300,7 @@ "title": "Conditional Trigger Selector", "description": "Use a rule selector based on a condition", "type": "object", - "required": [ - "condition", - "ifTrue", - "ifFalse", - "$kind" - ], + "required": ["condition", "ifTrue", "ifFalse", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -1487,16 +1337,11 @@ } }, "Microsoft.ConfirmInput": { - "$role": [ - "implements(Microsoft.IDialog)", - "extends(Microsoft.InputDialog)" - ], + "$role": ["implements(Microsoft.IDialog)", "extends(Microsoft.InputDialog)"], "title": "Confirm input dialog", "description": "Collect information - Ask for confirmation (yes or no).", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -1509,18 +1354,14 @@ "$ref": "#/definitions/valueExpression", "title": "Output format", "description": "Optional expression to use to format the output.", - "examples": [ - "=concat('confirmation:', this.value)" - ] + "examples": ["=concat('confirmation:', this.value)"] }, "defaultLocale": { "$ref": "#/definitions/stringExpression", "title": "Default locale", "description": "The Default locale or an expression which provides the default locale to use as default if not found in the activity.", "default": "en-us", - "examples": [ - "en-us" - ] + "examples": ["en-us"] }, "style": { "$role": "expression", @@ -1531,14 +1372,7 @@ "type": "string", "title": "Standard style", "description": "Standard style for rendering choices.", - "enum": [ - "none", - "auto", - "inline", - "list", - "suggestedAction", - "heroCard" - ], + "enum": ["none", "auto", "inline", "list", "suggestedAction", "heroCard"], "default": "auto" }, { @@ -1590,19 +1424,13 @@ "$ref": "#/definitions/booleanExpression", "title": "Default value", "description": "'Property' will be set to the value of this expression when max turn count is exceeded.", - "examples": [ - true, - "=user.age > 3" - ] + "examples": [true, "=user.age > 3"] }, "value": { "$ref": "#/definitions/booleanExpression", "title": "Value", "description": "'Property' will be set to the value of this expression unless it evaluates to null.", - "examples": [ - true, - "=user.isVip" - ] + "examples": [true, "=user.isVip"] }, "confirmChoices": { "$role": "expression", @@ -1670,18 +1498,13 @@ "title": "Disabled", "description": "Optional condition which if true will disable this action.", "default": false, - "examples": [ - false, - "=user.isVip" - ] + "examples": [false, "=user.isVip"] }, "prompt": { "$kind": "Microsoft.IActivityTemplate", "title": "Initial prompt", "description": "Message to send to collect information.", - "examples": [ - "What is your birth date?" - ], + "examples": ["What is your birth date?"], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "unrecognizedPrompt": { @@ -1697,9 +1520,7 @@ "$kind": "Microsoft.IActivityTemplate", "title": "Invalid prompt", "description": "Message to send when the user input does not meet any validation expression.", - "examples": [ - "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" - ], + "examples": ["Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?"], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "defaultValueResponse": { @@ -1716,10 +1537,7 @@ "title": "Max turn count", "description": "Maximum number of re-prompt attempts to collect information.", "default": 3, - "examples": [ - 3, - "=settings.xyz" - ] + "examples": [3, "=settings.xyz"] }, "validations": { "type": "array", @@ -1729,41 +1547,28 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Expression which needs to met for the input to be considered valid", - "examples": [ - "int(this.value) > 1 && int(this.value) <= 150", - "count(this.value) < 300" - ] + "examples": ["int(this.value) > 1 && int(this.value) <= 150", "count(this.value) < 300"] } }, "property": { "$ref": "#/definitions/stringExpression", "title": "Property", "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", - "examples": [ - "$birthday", - "dialog.${user.name}", - "=f(x)" - ] + "examples": ["$birthday", "dialog.${user.name}", "=f(x)"] }, "alwaysPrompt": { "$ref": "#/definitions/booleanExpression", "title": "Always prompt", "description": "Collect information even if the specified 'property' is not empty.", "default": false, - "examples": [ - false, - "=$val" - ] + "examples": [false, "=$val"] }, "allowInterruptions": { "$ref": "#/definitions/booleanExpression", "title": "Allow Interruptions", "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", "default": true, - "examples": [ - true, - "=user.xyz" - ] + "examples": [true, "=user.xyz"] }, "$kind": { "title": "Kind of dialog object", @@ -1784,9 +1589,7 @@ "title": "Confirmation Entity Recognizer", "description": "Recognizer which recognizes confirmation choices (yes/no).", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -1814,9 +1617,7 @@ "title": "Continue Loop", "description": "Stop executing this template and continue with the next iteration of the loop.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -1834,9 +1635,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "$kind": { "title": "Kind of dialog object", @@ -1857,10 +1656,7 @@ "title": "Cross-trained Recognizer Set", "description": "Recognizer for selecting between cross trained recognizers.", "type": "object", - "required": [ - "recognizers", - "$kind" - ], + "required": ["recognizers", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -1902,9 +1698,7 @@ "title": "Currency Entity Recognizer", "description": "Recognizer which recognizes currency.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -1932,9 +1726,7 @@ "title": "DateTime Entity Recognizer", "description": "Recognizer which recognizes dates and time fragments.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -1958,10 +1750,7 @@ } }, "Microsoft.DateTimeInput": { - "$role": [ - "implements(Microsoft.IDialog)", - "extends(Microsoft.InputDialog)" - ], + "$role": ["implements(Microsoft.IDialog)", "extends(Microsoft.InputDialog)"], "title": "Date/time input dialog", "description": "Collect information - Ask for date and/ or time", "type": "object", @@ -1971,9 +1760,7 @@ "description": "Default locale.", "default": "en-us" }, - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -1987,26 +1774,20 @@ "format": "date-time", "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" - ] + "examples": ["=user.birthday"] }, "value": { "$ref": "#/definitions/stringExpression", "format": "date-time", "title": "Value", "description": "'Property' will be set to the value or the result of the expression unless it evaluates to null.", - "examples": [ - "=user.birthday" - ] + "examples": ["=user.birthday"] }, "outputFormat": { "$ref": "#/definitions/stringExpression", "title": "Output format", "description": "Expression to use for formatting the output.", - "examples": [ - "=this.value[0].Value" - ] + "examples": ["=this.value[0].Value"] }, "id": { "type": "string", @@ -2018,18 +1799,13 @@ "title": "Disabled", "description": "Optional condition which if true will disable this action.", "default": false, - "examples": [ - false, - "=user.isVip" - ] + "examples": [false, "=user.isVip"] }, "prompt": { "$kind": "Microsoft.IActivityTemplate", "title": "Initial prompt", "description": "Message to send to collect information.", - "examples": [ - "What is your birth date?" - ], + "examples": ["What is your birth date?"], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "unrecognizedPrompt": { @@ -2045,9 +1821,7 @@ "$kind": "Microsoft.IActivityTemplate", "title": "Invalid prompt", "description": "Message to send when the user input does not meet any validation expression.", - "examples": [ - "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" - ], + "examples": ["Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?"], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "defaultValueResponse": { @@ -2064,10 +1838,7 @@ "title": "Max turn count", "description": "Maximum number of re-prompt attempts to collect information.", "default": 3, - "examples": [ - 3, - "=settings.xyz" - ] + "examples": [3, "=settings.xyz"] }, "validations": { "type": "array", @@ -2077,41 +1848,28 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Expression which needs to met for the input to be considered valid", - "examples": [ - "int(this.value) > 1 && int(this.value) <= 150", - "count(this.value) < 300" - ] + "examples": ["int(this.value) > 1 && int(this.value) <= 150", "count(this.value) < 300"] } }, "property": { "$ref": "#/definitions/stringExpression", "title": "Property", "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", - "examples": [ - "$birthday", - "dialog.${user.name}", - "=f(x)" - ] + "examples": ["$birthday", "dialog.${user.name}", "=f(x)"] }, "alwaysPrompt": { "$ref": "#/definitions/booleanExpression", "title": "Always prompt", "description": "Collect information even if the specified 'property' is not empty.", "default": false, - "examples": [ - false, - "=$val" - ] + "examples": [false, "=$val"] }, "allowInterruptions": { "$ref": "#/definitions/booleanExpression", "title": "Allow Interruptions", "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", "default": true, - "examples": [ - true, - "=user.xyz" - ] + "examples": [true, "=user.xyz"] }, "$kind": { "title": "Kind of dialog object", @@ -2132,9 +1890,7 @@ "title": "Debugger break", "description": "If debugger is attached, stop the execution at this point in the conversation.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2152,9 +1908,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "$kind": { "title": "Kind of dialog object", @@ -2175,10 +1929,7 @@ "title": "Delete Activity", "description": "Delete an activity that was previously sent.", "type": "object", - "required": [ - "activityId", - "$kind" - ], + "required": ["activityId", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2196,17 +1947,13 @@ "$ref": "#/definitions/stringExpression", "title": "ActivityId", "description": "expression to an activityId to delete", - "examples": [ - "=$lastActivity" - ] + "examples": ["=$lastActivity"] }, "disabled": { "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "$kind": { "title": "Kind of dialog object", @@ -2227,10 +1974,7 @@ "title": "Delete Properties", "description": "Delete multiple properties and any value it holds.", "type": "object", - "required": [ - "properties", - "$kind" - ], + "required": ["properties", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2248,9 +1992,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "properties": { "type": "array", @@ -2281,10 +2023,7 @@ "title": "Delete Property", "description": "Delete a property and any value it holds.", "type": "object", - "required": [ - "property", - "$kind" - ], + "required": ["property", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2302,9 +2041,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "property": { "$ref": "#/definitions/stringExpression", @@ -2330,9 +2067,7 @@ "title": "Dimension Entity Recognizer", "description": "Recognizer which recognizes dimension.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2360,11 +2095,7 @@ "title": "Edit actions.", "description": "Edit the current list of actions.", "type": "object", - "required": [ - "changeType", - "actions", - "$kind" - ], + "required": ["changeType", "actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2382,9 +2113,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "changeType": { "title": "Type of change", @@ -2435,10 +2164,7 @@ "title": "Edit array", "description": "Modify an array in memory", "type": "object", - "required": [ - "itemsProperty", - "$kind" - ], + "required": ["itemsProperty", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2460,13 +2186,7 @@ "type": "string", "title": "Enum", "description": "Standard change type.", - "enum": [ - "push", - "pop", - "take", - "remove", - "clear" - ] + "enum": ["push", "pop", "take", "remove", "clear"] }, { "$ref": "#/definitions/equalsExpression" @@ -2477,9 +2197,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "itemsProperty": { "$ref": "#/definitions/stringExpression", @@ -2495,11 +2213,7 @@ "$ref": "#/definitions/valueExpression", "title": "Value", "description": "New value or expression.", - "examples": [ - "milk", - "=dialog.favColor", - "=dialog.favColor == 'red'" - ] + "examples": ["milk", "=dialog.favColor", "=dialog.favColor == 'red'"] }, "$kind": { "title": "Kind of dialog object", @@ -2520,9 +2234,7 @@ "title": "Email Entity Recognizer", "description": "Recognizer which recognizes email.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2550,10 +2262,7 @@ "title": "Emit a custom event", "description": "Emit an event. Capture this event with a trigger.", "type": "object", - "required": [ - "eventName", - "$kind" - ], + "required": ["eventName", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2571,9 +2280,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "eventName": { "$role": "expression", @@ -2639,9 +2346,7 @@ "title": "End dialog", "description": "End this dialog.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2659,18 +2364,13 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "value": { "$ref": "#/definitions/valueExpression", "title": "Value", "description": "Result value returned to the parent dialog.", - "examples": [ - "=dialog.userName", - "='tomato'" - ] + "examples": ["=dialog.userName", "='tomato'"] }, "$kind": { "title": "Kind of dialog object", @@ -2691,9 +2391,7 @@ "title": "End turn", "description": "End the current turn without ending the dialog.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2711,9 +2409,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "$kind": { "title": "Kind of dialog object", @@ -2734,9 +2430,7 @@ "title": "First Trigger Selector", "description": "Selector for first true rule", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2764,11 +2458,7 @@ "title": "For each item", "description": "Execute actions on each item in an a collection.", "type": "object", - "required": [ - "itemsProperty", - "actions", - "$kind" - ], + "required": ["itemsProperty", "actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2786,17 +2476,13 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "itemsProperty": { "$ref": "#/definitions/stringExpression", "title": "Items property", "description": "Property that holds the array.", - "examples": [ - "user.todoList" - ] + "examples": ["user.todoList"] }, "index": { "$ref": "#/definitions/stringExpression", @@ -2838,11 +2524,7 @@ "title": "For each page", "description": "Execute actions on each page (collection of items) in an array.", "type": "object", - "required": [ - "itemsProperty", - "actions", - "$kind" - ], + "required": ["itemsProperty", "actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2860,17 +2542,13 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "itemsProperty": { "$ref": "#/definitions/stringExpression", "title": "Items property", "description": "Property that holds the array.", - "examples": [ - "user.todoList" - ] + "examples": ["user.todoList"] }, "actions": { "type": "array", @@ -2918,9 +2596,7 @@ "title": "Get Activity Members", "description": "Get the members who are participating in an activity. (BotFrameworkAdapter only)", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2938,25 +2614,19 @@ "$ref": "#/definitions/stringExpression", "title": "Property", "description": "Property (named location to store information).", - "examples": [ - "user.age" - ] + "examples": ["user.age"] }, "activityId": { "$ref": "#/definitions/stringExpression", "title": "ActivityId", "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" - ] + "examples": ["$lastActivity"] }, "disabled": { "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "$kind": { "title": "Kind of dialog object", @@ -2977,9 +2647,7 @@ "title": "Get Converation Members", "description": "Get the members who are participating in an conversation. (BotFrameworkAdapter only)", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -2997,17 +2665,13 @@ "$ref": "#/definitions/stringExpression", "title": "Property", "description": "Property (named location to store information).", - "examples": [ - "user.age" - ] + "examples": ["user.age"] }, "disabled": { "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "$kind": { "title": "Kind of dialog object", @@ -3028,10 +2692,7 @@ "title": "Go to Action", "description": "Go to an an action by id.", "type": "object", - "required": [ - "actionId", - "$kind" - ], + "required": ["actionId", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -3049,9 +2710,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "actionId": { "$ref": "#/definitions/stringExpression", @@ -3077,9 +2736,7 @@ "title": "Guid Entity Recognizer", "description": "Recognizer which recognizes guids.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -3107,9 +2764,7 @@ "title": "Hashtag Entity Recognizer", "description": "Recognizer which recognizes Hashtags.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -3137,11 +2792,7 @@ "type": "object", "title": "HTTP request", "description": "Make a HTTP request.", - "required": [ - "url", - "method", - "$kind" - ], + "required": ["url", "method", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -3159,33 +2810,20 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "method": { "type": "string", "title": "HTTP method", "description": "HTTP method to use.", - "enum": [ - "GET", - "POST", - "PATCH", - "PUT", - "DELETE" - ], - "examples": [ - "GET", - "POST" - ] + "enum": ["GET", "POST", "PATCH", "PUT", "DELETE"], + "examples": ["GET", "POST"] }, "url": { "$ref": "#/definitions/stringExpression", "title": "Url", "description": "URL to call (supports data binding).", - "examples": [ - "https://contoso.com" - ] + "examples": ["https://contoso.com"] }, "body": { "$ref": "#/definitions/valueExpression", @@ -3197,18 +2835,13 @@ "$ref": "#/definitions/stringExpression", "title": "Result property", "description": "A property to store the result of this action. The result can include any of the 4 properties from the HTTP response: statusCode, reasonPhrase, content, and headers. If the content is JSON it will be a deserialized object. The values can be accessed via .content for example.", - "examples": [ - "dialog.contosodata" - ] + "examples": ["dialog.contosodata"] }, "contentType": { "$ref": "#/definitions/stringExpression", "title": "Content type", "description": "Content media type for the body.", - "examples": [ - "application/json", - "text/plain" - ] + "examples": ["application/json", "text/plain"] }, "headers": { "type": "object", @@ -3227,12 +2860,7 @@ "type": "string", "title": "Standard response", "description": "Standard response type.", - "enum": [ - "none", - "json", - "activity", - "activities" - ], + "enum": ["none", "json", "activity", "activities"], "default": "json" }, { @@ -3263,9 +2891,7 @@ "type": "string" }, { - "required": [ - "type" - ], + "required": ["type"], "description": "An Activity is the basic communication type for the Bot Framework 3.0 protocol.", "title": "Activity", "type": "object", @@ -3316,12 +2942,7 @@ "description": "Identifies the conversation to which the activity belongs.", "title": "conversation", "type": "object", - "required": [ - "conversationType", - "id", - "isGroup", - "name" - ], + "required": ["conversationType", "id", "isGroup", "name"], "properties": { "isGroup": { "description": "Indicates whether the conversation contains more than two participants at the time the\nactivity was generated", @@ -3350,10 +2971,7 @@ }, "role": { "description": "Role of the entity behind the account (Example: User, Bot, etc.). Possible values include:\n'user', 'bot'", - "enum": [ - "bot", - "user" - ], + "enum": ["bot", "user"], "type": "string", "title": "role" } @@ -3382,10 +3000,7 @@ "description": "Channel account information needed to route a message", "title": "ChannelAccount", "type": "object", - "required": [ - "id", - "name" - ], + "required": ["id", "name"], "properties": { "id": { "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or\n123456)", @@ -3426,9 +3041,7 @@ "description": "Message reaction object", "title": "MessageReaction", "type": "object", - "required": [ - "type" - ], + "required": ["type"], "properties": { "type": { "description": "Message reaction type. Possible values include: 'like', 'plusOne'", @@ -3485,10 +3098,7 @@ "description": "The suggested actions for the activity.", "title": "suggestedActions", "type": "object", - "required": [ - "actions", - "to" - ], + "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", @@ -3508,11 +3118,7 @@ "description": "A clickable action", "title": "CardAction", "type": "object", - "required": [ - "title", - "type", - "value" - ], + "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'", @@ -3560,9 +3166,7 @@ "description": "An attachment within an activity", "title": "Attachment", "type": "object", - "required": [ - "contentType" - ], + "required": ["contentType"], "properties": { "contentType": { "description": "mimetype/Contenttype for the file", @@ -3600,9 +3204,7 @@ "description": "Metadata object pertaining to an activity", "title": "Entity", "type": "object", - "required": [ - "type" - ], + "required": ["type"], "properties": { "type": { "description": "Type of this entity (RFC 3987 IRI)", @@ -3649,12 +3251,7 @@ "description": "A reference to another conversation or activity.", "title": "relatesTo", "type": "object", - "required": [ - "bot", - "channelId", - "conversation", - "serviceUrl" - ], + "required": ["bot", "channelId", "conversation", "serviceUrl"], "properties": { "activityId": { "description": "(Optional) ID of the activity to refer to", @@ -3727,10 +3324,7 @@ "description": "Refers to a substring of content within another field", "title": "TextHighlight", "type": "object", - "required": [ - "occurrence", - "text" - ], + "required": ["occurrence", "text"], "properties": { "text": { "description": "Defines the snippet of text to highlight", @@ -3749,10 +3343,7 @@ "description": "An optional programmatic action accompanying this request", "title": "semanticAction", "type": "object", - "required": [ - "entities", - "id" - ], + "required": ["entities", "id"], "properties": { "id": { "description": "ID of this action", @@ -4158,11 +3749,7 @@ "title": "If condition", "description": "Two-way branch the conversation flow based on a condition.", "type": "object", - "required": [ - "condition", - "actions", - "$kind" - ], + "required": ["condition", "actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -4180,18 +3767,13 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Expression to evaluate.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "disabled": { "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - true, - "=user.age > 3" - ] + "examples": [true, "=user.age > 3"] }, "actions": { "type": "array", @@ -4227,9 +3809,7 @@ }, "Microsoft.InputDialog": { "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -4248,18 +3828,13 @@ "title": "Disabled", "description": "Optional condition which if true will disable this action.", "default": false, - "examples": [ - false, - "=user.isVip" - ] + "examples": [false, "=user.isVip"] }, "prompt": { "$kind": "Microsoft.IActivityTemplate", "title": "Initial prompt", "description": "Message to send to collect information.", - "examples": [ - "What is your birth date?" - ], + "examples": ["What is your birth date?"], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "unrecognizedPrompt": { @@ -4275,9 +3850,7 @@ "$kind": "Microsoft.IActivityTemplate", "title": "Invalid prompt", "description": "Message to send when the user input does not meet any validation expression.", - "examples": [ - "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" - ], + "examples": ["Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?"], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "defaultValueResponse": { @@ -4294,10 +3867,7 @@ "title": "Max turn count", "description": "Maximum number of re-prompt attempts to collect information.", "default": 3, - "examples": [ - 3, - "=settings.xyz" - ] + "examples": [3, "=settings.xyz"] }, "validations": { "type": "array", @@ -4307,41 +3877,28 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Expression which needs to met for the input to be considered valid", - "examples": [ - "int(this.value) > 1 && int(this.value) <= 150", - "count(this.value) < 300" - ] + "examples": ["int(this.value) > 1 && int(this.value) <= 150", "count(this.value) < 300"] } }, "property": { "$ref": "#/definitions/stringExpression", "title": "Property", "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", - "examples": [ - "$birthday", - "dialog.${user.name}", - "=f(x)" - ] + "examples": ["$birthday", "dialog.${user.name}", "=f(x)"] }, "alwaysPrompt": { "$ref": "#/definitions/booleanExpression", "title": "Always prompt", "description": "Collect information even if the specified 'property' is not empty.", "default": false, - "examples": [ - false, - "=$val" - ] + "examples": [false, "=$val"] }, "allowInterruptions": { "$ref": "#/definitions/booleanExpression", "title": "Allow Interruptions", "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", "default": true, - "examples": [ - true, - "=user.xyz" - ] + "examples": [true, "=user.xyz"] }, "$kind": { "title": "Kind of dialog object", @@ -4362,9 +3919,7 @@ "title": "Ip Entity Recognizer", "description": "Recognizer which recognizes internet IP patterns (like 192.1.1.1).", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -4391,9 +3946,7 @@ "title": "Language Policy", "description": "This represents a policy map for locales lookups to use for language", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": { "type": "array", "title": "Per-locale policy", @@ -4430,10 +3983,7 @@ "title": "Log to console", "description": "Log a message to the host application. Send a TraceActivity to Bot Framework Emulator (optional).", "type": "object", - "required": [ - "text", - "$kind" - ], + "required": ["text", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -4451,10 +4001,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - true, - "=user.age > 3" - ] + "examples": [true, "=user.age > 3"] }, "text": { "$ref": "#/definitions/stringExpression", @@ -4490,12 +4037,7 @@ "title": "LUIS Recognizer", "description": "LUIS recognizer.", "type": "object", - "required": [ - "applicationId", - "endpoint", - "endpointKey", - "$kind" - ], + "required": ["applicationId", "endpoint", "endpointKey", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -4630,9 +4172,7 @@ "title": "Mentions Entity Recognizer", "description": "Recognizer which recognizes @Mentions", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -4660,9 +4200,7 @@ "title": "Most Specific Trigger Selector", "description": "Select most specific true events with optional additional selector", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -4694,10 +4232,7 @@ "title": "Multi-language recognizer", "description": "Configure one recognizer per language and the specify the language fallback policy.", "type": "object", - "required": [ - "recognizers", - "$kind" - ], + "required": ["recognizers", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -4746,9 +4281,7 @@ "title": "Number Entity Recognizer", "description": "Recognizer which recognizes numbers.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -4772,16 +4305,11 @@ } }, "Microsoft.NumberInput": { - "$role": [ - "implements(Microsoft.IDialog)", - "extends(Microsoft.InputDialog)" - ], + "$role": ["implements(Microsoft.IDialog)", "extends(Microsoft.InputDialog)"], "title": "Number input dialog", "description": "Collect information - Ask for a number.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -4794,28 +4322,19 @@ "$ref": "#/definitions/numberExpression", "title": "Default value", "description": "'Property' will be set to the value of this expression when max turn count is exceeded.", - "examples": [ - 13, - "=user.age" - ] + "examples": [13, "=user.age"] }, "value": { "$ref": "#/definitions/numberExpression", "title": "Value", "description": "'Property' will be set to the value of this expression unless it evaluates to null.", - "examples": [ - 13, - "=user.age" - ] + "examples": [13, "=user.age"] }, "outputFormat": { "$ref": "#/definitions/expression", "title": "Output format", "description": "Expression to format the number output.", - "examples": [ - "=this.value", - "=int(this.text)" - ] + "examples": ["=this.value", "=int(this.text)"] }, "defaultLocale": { "$ref": "#/definitions/stringExpression", @@ -4833,18 +4352,13 @@ "title": "Disabled", "description": "Optional condition which if true will disable this action.", "default": false, - "examples": [ - false, - "=user.isVip" - ] + "examples": [false, "=user.isVip"] }, "prompt": { "$kind": "Microsoft.IActivityTemplate", "title": "Initial prompt", "description": "Message to send to collect information.", - "examples": [ - "What is your birth date?" - ], + "examples": ["What is your birth date?"], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "unrecognizedPrompt": { @@ -4860,9 +4374,7 @@ "$kind": "Microsoft.IActivityTemplate", "title": "Invalid prompt", "description": "Message to send when the user input does not meet any validation expression.", - "examples": [ - "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" - ], + "examples": ["Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?"], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "defaultValueResponse": { @@ -4879,10 +4391,7 @@ "title": "Max turn count", "description": "Maximum number of re-prompt attempts to collect information.", "default": 3, - "examples": [ - 3, - "=settings.xyz" - ] + "examples": [3, "=settings.xyz"] }, "validations": { "type": "array", @@ -4892,41 +4401,28 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Expression which needs to met for the input to be considered valid", - "examples": [ - "int(this.value) > 1 && int(this.value) <= 150", - "count(this.value) < 300" - ] + "examples": ["int(this.value) > 1 && int(this.value) <= 150", "count(this.value) < 300"] } }, "property": { "$ref": "#/definitions/stringExpression", "title": "Property", "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", - "examples": [ - "$birthday", - "dialog.${user.name}", - "=f(x)" - ] + "examples": ["$birthday", "dialog.${user.name}", "=f(x)"] }, "alwaysPrompt": { "$ref": "#/definitions/booleanExpression", "title": "Always prompt", "description": "Collect information even if the specified 'property' is not empty.", "default": false, - "examples": [ - false, - "=$val" - ] + "examples": [false, "=$val"] }, "allowInterruptions": { "$ref": "#/definitions/booleanExpression", "title": "Allow Interruptions", "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", "default": true, - "examples": [ - true, - "=user.xyz" - ] + "examples": [true, "=user.xyz"] }, "$kind": { "title": "Kind of dialog object", @@ -4947,9 +4443,7 @@ "title": "NumberRange Entity Recognizer", "description": "Recognizer which recognizes ranges of numbers (Example:2 to 5).", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -4977,10 +4471,7 @@ "title": "OAuthInput Dialog", "description": "Collect login information.", "type": "object", - "required": [ - "connectionName", - "$kind" - ], + "required": ["connectionName", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -4993,35 +4484,25 @@ "$ref": "#/definitions/stringExpression", "title": "Connection name", "description": "The connection name configured in Azure Web App Bot OAuth settings.", - "examples": [ - "msgraphOAuthConnection" - ] + "examples": ["msgraphOAuthConnection"] }, "disabled": { "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - true, - "=user.age > 3" - ] + "examples": [true, "=user.age > 3"] }, "text": { "$ref": "#/definitions/stringExpression", "title": "Text", "description": "Text shown in the OAuth signin card.", - "examples": [ - "Please sign in. ", - "=concat(x,y,z)" - ] + "examples": ["Please sign in. ", "=concat(x,y,z)"] }, "title": { "$ref": "#/definitions/stringExpression", "title": "Title", "description": "Title shown in the OAuth signin card.", - "examples": [ - "Login" - ] + "examples": ["Login"] }, "timeout": { "$ref": "#/definitions/integerExpression", @@ -5033,26 +4514,20 @@ "$ref": "#/definitions/stringExpression", "title": "Token property", "description": "Property to store the OAuth token result.", - "examples": [ - "dialog.token" - ] + "examples": ["dialog.token"] }, "invalidPrompt": { "$kind": "Microsoft.IActivityTemplate", "title": "Invalid prompt", "description": "Message to send if user response is invalid.", - "examples": [ - "Sorry, the login info you provided is not valid." - ], + "examples": ["Sorry, the login info you provided is not valid."], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "defaultValueResponse": { "$kind": "Microsoft.IActivityTemplate", "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.", - "examples": [ - "Login failed." - ], + "examples": ["Login failed."], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "maxTurnCount": { @@ -5060,36 +4535,26 @@ "title": "Max turn count", "description": "Maximum number of re-prompt attempts to collect information.", "default": 3, - "examples": [ - 3 - ] + "examples": [3] }, "defaultValue": { "$ref": "#/definitions/expression", "title": "Default value", "description": "Expression to examine on each turn of the conversation as possible value to the property.", - "examples": [ - "@token" - ] + "examples": ["@token"] }, "allowInterruptions": { "$ref": "#/definitions/booleanExpression", "title": "Allow Interruptions", "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", "default": true, - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "alwaysPrompt": { "$ref": "#/definitions/booleanExpression", "title": "Always prompt", "description": "Collect information even if the specified 'property' is not empty.", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -5106,18 +4571,11 @@ } }, "Microsoft.OnActivity": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On activity", "description": "Actions to perform on receipt of a generic activity.", "type": "object", - "required": [ - "type", - "actions", - "$kind" - ], + "required": ["type", "actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -5135,9 +4593,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -5157,10 +4613,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -5177,17 +4630,11 @@ } }, "Microsoft.OnAssignEntity": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On entity assignment", "description": "Actions to take when an entity should be assigned to a property.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -5215,9 +4662,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -5237,10 +4682,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -5257,17 +4699,11 @@ } }, "Microsoft.OnBeginDialog": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On begin dialog", "description": "Actions to perform when this dialog begins.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -5280,9 +4716,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -5302,10 +4736,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -5326,9 +4757,7 @@ "title": "On cancel dialog", "description": "Actions to perform on cancel dialog event.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -5352,17 +4781,11 @@ } }, "Microsoft.OnChooseEntity": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On choose entity", "description": "Actions to be performed when an entity value needs to be resolved.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -5385,9 +4808,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -5407,10 +4828,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -5427,17 +4845,11 @@ } }, "Microsoft.OnChooseIntent": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On ambigious intent", "description": "Actions to perform on when an intent is ambigious.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -5460,9 +4872,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -5482,10 +4892,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -5502,17 +4909,11 @@ } }, "Microsoft.OnChooseProperty": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On choose property", "description": "Actions to take when there are multiple possible mappings of entities to properties.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -5550,9 +4951,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -5572,10 +4971,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -5596,10 +4992,7 @@ "title": "On condition", "description": "Actions to perform when specified condition is true.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -5612,9 +5005,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -5634,10 +5025,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -5654,17 +5042,11 @@ } }, "Microsoft.OnConversationUpdateActivity": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On ConversationUpdate activity", "description": "Actions to perform on receipt of an activity with type 'ConversationUpdate'.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -5677,9 +5059,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -5699,10 +5079,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -5719,18 +5096,11 @@ } }, "Microsoft.OnDialogEvent": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On dialog event", "description": "Actions to perform when a specific dialog event occurs.", "type": "object", - "required": [ - "actions", - "event", - "$kind" - ], + "required": ["actions", "event", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -5748,9 +5118,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -5770,10 +5138,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -5790,17 +5155,11 @@ } }, "Microsoft.OnEndOfActions": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On end of actions", "description": "Actions to take when there are no more actions in the current dialog.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -5813,9 +5172,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -5835,10 +5192,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -5855,17 +5209,11 @@ } }, "Microsoft.OnEndOfConversationActivity": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On EndOfConversation activity", "description": "Actions to perform on receipt of an activity with type 'EndOfConversation'.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -5878,9 +5226,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -5900,10 +5246,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -5920,17 +5263,11 @@ } }, "Microsoft.OnError": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On Error", "description": "Action to perform when an 'Error' dialog event occurs.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -5943,9 +5280,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -5965,10 +5300,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -5985,17 +5317,11 @@ } }, "Microsoft.OnEventActivity": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On Event activity", "description": "Actions to perform on receipt of an activity with type 'Event'.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6008,9 +5334,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -6030,10 +5354,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -6050,17 +5371,11 @@ } }, "Microsoft.OnHandoffActivity": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On Handoff activity", "description": "Actions to perform on receipt of an activity with type 'HandOff'.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6073,9 +5388,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -6095,10 +5408,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -6115,17 +5425,11 @@ } }, "Microsoft.OnIntent": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On intent recognition", "description": "Actions to perform when specified intent is recognized.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6153,9 +5457,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -6175,10 +5477,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -6195,17 +5494,11 @@ } }, "Microsoft.OnInvokeActivity": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On Invoke activity", "description": "Actions to perform on receipt of an activity with type 'Invoke'.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6218,9 +5511,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -6240,10 +5531,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -6260,17 +5548,11 @@ } }, "Microsoft.OnMessageActivity": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On Message activity", "description": "Actions to perform on receipt of an activity with type 'Message'. Overrides Intent trigger.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6283,9 +5565,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -6305,10 +5585,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -6325,17 +5602,11 @@ } }, "Microsoft.OnMessageDeleteActivity": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On MessageDelete activity", "description": "Actions to perform on receipt of an activity with type 'MessageDelete'.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6348,9 +5619,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -6370,10 +5639,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -6390,17 +5656,11 @@ } }, "Microsoft.OnMessageReactionActivity": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On MessageReaction activity", "description": "Actions to perform on receipt of an activity with type 'MessageReaction'.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6413,9 +5673,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -6435,10 +5693,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -6455,17 +5710,11 @@ } }, "Microsoft.OnMessageUpdateActivity": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On MessageUpdate activity", "description": "Actions to perform on receipt of an activity with type 'MessageUpdate'.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6478,9 +5727,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -6500,10 +5747,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -6520,17 +5764,11 @@ } }, "Microsoft.OnQnAMatch": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On QnAMaker Match", "description": "Actions to perform on when an match from QnAMaker is found.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6543,9 +5781,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -6565,10 +5801,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -6585,17 +5818,11 @@ } }, "Microsoft.OnRepromptDialog": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On RepromptDialog", "description": "Actions to perform when 'RepromptDialog' event occurs.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6608,9 +5835,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -6630,10 +5855,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -6650,17 +5872,11 @@ } }, "Microsoft.OnTypingActivity": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On Typing activity", "description": "Actions to perform on receipt of an activity with type 'Typing'.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6673,9 +5889,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -6695,10 +5909,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -6715,17 +5926,11 @@ } }, "Microsoft.OnUnknownIntent": { - "$role": [ - "implements(Microsoft.ITrigger)", - "extends(Microsoft.OnCondition)" - ], + "$role": ["implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)"], "title": "On unknown intent", "description": "Action to perform when user input is unrecognized and if none of the 'on intent recognition' triggers match recognized intent.", "type": "object", - "required": [ - "actions", - "$kind" - ], + "required": ["actions", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6738,9 +5943,7 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Condition (expression).", - "examples": [ - "user.vip == true" - ] + "examples": ["user.vip == true"] }, "actions": { "type": "array", @@ -6760,10 +5963,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Run Once", "description": "True if rule should run once per unique conditions", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "$kind": { "title": "Kind of dialog object", @@ -6784,9 +5984,7 @@ "title": "Ordinal Entity Recognizer", "description": "Recognizer which recognizes ordinals (example: first, second, 3rd).", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6814,9 +6012,7 @@ "title": "Percentage Entity Recognizer", "description": "Recognizer which recognizes percentages.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6844,9 +6040,7 @@ "title": "Phone Number Entity Recognizer", "description": "Recognizer which recognizes phone numbers.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6874,12 +6068,7 @@ "title": "QnAMaker Dialog", "description": "Dialog which uses QnAMAker knowledge base to answer questions.", "type": "object", - "required": [ - "knowledgeBaseId", - "endpointKey", - "hostname", - "$kind" - ], + "required": ["knowledgeBaseId", "endpointKey", "hostname", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -6905,9 +6094,7 @@ "title": "Hostname", "description": "Hostname for your QnA Maker service.", "default": "=settings.qna.hostname", - "examples": [ - "https://yourserver.azurewebsites.net/qnamaker" - ] + "examples": ["https://yourserver.azurewebsites.net/qnamaker"] }, "noAnswer": { "$kind": "Microsoft.IActivityTemplate", @@ -6985,11 +6172,7 @@ { "title": "Standard ranker", "description": "Standard ranker types.", - "enum": [ - "default", - "questionOnly", - "autoSuggestQuestion" - ], + "enum": ["default", "questionOnly", "autoSuggestQuestion"], "default": "default" }, { @@ -7016,12 +6199,7 @@ "title": "QnAMaker Recognizer", "description": "Recognizer for generating QnAMatch intents from a KB.", "type": "object", - "required": [ - "knowledgeBaseId", - "endpointKey", - "hostname", - "$kind" - ], + "required": ["knowledgeBaseId", "endpointKey", "hostname", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -7052,9 +6230,7 @@ "title": "Hostname", "description": "Hostname for your QnA Maker service.", "default": "settings.qna.hostname", - "examples": [ - "https://yourserver.azurewebsites.net/qnamaker" - ] + "examples": ["https://yourserver.azurewebsites.net/qnamaker"] }, "threshold": { "$ref": "#/definitions/numberExpression", @@ -7096,10 +6272,7 @@ "$ref": "#/definitions/booleanExpression", "title": "IsTest", "description": "True, if pointing to Test environment, else false.", - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "rankerType": { "title": "Ranker Type", @@ -7109,11 +6282,7 @@ "type": "string", "title": "Ranker type", "description": "Type of Ranker.", - "enum": [ - "default", - "questionOnly", - "autoSuggestQuestion" - ], + "enum": ["default", "questionOnly", "autoSuggestQuestion"], "default": "default" }, { @@ -7126,10 +6295,7 @@ "title": "Include Dialog Name", "description": "When set to false, the dialog name will not be passed to QnAMaker. (default) is true", "default": true, - "examples": [ - true, - "=f(x)" - ] + "examples": [true, "=f(x)"] }, "metadata": { "$ref": "#/definitions/arrayExpression", @@ -7182,9 +6348,7 @@ "title": "Random rule selector", "description": "Select most specific true rule.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -7217,10 +6381,7 @@ "title": "Recognizer Set", "description": "Creates the union of the intents and entities of the recognizers in the set.", "type": "object", - "required": [ - "recognizers", - "$kind" - ], + "required": ["recognizers", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -7262,11 +6423,7 @@ "title": "Regex Entity Recognizer", "description": "Recognizer which recognizes patterns of input based on regex.", "type": "object", - "required": [ - "name", - "pattern", - "$kind" - ], + "required": ["name", "pattern", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -7304,9 +6461,7 @@ "title": "Regex recognizer", "description": "Use regular expressions to recognize intents and entities from user input.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -7370,9 +6525,7 @@ "type": "object", "title": "Repeat dialog", "description": "Repeat current dialog.", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -7390,17 +6543,13 @@ "$ref": "#/definitions/booleanExpression", "title": "AllowLoop", "description": "Optional condition which if true will allow loop of the repeated dialog.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "disabled": { "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "options": { "$ref": "#/definitions/objectExpression", @@ -7437,9 +6586,7 @@ "type": "object", "title": "Replace dialog", "description": "Replace current dialog with another dialog.", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -7457,9 +6604,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "dialog": { "oneOf": [ @@ -7471,9 +6616,7 @@ }, { "$ref": "#/definitions/equalsExpression", - "examples": [ - "=settings.dialogId" - ] + "examples": ["=settings.dialogId"] } ], "title": "Dialog name", @@ -7514,9 +6657,7 @@ "title": "Resource Multi-Language Generator", "description": "MultiLanguage Generator which is bound to resource by resource Id.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -7560,9 +6701,7 @@ "title": "Send an activity", "description": "Respond with an activity.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -7580,9 +6719,7 @@ "$ref": "#/definitions/stringExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - "user.age > 3" - ] + "examples": ["user.age > 3"] }, "activity": { "$kind": "Microsoft.IActivityTemplate", @@ -7609,10 +6746,7 @@ "title": "Set property", "description": "Set one or more property values.", "type": "object", - "required": [ - "assignments", - "$kind" - ], + "required": ["assignments", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -7630,10 +6764,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - true, - "=user.age > 3" - ] + "examples": [true, "=user.age > 3"] }, "assignments": { "type": "array", @@ -7648,19 +6779,13 @@ "$ref": "#/definitions/stringExpression", "title": "Property", "description": "Property (named location to store information).", - "examples": [ - "user.age" - ] + "examples": ["user.age"] }, "value": { "$ref": "#/definitions/valueExpression", "title": "Value", "description": "New value or expression.", - "examples": [ - "='milk'", - "=dialog.favColor", - "=dialog.favColor == 'red'" - ] + "examples": ["='milk'", "=dialog.favColor", "=dialog.favColor == 'red'"] } } } @@ -7684,11 +6809,7 @@ "title": "Set property", "description": "Set property to a value.", "type": "object", - "required": [ - "property", - "value", - "$kind" - ], + "required": ["property", "value", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -7706,28 +6827,19 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - true, - "=user.age > 3" - ] + "examples": [true, "=user.age > 3"] }, "property": { "$ref": "#/definitions/stringExpression", "title": "Property", "description": "Property (named location to store information).", - "examples": [ - "user.age" - ] + "examples": ["user.age"] }, "value": { "$ref": "#/definitions/valueExpression", "title": "Value", "description": "New value or expression.", - "examples": [ - "='milk'", - "=dialog.favColor", - "=dialog.favColor == 'red'" - ] + "examples": ["='milk'", "=dialog.favColor", "=dialog.favColor == 'red'"] }, "$kind": { "title": "Kind of dialog object", @@ -7748,9 +6860,7 @@ "title": "Sign Out User", "description": "Sign a user out that was logged in previously using OAuthInput.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -7768,9 +6878,7 @@ "$ref": "#/definitions/stringExpression", "title": "ActivityId", "description": "expression to an activityId to get the members. If none is defined then the current activity id will be used.", - "examples": [ - "=$lastActivity" - ] + "examples": ["=$lastActivity"] }, "connectionName": { "$ref": "#/definitions/stringExpression", @@ -7781,10 +6889,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - true, - "=user.age > 3" - ] + "examples": [true, "=user.age > 3"] }, "$kind": { "title": "Kind of dialog object", @@ -7805,10 +6910,7 @@ "title": "Microsoft Static Activity Template", "description": "This allows you to define a static Activity object", "type": "object", - "required": [ - "activity", - "$kind" - ], + "required": ["activity", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -7821,9 +6923,7 @@ "$ref": "#/definitions/Microsoft.IActivityTemplate/oneOf/1", "title": "Activity", "description": "A static Activity to used.", - "required": [ - "type" - ] + "required": ["type"] }, "$kind": { "title": "Kind of dialog object", @@ -7844,10 +6944,7 @@ "title": "Switch condition", "description": "Execute different actions based on the value of a property.", "type": "object", - "required": [ - "condition", - "$kind" - ], + "required": ["condition", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -7865,18 +6962,13 @@ "$ref": "#/definitions/stringExpression", "title": "Condition", "description": "Property to evaluate.", - "examples": [ - "user.favColor" - ] + "examples": ["user.favColor"] }, "disabled": { "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - true, - "=user.age > 3" - ] + "examples": [true, "=user.age > 3"] }, "cases": { "type": "array", @@ -7886,25 +6978,13 @@ "type": "object", "title": "Case", "description": "Case and actions.", - "required": [ - "value", - "actions" - ], + "required": ["value", "actions"], "properties": { "value": { - "type": [ - "number", - "integer", - "boolean", - "string" - ], + "type": ["number", "integer", "boolean", "string"], "title": "Value", "description": "The value to compare the condition with.", - "examples": [ - "red", - "true", - "13" - ] + "examples": ["red", "true", "13"] }, "actions": { "type": "array", @@ -7946,9 +7026,7 @@ "title": "Temperature Recognizer", "description": "Recognizer which recognizes temperatures.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -7976,9 +7054,7 @@ "title": "Template Multi-Language Generator", "description": "Template Generator which allows only inline evaluation of templates.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -8007,16 +7083,11 @@ } }, "Microsoft.TextInput": { - "$role": [ - "implements(Microsoft.IDialog)", - "extends(Microsoft.InputDialog)" - ], + "$role": ["implements(Microsoft.IDialog)", "extends(Microsoft.InputDialog)"], "type": "object", "title": "Text input dialog", "description": "Collection information - Ask for a word or sentence.", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -8029,30 +7100,19 @@ "$ref": "#/definitions/stringExpression", "title": "Default value", "description": "'Property' will be set to the value of this expression when max turn count is exceeded.", - "examples": [ - "hello world", - "Hello ${user.name}", - "=concat(user.firstname, user.lastName)" - ] + "examples": ["hello world", "Hello ${user.name}", "=concat(user.firstname, user.lastName)"] }, "value": { "$ref": "#/definitions/stringExpression", "title": "Value", "description": "'Property' will be set to the value of this expression unless it evaluates to null.", - "examples": [ - "hello world", - "Hello ${user.name}", - "=concat(user.firstname, user.lastName)" - ] + "examples": ["hello world", "Hello ${user.name}", "=concat(user.firstname, user.lastName)"] }, "outputFormat": { "$ref": "#/definitions/stringExpression", "title": "Output format", "description": "Expression to format the output.", - "examples": [ - "=toUpper(this.value)", - "${toUpper(this.value)}" - ] + "examples": ["=toUpper(this.value)", "${toUpper(this.value)}"] }, "id": { "type": "string", @@ -8064,18 +7124,13 @@ "title": "Disabled", "description": "Optional condition which if true will disable this action.", "default": false, - "examples": [ - false, - "=user.isVip" - ] + "examples": [false, "=user.isVip"] }, "prompt": { "$kind": "Microsoft.IActivityTemplate", "title": "Initial prompt", "description": "Message to send to collect information.", - "examples": [ - "What is your birth date?" - ], + "examples": ["What is your birth date?"], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "unrecognizedPrompt": { @@ -8091,9 +7146,7 @@ "$kind": "Microsoft.IActivityTemplate", "title": "Invalid prompt", "description": "Message to send when the user input does not meet any validation expression.", - "examples": [ - "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" - ], + "examples": ["Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?"], "$ref": "#/definitions/Microsoft.IActivityTemplate" }, "defaultValueResponse": { @@ -8110,10 +7163,7 @@ "title": "Max turn count", "description": "Maximum number of re-prompt attempts to collect information.", "default": 3, - "examples": [ - 3, - "=settings.xyz" - ] + "examples": [3, "=settings.xyz"] }, "validations": { "type": "array", @@ -8123,41 +7173,28 @@ "$ref": "#/definitions/condition", "title": "Condition", "description": "Expression which needs to met for the input to be considered valid", - "examples": [ - "int(this.value) > 1 && int(this.value) <= 150", - "count(this.value) < 300" - ] + "examples": ["int(this.value) > 1 && int(this.value) <= 150", "count(this.value) < 300"] } }, "property": { "$ref": "#/definitions/stringExpression", "title": "Property", "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", - "examples": [ - "$birthday", - "dialog.${user.name}", - "=f(x)" - ] + "examples": ["$birthday", "dialog.${user.name}", "=f(x)"] }, "alwaysPrompt": { "$ref": "#/definitions/booleanExpression", "title": "Always prompt", "description": "Collect information even if the specified 'property' is not empty.", "default": false, - "examples": [ - false, - "=$val" - ] + "examples": [false, "=$val"] }, "allowInterruptions": { "$ref": "#/definitions/booleanExpression", "title": "Allow Interruptions", "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", "default": true, - "examples": [ - true, - "=user.xyz" - ] + "examples": [true, "=user.xyz"] }, "$kind": { "title": "Kind of dialog object", @@ -8178,10 +7215,7 @@ "title": "Microsoft TextTemplate", "description": "Use LG Templates to create text", "type": "object", - "required": [ - "template", - "$kind" - ], + "required": ["template", "$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -8214,9 +7248,7 @@ "title": "Send a TraceActivity", "description": "Send a trace activity to the transcript logger and/ or Bot Framework Emulator.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -8234,10 +7266,7 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - true, - "=user.age > 3" - ] + "examples": [true, "=user.age > 3"] }, "name": { "$ref": "#/definitions/stringExpression", @@ -8278,9 +7307,7 @@ "title": "True Trigger Selector", "description": "Selector for all true events", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -8308,9 +7335,7 @@ "title": "Send an activity", "description": "Respond with an activity.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -8328,18 +7353,13 @@ "$ref": "#/definitions/booleanExpression", "title": "Disabled", "description": "Optional condition which if true will disable this action.", - "examples": [ - true, - "=user.age > 3" - ] + "examples": [true, "=user.age > 3"] }, "activityId": { "$ref": "#/definitions/stringExpression", "title": "Activity Id", "description": "An string expression with the activity id to update.", - "examples": [ - "=dialog.lastActivityId" - ] + "examples": ["=dialog.lastActivityId"] }, "activity": { "$kind": "Microsoft.IActivityTemplate", @@ -8366,9 +7386,7 @@ "title": "Confirmation Url Recognizer", "description": "Recognizer which recognizes urls.", "type": "object", - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -8416,22 +7434,16 @@ "title": "Boolean", "description": "Boolean constant.", "default": false, - "examples": [ - false - ] + "examples": [false] }, { "$ref": "#/definitions/equalsExpression", - "examples": [ - "=user.isVip" - ] + "examples": ["=user.isVip"] } ] }, "component": { - "required": [ - "$kind" - ], + "required": ["$kind"], "additionalProperties": false, "patternProperties": { "^\\$": { @@ -8466,9 +7478,7 @@ "title": "Boolean", "description": "Boolean value.", "default": true, - "examples": [ - false - ] + "examples": [false] } ] }, @@ -8477,18 +7487,14 @@ "title": "Expression", "description": "Expression starting with =.", "pattern": "^=.*\\S.*", - "examples": [ - "=user.name" - ] + "examples": ["=user.name"] }, "expression": { "type": "string", "title": "Expression", "description": "Expression to evaluate.", "pattern": "^.*\\S.*", - "examples": [ - "user.age > 13" - ] + "examples": ["user.age > 13"] }, "integerExpression": { "$role": "expression", @@ -8500,15 +7506,11 @@ "title": "Integer", "description": "Integer constant.", "default": 0, - "examples": [ - 15 - ] + "examples": [15] }, { "$ref": "#/definitions/equalsExpression", - "examples": [ - "=user.age" - ] + "examples": ["=user.age"] } ] }, @@ -8522,15 +7524,11 @@ "title": "Number", "description": "Number constant.", "default": 0, - "examples": [ - 15.5 - ] + "examples": [15.5] }, { "$ref": "#/definitions/equalsExpression", - "examples": [ - "=dialog.quantity" - ] + "examples": ["=dialog.quantity"] } ] }, @@ -8565,15 +7563,11 @@ "title": "String", "description": "Interpolated string", "pattern": "^(?!(=)).*", - "examples": [ - "Hello ${user.name}" - ] + "examples": ["Hello ${user.name}"] }, { "$ref": "#/definitions/equalsExpression", - "examples": [ - "=concat('x','y','z')" - ] + "examples": ["=concat('x','y','z')"] } ] }, @@ -8597,31 +7591,23 @@ "title": "String", "description": "Interpolated string.", "pattern": "^(?!(=)).*", - "examples": [ - "Hello ${user.name}" - ] + "examples": ["Hello ${user.name}"] }, { "type": "boolean", "title": "Boolean", "description": "Boolean constant", - "examples": [ - false - ] + "examples": [false] }, { "type": "number", "title": "Number", "description": "Number constant.", - "examples": [ - 15.5 - ] + "examples": [15.5] }, { "$ref": "#/definitions/equalsExpression", - "examples": [ - "=..." - ] + "examples": ["=..."] } ] } @@ -8680,7 +7666,8 @@ "logActivities": true }, "runtime": { - "customRuntime": false, + "key": "adaptive-runtime-dotnet-webapp", + "customRuntime": true, "path": "", "command": "" }, diff --git a/Composer/packages/client/src/recoilModel/dispatchers/project.ts b/Composer/packages/client/src/recoilModel/dispatchers/project.ts index 833a6a6e62..5783f9fcf5 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/project.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/project.ts @@ -4,11 +4,12 @@ import formatMessage from 'format-message'; import findIndex from 'lodash/findIndex'; +import { OpenConfirmModal } from '@bfc/ui-shared'; import { PublishTarget, QnABotTemplateId, RootBotManagedProperties } from '@bfc/shared'; import get from 'lodash/get'; import { CallbackInterface, useRecoilCallback } from 'recoil'; -import { BotStatus, FEEDVERSION } from '../../constants'; +import { BotStatus, CreationFlowStatus, FEEDVERSION } from '../../constants'; import settingStorage from '../../utils/dialogSettingStorage'; import { getFileNameFromPath } from '../../utils/fileUtil'; import httpClient from '../../utils/httpUtil'; @@ -39,6 +40,7 @@ import { warnAboutDotNetState, warnAboutFunctionsState, settingsState, + creationFlowStatusState, orchestratorForSkillsDialogState, } from '../atoms'; import { botRuntimeOperationsSelector, rootBotProjectIdSelector } from '../selectors'; @@ -66,6 +68,7 @@ import { removeRecentProject, resetBotStates, saveProject, + migrateToV2, } from './utils/project'; export const projectDispatcher = () => { @@ -242,6 +245,23 @@ export const projectDispatcher = () => { } ); + const forceMigrate = useRecoilCallback((callbackHelpers: CallbackInterface) => async (projectId: string) => { + if ( + await OpenConfirmModal( + formatMessage('Convert your project to the latest format'), + formatMessage( + 'This project was created in an older version of Composer. To open this project in Composer 2.0, we must copy your project and convert it to the latest format. Your original project will not be changed.' + ), + { confirmText: formatMessage('Convert') } + ) + ) { + callbackHelpers.set(creationFlowStatusState, CreationFlowStatus.MIGRATE); + navigateTo(`/v2/projects/migrate/${projectId}`); + } else { + navigateTo(`/home`); + } + }); + const openProject = useRecoilCallback( (callbackHelpers: CallbackInterface) => async ( path: string, @@ -255,7 +275,16 @@ export const projectDispatcher = () => { set(botOpeningState, true); await flushExistingTasks(callbackHelpers); - const { projectId, mainDialog } = await openRootBotAndSkillsByPath(callbackHelpers, path, storageId); + const { projectId, mainDialog, requiresMigrate } = await openRootBotAndSkillsByPath( + callbackHelpers, + path, + storageId + ); + + if (requiresMigrate) { + await forceMigrate(projectId); + return; + } // ABS open Flow, update publishProfile & set alias for project after open project if (absData) { @@ -316,8 +345,11 @@ export const projectDispatcher = () => { try { await flushExistingTasks(callbackHelpers); set(botOpeningState, true); - await openRootBotAndSkillsByProjectId(callbackHelpers, projectId); - + const { requiresMigrate } = await openRootBotAndSkillsByProjectId(callbackHelpers, projectId); + if (requiresMigrate) { + await forceMigrate(projectId); + return; + } // Post project creation set(projectMetaDataState(projectId), { isRootBot: true, @@ -485,6 +517,42 @@ export const projectDispatcher = () => { } ); + const migrateProjectTo = useRecoilCallback( + (callbackHelpers: CallbackInterface) => async ( + oldProjectId: string, + name: string, + description: string, + location: string, + runtimeLanguage: string, + runtimeType: string + ) => { + const { set, snapshot } = callbackHelpers; + try { + const dispatcher = await snapshot.getPromise(dispatcherState); + set(botOpeningState, true); + + // starts the creation process and stores the jobID in state for tracking + const response = await migrateToV2( + callbackHelpers, + oldProjectId, + name, + description, + location, + runtimeLanguage, + runtimeType + ); + + if (response.data.jobId) { + dispatcher.updateCreationMessage(response.data.jobId); + } + } catch (ex) { + set(botProjectIdsState, []); + handleProjectFailure(callbackHelpers, ex); + navigateTo('/home'); + } + } + ); + const deleteBot = useRecoilCallback((callbackHelpers: CallbackInterface) => async (projectId: string) => { try { const { reset } = callbackHelpers; @@ -584,10 +652,10 @@ export const projectDispatcher = () => { const updateCreationMessage = useRecoilCallback( (callbackHelpers: CallbackInterface) => async ( jobId: string, - templateId: string, - urlSuffix: string, - profile: any, - source: any + templateId?: string, + urlSuffix?: string, + profile?: any, + source?: any ) => { const timer = setInterval(async () => { try { @@ -685,6 +753,7 @@ export const projectDispatcher = () => { createNewBotV2, deleteBot, saveProjectAs, + migrateProjectTo, fetchProjectById, fetchRecentProjects, updateCurrentTarget, diff --git a/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts b/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts index bd7f3d7e69..a87110d988 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts @@ -463,6 +463,14 @@ export const initQnaFilesStatus = (projectId: string, qnaFiles: QnAFile[], dialo return updateQnaFilesStatus(projectId, qnaFiles); }; +export const isAdaptiveRuntime = (settings): boolean => { + return settings?.runtime?.key?.match(/^adaptive-runtime/) ? true : false; +}; + +export const isPVA = (settings): boolean => { + return settings?.publishTargets?.some((target) => target.type === 'pva-publish-composer'); +}; + export const initBotState = async (callbackHelpers: CallbackInterface, data: any, botFiles: any) => { const { set } = callbackHelpers; const { botName, botEnvironment, location, readme, schemas, settings, id: projectId, diagnostics } = data; @@ -479,7 +487,6 @@ export const initBotState = async (callbackHelpers: CallbackInterface, data: any recognizers, crossTrainConfig, } = botFiles; - const storedLocale = languageStorage.get(botName)?.locale; const locale = settings.languages.includes(storedLocale) ? storedLocale : settings.defaultLanguage; languageStorage.setLocale(botName, locale); @@ -603,6 +610,7 @@ export const openLocalSkill = async (callbackHelpers, pathToBot: string, storage if (error) { throw error; } + const mainDialog = await initBotState(callbackHelpers, projectData, botFiles); set(projectMetaDataState(projectData.id), { isRootBot: false, @@ -652,7 +660,6 @@ export const createNewBotFromTemplate = async ( } const currentBotProjectFileIndexed: BotProjectFile = botFiles.botProjectSpaceFiles[0]; set(botProjectFileState(projectId), currentBotProjectFileIndexed); - const mainDialog = await initBotState(callbackHelpers, projectData, botFiles); // if create from QnATemplate, continue creation flow. if (templateId === QnABotTemplateId) { @@ -663,6 +670,58 @@ export const createNewBotFromTemplate = async ( return { projectId, mainDialog }; }; +export const createNewBotFromTemplateV2 = async ( + callbackHelpers, + templateId: string, + templateVersion: string, + name: string, + description: string, + location: string, + schemaUrl?: string, + locale?: string, + templateDir?: string, + eTag?: string, + alias?: string, + preserveRoot?: boolean +) => { + const jobId = await httpClient.post(`/v2/projects`, { + storageId: 'default', + templateId, + templateVersion, + name, + description, + location, + schemaUrl, + locale, + templateDir, + eTag, + alias, + preserveRoot, + }); + return jobId; +}; + +export const migrateToV2 = async ( + callbackHelpers, + oldProjectId: string, + name: string, + description: string, + location: string, + runtimeLanguage: string, + runtimeType: string +) => { + const jobId = await httpClient.post(`/v2/projects/migrate`, { + storageId: 'default', + oldProjectId, + name, + description, + location, + runtimeLanguage, + runtimeType, + }); + return jobId; +}; + const addProjectToBotProjectSpace = (set, projectId: string, skillCt: number) => { let isBotProjectLoaded = false; set(botProjectIdsState, (current: string[]) => { @@ -776,6 +835,7 @@ export const openRootBotAndSkills = async (callbackHelpers: CallbackInterface, d return { mainDialog, projectId: rootBotProjectId, + requiresMigrate: !isAdaptiveRuntime(botFiles.mergedSettings) && !isPVA(botFiles.mergedSettings), }; }; @@ -831,6 +891,7 @@ export const openRootBotAndSkillsByProjectId = async (callbackHelpers: CallbackI if (data.error) { throw data.error; } + return await openRootBotAndSkills(callbackHelpers, data); }; diff --git a/Composer/packages/client/src/recoilModel/selectors/diagnosticsPageSelector.ts b/Composer/packages/client/src/recoilModel/selectors/diagnosticsPageSelector.ts index c6acbfba0e..9fc549c30d 100644 --- a/Composer/packages/client/src/recoilModel/selectors/diagnosticsPageSelector.ts +++ b/Composer/packages/client/src/recoilModel/selectors/diagnosticsPageSelector.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { BotIndexer } from '@bfc/indexers'; +import { BotIndexer, validateSchema } from '@bfc/indexers'; import { selectorFamily, selector } from 'recoil'; import lodashGet from 'lodash/get'; import formatMessage from 'format-message'; @@ -18,6 +18,7 @@ import { BotDiagnostic, SettingDiagnostic, SkillSettingDiagnostic, + SchemaDiagnostic, } from '../../pages/diagnostics/types'; import { botDiagnosticsState, @@ -176,7 +177,35 @@ export const dialogsDiagnosticsSelectorFamily = selectorFamily({ }); }); - return []; + return diagnosticList; + }, +}); + +export const schemaDiagnosticsSelectorFamily = selectorFamily({ + key: 'schemaDiagnosticsSelectorFamily', + get: (projectId: string) => ({ get }) => { + const botAssets = get(botAssetsSelectFamily(projectId)); + if (botAssets === null) return []; + + const rootProjectId = get(rootBotProjectIdSelector) ?? projectId; + + /** + * `botAssets.dialogSchema` contains all *.schema files loaded by project indexer. However, it actually messes up sdk.schema and *.dialog.schema. + * To get the correct sdk.schema content, current workaround is to filter schema by id. + * + * TODO: To fix it entirely, we need to differentiate dialog.schema from sdk.schema in indexer. + */ + const sdkSchemaContent = botAssets.dialogSchemas.find((d) => d.id === '')?.content; + if (!sdkSchemaContent) return []; + + const fullDiagnostics: DiagnosticInfo[] = []; + botAssets.dialogs.forEach((dialog) => { + const diagnostics = validateSchema(dialog.id, dialog.content, sdkSchemaContent); + fullDiagnostics.push( + ...diagnostics.map((d) => new SchemaDiagnostic(rootProjectId, projectId, dialog.id, `${dialog.id}.dialog`, d)) + ); + }); + return fullDiagnostics; }, }); @@ -257,6 +286,7 @@ export const diagnosticsSelectorFamily = selectorFamily({ ...get(luDiagnosticsSelectorFamily(projectId)), ...get(lgDiagnosticsSelectorFamily(projectId)), ...get(qnaDiagnosticsSelectorFamily(projectId)), + ...get(schemaDiagnosticsSelectorFamily(projectId)), ], }); diff --git a/Composer/packages/lib/indexers/__tests__/validations/schemaValidation/__mocks__/dialogMocks.ts b/Composer/packages/lib/indexers/__tests__/validations/schemaValidation/__mocks__/dialogMocks.ts new file mode 100644 index 0000000000..d3c7c1a8df --- /dev/null +++ b/Composer/packages/lib/indexers/__tests__/validations/schemaValidation/__mocks__/dialogMocks.ts @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export const sendActivityStub = { + $kind: 'Microsoft.SendActivity', + $designer: { + id: 'AwT1u7', + }, +}; + +export const switchConditionStub = { + $kind: 'Microsoft.SwitchCondition', + $designer: { + id: 'sJzdQm', + }, + default: [sendActivityStub], + cases: [ + { + value: 'case1', + actions: [sendActivityStub], + }, + ], +}; + +export const onConversationUpdateActivityStub = { + $kind: 'Microsoft.OnConversationUpdateActivity', + $designer: { + id: '376720', + }, + actions: [switchConditionStub], +}; + +export const simpleGreetingDialog: any = { + $kind: 'Microsoft.AdaptiveDialog', + $designer: { + $designer: { + name: 'EmptyBot-1', + description: '', + id: '47yxe0', + }, + }, + autoEndDialog: true, + defaultResultProperty: 'dialog.result', + triggers: [onConversationUpdateActivityStub], + $schema: + 'https://raw.githubusercontent.com/microsoft/BotFramework-Composer/stable/Composer/packages/server/schemas/sdk.schema', + generator: 'EmptyBot-1.lg', + id: 'EmptyBot-1', + recognizer: 'EmptyBot-1.lu.qna', +}; diff --git a/Composer/packages/lib/indexers/__tests__/validations/schemaValidation/__mocks__/sdkSchema.ts b/Composer/packages/lib/indexers/__tests__/validations/schemaValidation/__mocks__/sdkSchema.ts new file mode 100644 index 0000000000..de22165971 --- /dev/null +++ b/Composer/packages/lib/indexers/__tests__/validations/schemaValidation/__mocks__/sdkSchema.ts @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { SDKKinds } from '@botframework-composer/types'; + +import { + AdaptiveDialogSchema, + IfConditionSchema, + OnConvUpdateSchema, + OnDialogEventSchema, + SwitchConditionSchema, +} from './sdkSchemaMocks'; + +export const sdkSchemaDefinitionMock = { + [SDKKinds.SwitchCondition]: SwitchConditionSchema, + [SDKKinds.IfCondition]: IfConditionSchema, + [SDKKinds.AdaptiveDialog]: AdaptiveDialogSchema, + [SDKKinds.OnDialogEvent]: OnDialogEventSchema, + [SDKKinds.OnConversationUpdateActivity]: OnConvUpdateSchema, +}; diff --git a/Composer/packages/lib/indexers/__tests__/validations/schemaValidation/__mocks__/sdkSchemaMocks.ts b/Composer/packages/lib/indexers/__tests__/validations/schemaValidation/__mocks__/sdkSchemaMocks.ts new file mode 100644 index 0000000000..e13b92d786 --- /dev/null +++ b/Composer/packages/lib/indexers/__tests__/validations/schemaValidation/__mocks__/sdkSchemaMocks.ts @@ -0,0 +1,269 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { JSONSchema7 } from '@botframework-composer/types'; + +export const AdaptiveDialogSchema: JSONSchema7 = { + $schema: 'https://schemas.botframework.com/schemas/component/v1.0/component.schema', + $role: 'implements(Microsoft.IDialog)', + title: 'Adaptive dialog', + description: 'Flexible, data driven dialog that can adapt to the conversation.', + type: 'object', + properties: { + id: { + type: 'string', + pattern: '^(?!(=)).*', + title: 'Id', + description: 'Optional dialog ID.', + }, + autoEndDialog: { + $ref: 'schema:#/definitions/booleanExpression', + title: 'Auto end dialog', + description: + 'If set to true the dialog will automatically end when there are no further actions. If set to false, remember to manually end the dialog using EndDialog action.', + default: true, + }, + defaultResultProperty: { + type: 'string', + title: 'Default result property', + description: 'Value that will be passed back to the parent dialog.', + default: 'dialog.result', + }, + dialogs: { + type: 'array', + title: 'Dialogs added to DialogSet', + items: { + $kind: 'Microsoft.IDialog', + title: 'Dialog', + description: 'Dialogs will be added to DialogSet.', + }, + }, + recognizer: { + $kind: 'Microsoft.IRecognizer', + title: 'Recognizer', + description: 'Input recognizer that interprets user input into intent and entities.', + }, + generator: { + $kind: 'Microsoft.ILanguageGenerator', + title: 'Language generator', + description: 'Language generator that generates bot responses.', + }, + selector: { + $kind: 'Microsoft.ITriggerSelector', + title: 'Selector', + description: "Policy to determine which trigger is executed. Defaults to a 'best match' selector (optional).", + }, + triggers: { + type: 'array', + description: 'List of triggers defined for this dialog.', + title: 'Triggers', + items: { + $kind: 'Microsoft.ITrigger', + title: 'Event triggers', + description: 'Event triggers for handling events.', + }, + }, + schema: { + title: 'Schema', + description: 'Schema to fill in.', + anyOf: [ + { + $ref: 'http://json-schema.org/draft-07/schema#', + }, + { + type: 'string', + title: 'Reference to JSON schema', + description: 'Reference to JSON schema .dialog file.', + }, + ], + }, + }, +}; + +export const IfConditionSchema: JSONSchema7 = { + $schema: 'https://schemas.botframework.com/schemas/component/v1.0/component.schema', + $role: 'implements(Microsoft.IDialog)', + title: 'If condition', + description: 'Two-way branch the conversation flow based on a condition.', + type: 'object', + required: ['condition'], + properties: { + id: { + type: 'string', + title: 'Id', + description: 'Optional id for the dialog', + }, + condition: { + $ref: 'schema:#/definitions/condition', + title: 'Condition', + description: 'Expression to evaluate.', + examples: ['user.age > 3'], + }, + disabled: { + $ref: 'schema:#/definitions/booleanExpression', + title: 'Disabled', + description: 'Optional condition which if true will disable this action.', + examples: [true, '=user.age > 3'], + }, + actions: { + type: 'array', + items: { + $kind: 'Microsoft.IDialog', + }, + title: 'Actions', + description: 'Actions to execute if condition is true.', + }, + elseActions: { + type: 'array', + items: { + $kind: 'Microsoft.IDialog', + }, + title: 'Else', + description: 'Actions to execute if condition is false.', + }, + }, +}; + +export const OnConvUpdateSchema: JSONSchema7 = { + $schema: 'https://schemas.botframework.com/schemas/component/v1.0/component.schema', + $role: ['implements(Microsoft.ITrigger)', 'extends(Microsoft.OnCondition)'], + title: 'On ConversationUpdate activity', + description: "Actions to perform on receipt of an activity with type 'ConversationUpdate'.", + type: 'object', + required: [], +}; +export const OnDialogEventSchema: JSONSchema7 = { + $schema: 'https://schemas.botframework.com/schemas/component/v1.0/component.schema', + $role: ['implements(Microsoft.ITrigger)', 'extends(Microsoft.OnCondition)'], + title: 'On dialog event', + description: 'Actions to perform when a specific dialog event occurs.', + type: 'object', + properties: { + event: { + type: 'string', + title: 'Dialog event name', + description: 'Name of dialog event.', + }, + }, + required: ['event'], +}; + +export const SwitchConditionSchema: JSONSchema7 = { + $schema: 'https://schemas.botframework.com/schemas/component/v1.0/component.schema', + $role: 'implements(Microsoft.IDialog)', + title: 'Switch condition', + description: 'Execute different actions based on the value of a property.', + type: 'object', + required: ['condition'], + properties: { + id: { + type: 'string', + title: 'Id', + description: 'Optional id for the dialog', + }, + condition: { + $ref: 'schema:#/definitions/stringExpression', + title: 'Condition', + description: 'Property to evaluate.', + examples: ['user.favColor'], + }, + disabled: { + $ref: 'schema:#/definitions/booleanExpression', + title: 'Disabled', + description: 'Optional condition which if true will disable this action.', + examples: [true, '=user.age > 3'], + }, + cases: { + type: 'array', + title: 'Cases', + description: 'Actions for each possible condition.', + items: { + type: 'object', + title: 'Case', + description: 'Case and actions.', + properties: { + value: { + type: ['number', 'integer', 'boolean', 'string'], + title: 'Value', + description: 'The value to compare the condition with.', + examples: ['red', 'true', '13'], + }, + actions: { + type: 'array', + items: { + $kind: 'Microsoft.IDialog', + }, + title: 'Actions', + description: 'Actions to execute.', + }, + }, + required: ['value'], + }, + }, + default: { + type: 'array', + items: { + $kind: 'Microsoft.IDialog', + }, + title: 'Default', + description: 'Actions to execute if none of the cases meet the condition.', + }, + }, +}; + +export const SetPropertiesSchema: JSONSchema7 = { + "$schema": "https://schemas.botframework.com/schemas/component/v1.0/component.schema", + "$role": "implements(Microsoft.IDialog)", + "title": "Set property", + "description": "Set one or more property values.", + "type": "object", + "required": [ + "assignments" + ], + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "schema:#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "assignments": { + "type": "array", + "title": "Assignments", + "description": "Property value assignments to set.", + "items": { + "type": "object", + "title": "Assignment", + "description": "Property assignment.", + "properties": { + "property": { + "$ref": "schema:#/definitions/stringExpression", + "title": "Property", + "description": "Property (named location to store information).", + "examples": [ + "user.age" + ] + }, + "value": { + "$ref": "schema:#/definitions/valueExpression", + "title": "Value", + "description": "New value or expression.", + "examples": [ + "='milk'", + "=dialog.favColor", + "=dialog.favColor == 'red'" + ] + } + } + } + } + } +} diff --git a/Composer/packages/lib/indexers/__tests__/validations/schemaValidation/schemaUtils.test.ts b/Composer/packages/lib/indexers/__tests__/validations/schemaValidation/schemaUtils.test.ts new file mode 100644 index 0000000000..8020e3eaa1 --- /dev/null +++ b/Composer/packages/lib/indexers/__tests__/validations/schemaValidation/schemaUtils.test.ts @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { discoverNestedPaths, isTrigger } from '../../../src/validations/schemaValidation/schemaUtils'; + +import { onConversationUpdateActivityStub, simpleGreetingDialog, switchConditionStub } from './__mocks__/dialogMocks'; +import { + AdaptiveDialogSchema, + IfConditionSchema, + OnConvUpdateSchema, + OnDialogEventSchema, + SetPropertiesSchema, + SwitchConditionSchema, +} from './__mocks__/sdkSchemaMocks'; + +describe('#schemaUtils', () => { + it('isTrigger() should recognizer trigger schema.', () => { + expect(isTrigger(OnDialogEventSchema)).toBeTruthy(); + expect(isTrigger(IfConditionSchema)).toBeFalsy(); + expect(isTrigger(AdaptiveDialogSchema)).toBeFalsy(); + }); + + it('discoverNestedProperties() should find correct property names.', () => { + expect(discoverNestedPaths(simpleGreetingDialog, AdaptiveDialogSchema)).toEqual( + expect.arrayContaining(['triggers']) + ); + expect(discoverNestedPaths(onConversationUpdateActivityStub, OnConvUpdateSchema)).toEqual( + expect.arrayContaining(['actions']) + ); + expect(discoverNestedPaths(switchConditionStub, SwitchConditionSchema)).toEqual( + expect.arrayContaining(['cases[0].actions', 'default']) + ); + }); + + it('disconverNestedProperties() should defense invalid input.', () => { + const setPropertiesStub = { + $kind: 'Microsoft.SetProperties', + $designer: { + id: 'sJzdQm', + }, + assignments: [ + { property: 'username', value: 'test' } + ] + }; + + expect(discoverNestedPaths(setPropertiesStub, SetPropertiesSchema)).toEqual([]); + }) +}); diff --git a/Composer/packages/lib/indexers/__tests__/validations/schemaValidation/walkAdaptiveDialog.test.ts b/Composer/packages/lib/indexers/__tests__/validations/schemaValidation/walkAdaptiveDialog.test.ts new file mode 100644 index 0000000000..9ecb75e4f9 --- /dev/null +++ b/Composer/packages/lib/indexers/__tests__/validations/schemaValidation/walkAdaptiveDialog.test.ts @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { SDKKinds } from '@botframework-composer/types'; + +import { walkAdaptiveDialog } from '../../../src/validations/schemaValidation/walkAdaptiveDialog'; + +import { simpleGreetingDialog } from './__mocks__/dialogMocks'; +import { sdkSchemaDefinitionMock } from './__mocks__/sdkSchema'; + +describe('visitAdaptiveDialog', () => { + it('should visit every adaptive elements in `simpleGreeting`', () => { + const result: any = {}; + walkAdaptiveDialog(simpleGreetingDialog, sdkSchemaDefinitionMock, ($kind, _, path) => { + result[path] = $kind; + return true; + }); + expect(result).toEqual( + expect.objectContaining({ + '': SDKKinds.AdaptiveDialog, + 'triggers[0]': SDKKinds.OnConversationUpdateActivity, + 'triggers[0].actions[0]': SDKKinds.SwitchCondition, + 'triggers[0].actions[0].default[0]': SDKKinds.SendActivity, + 'triggers[0].actions[0].cases[0].actions[0]': SDKKinds.SendActivity, + }) + ); + }); +}); diff --git a/Composer/packages/lib/indexers/src/validations/index.ts b/Composer/packages/lib/indexers/src/validations/index.ts index d88ead354e..cc4b6a7919 100644 --- a/Composer/packages/lib/indexers/src/validations/index.ts +++ b/Composer/packages/lib/indexers/src/validations/index.ts @@ -69,3 +69,5 @@ export function validateDialog( return { diagnostics: [new Diagnostic(error.message, id)], cache }; } } + +export { validateSchema } from './schemaValidation'; diff --git a/Composer/packages/lib/indexers/src/validations/schemaValidation/index.ts b/Composer/packages/lib/indexers/src/validations/schemaValidation/index.ts new file mode 100644 index 0000000000..081d23f40c --- /dev/null +++ b/Composer/packages/lib/indexers/src/validations/schemaValidation/index.ts @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +import { Diagnostic } from '@bfc/shared'; +import formatMessage from 'format-message'; +import { BaseSchema, DiagnosticSeverity, SchemaDefinitions } from '@botframework-composer/types'; + +import { walkAdaptiveDialog } from './walkAdaptiveDialog'; + +const SCHEMA_NOT_FOUND = formatMessage('Schema definition not found in sdk.schema.'); + +export const validateSchema = (dialogId: string, dialogData: BaseSchema, schema: SchemaDefinitions): Diagnostic[] => { + const diagnostics: Diagnostic[] = []; + const schemas: any = schema.definitions ?? {}; + + walkAdaptiveDialog(dialogData, schemas, ($kind, data, path) => { + if (!schemas[$kind]) { + diagnostics.push( + new Diagnostic(`${$kind}: ${SCHEMA_NOT_FOUND}`, `${dialogId}.dialog`, DiagnosticSeverity.Error, path) + ); + } + return true; + }); + + return diagnostics; +}; diff --git a/Composer/packages/lib/indexers/src/validations/schemaValidation/schemaUtils.ts b/Composer/packages/lib/indexers/src/validations/schemaValidation/schemaUtils.ts new file mode 100644 index 0000000000..efe220d6f7 --- /dev/null +++ b/Composer/packages/lib/indexers/src/validations/schemaValidation/schemaUtils.ts @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { BaseSchema, JSONSchema7 } from '@botframework-composer/types'; +import get from 'lodash/get'; + +export const isTrigger = (schema: JSONSchema7): boolean => { + const roles = typeof schema.$role === 'string' ? [schema.$role] : schema.$role ?? []; + + return roles.some((roleString) => { + return roleString.indexOf('implements(Microsoft.ITrigger)') > -1; + }); +}; + +const triggerNesterProperties = ['actions']; + +const propertyDefinesActionArray = (propertyDefinition: JSONSchema7): boolean => { + if (!propertyDefinition) { + return false; + } + const { type, items } = propertyDefinition; + return type === 'array' && Boolean(get(items, '$kind')); +}; + +const discoverNestedSchemaPaths = (data: BaseSchema, schema: JSONSchema7): string[] => { + if (isTrigger(schema)) return triggerNesterProperties; + if (!schema.properties) return []; + + const nestedPaths: string[] = []; + + const entries = Object.entries(schema.properties); + for (const entry of entries) { + const [propertyName, propertyDef] = entry; + /** + * Discover child elements (triggers, actions). For example: + * 1. In Microsoft.IfCondition.schema + * ```json + * properties.actions = { + * "type": "array", + * "items": { + * "$kind": "Microsoft.IDialog" + * }, + * "title": "Actions", + * "description": "Actions to execute if condition is true." + * } + * ``` + * Returns ["actions"]. + * + * 2. In Microsoft.AdaptiveDialog.schema + * ```json + * properties.triggers = { + * "type": "array", + * "description": "List of triggers defined for this dialog.", + * "title": "Triggers", + * "items": { + * "$kind": "Microsoft.ITrigger", + * "title": "Event triggers", + * "description": "Event triggers for handling events." + * } + * } + * ``` + * Returns ["triggers"]. + */ + const propertyData = get(data, propertyName); + if (!Array.isArray(propertyData) || !propertyData.length) continue; + + const isSchemaNested = propertyDefinesActionArray(propertyDef); + const dataContainsAction = Boolean(propertyData[0].$kind); + + if (isSchemaNested && dataContainsAction) { + nestedPaths.push(propertyName); + continue; + } + + /** + * Discover skip-level child elements. + * Currently, this logic can only handle skip-level child actions under the 'actions' field. + * To discover all possible actions under arbitrary levels / field names, needs to traverse the schema tree. + * + * Example: (Reference to SwitchCondition.schema: https://github.com/microsoft/botbuilder-dotnet/blob/main/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/Schemas/Actions/Microsoft.SwitchCondition.schema) + * properties.cases.items.properties = { + * "value": { ... }, + * "actions": { // Discover this property + * "type": "array", + * "items": { + * "$kind": "Microsoft.IDialog" + * }, + * "title": "Actions", + * "description": "Actions to execute." + * } + * } + */ + const actionsDefUnderItems = get(propertyDef, 'items.properties.actions'); + const schemaHasSkipLevelActions = + propertyDef?.type === 'array' && + Boolean(actionsDefUnderItems) && + propertyDefinesActionArray(actionsDefUnderItems); + + if (schemaHasSkipLevelActions) { + propertyData.forEach((caseData, caseIndex) => { + const caseActions = caseData.actions; + if (!Array.isArray(caseActions) || !caseActions.length) return; + + for (let i = 0; i < caseActions.length; i++) { + nestedPaths.push(`${propertyName}[${caseIndex}].actions`); + } + }); + } + } + + return nestedPaths; +}; + +export const discoverNestedPaths = (data: BaseSchema, schema: JSONSchema7): string[] => { + try { + return discoverNestedSchemaPaths(data, schema); + } catch (e) { + // Met potential schema visit bugs + return []; + } +}; diff --git a/Composer/packages/lib/indexers/src/validations/schemaValidation/walkAdaptiveDialog.ts b/Composer/packages/lib/indexers/src/validations/schemaValidation/walkAdaptiveDialog.ts new file mode 100644 index 0000000000..1b7d89e19f --- /dev/null +++ b/Composer/packages/lib/indexers/src/validations/schemaValidation/walkAdaptiveDialog.ts @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { BaseSchema, SchemaDefinitions, SDKKinds } from '@botframework-composer/types'; +import get from 'lodash/get'; + +import { discoverNestedPaths } from './schemaUtils'; + +// returns true to continue the visit. +type VisitAdaptiveComponentFn = ( + $kind: SDKKinds | string, + data: BaseSchema, + currentPath: string, + parentPath: string +) => boolean; + +export const walkAdaptiveDialog = ( + adaptiveDialog: BaseSchema, + sdkSchema: SchemaDefinitions, + fn: VisitAdaptiveComponentFn +): boolean => { + return walkWithPath(adaptiveDialog, sdkSchema, '', '', fn); +}; + +const joinPath = (parentPath: string, currentKey: string | number): string => { + if (typeof currentKey === 'string') { + return parentPath ? `${parentPath}.${currentKey}` : currentKey; + } + + if (typeof currentKey === 'number') { + return parentPath ? `${parentPath}[${currentKey}]` : `[${currentKey}]`; + } + + return ''; +}; + +const walkWithPath = ( + adaptiveData: BaseSchema, + sdkSchema: SchemaDefinitions, + currentPath: string, + parentPath: string, + fn: VisitAdaptiveComponentFn +): boolean => { + const { $kind } = adaptiveData; + // Visit current data before schema validation to make sure all $kind blocks are visited. + fn($kind, adaptiveData, currentPath, parentPath); + + const schema = sdkSchema[$kind]; + const nestedPaths = schema ? discoverNestedPaths(adaptiveData, schema) : adaptiveData.actions ? ['actions'] : []; + if (nestedPaths.length === 0) return true; + + /** + * Examples of nested properties in built-in $kinds: + * 1. ['actions'] in every Trigger $kind + * 2. ['actions'] in Foreach, ForeachPage + * 3. ['actions', 'elseActions'] in IfCondition + * 4. ['cases', 'default'] in SwitchCondition + */ + for (const path of nestedPaths) { + const childElements = get(adaptiveData, path); + if (!Array.isArray(childElements)) { + continue; + } + + /** + * Visit nested adaptive elements. For example: + * 1. Triggers under Dialog; + * 2. Actions under Trigger; + * 3. Actions under Action. + */ + for (let i = 0; i < childElements.length; i++) { + const shouldContinue = walkWithPath( + childElements[i], + sdkSchema, + joinPath(currentPath, `${path}[${i}]`), + currentPath, + fn + ); + if (!shouldContinue) return false; + } + } + + return true; +}; diff --git a/Composer/packages/server-workers/src/workers/templateInstallation.worker.ts b/Composer/packages/server-workers/src/workers/templateInstallation.worker.ts index 7ab87d04b6..3955a64dea 100644 --- a/Composer/packages/server-workers/src/workers/templateInstallation.worker.ts +++ b/Composer/packages/server-workers/src/workers/templateInstallation.worker.ts @@ -32,14 +32,15 @@ const instantiateRemoteTemplate = async ( dstDir: string, projectName: string, runtimeType: string, - runtimeLanguage: string + runtimeLanguage: string, + yeomanOptions: any ): Promise => { log('About to instantiate a template!', dstDir, generatorName, projectName); yeomanEnv.cwd = dstDir; try { // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore @types/yeoman-environment is outdated - await yeomanEnv.run([generatorName, projectName, '-p', runtimeLanguage, '-i', runtimeType]); + await yeomanEnv.run([generatorName, projectName, '-p', runtimeLanguage, '-i', runtimeType], yeomanOptions); log('Template successfully instantiated', dstDir, generatorName, projectName); } catch (err) { log('Template failed to instantiate', dstDir, generatorName, projectName); @@ -54,7 +55,8 @@ const yeomanWork = async ( projectName: string, templateGeneratorPath: string, runtimeType: string, - runtimeLanguage: string + runtimeLanguage: string, + yeomanOptions: any ) => { const generatorName = npmPackageName.toLowerCase().replace('generator-', ''); // create yeoman environment @@ -75,7 +77,15 @@ const yeomanWork = async ( log('Instantiating Yeoman template'); parentPort?.postMessage({ status: 'Creating project' }); - await instantiateRemoteTemplate(yeomanEnv, generatorName, dstDir, projectName, runtimeType, runtimeLanguage); + await instantiateRemoteTemplate( + yeomanEnv, + generatorName, + dstDir, + projectName, + runtimeType, + runtimeLanguage, + yeomanOptions + ); }; export type TemplateInstallationArgs = { @@ -86,6 +96,7 @@ export type TemplateInstallationArgs = { templateGeneratorPath: string; runtimeType: string; runtimeLanguage: string; + yeomanOptions?: any; }; if (!isMainThread) { @@ -96,7 +107,8 @@ if (!isMainThread) { workerData.projectName, workerData.templateGeneratorPath, workerData.runtimeType, - workerData.runtimeLanguage + workerData.runtimeLanguage, + workerData.yeomanOptions ) .then(() => { process.exit(0); diff --git a/Composer/packages/server/src/controllers/asset.ts b/Composer/packages/server/src/controllers/asset.ts index dcffd3869d..553267bc29 100644 --- a/Composer/packages/server/src/controllers/asset.ts +++ b/Composer/packages/server/src/controllers/asset.ts @@ -73,6 +73,18 @@ export async function getProjTemplatesV2(req: any, res: any) { } } +export async function getLatestGeneratorVersion(moduleName: string): Promise { + try { + const moduleURL = `https://registry.npmjs.org/${moduleName}`; + const response = await fetch(moduleURL); + const data = await response.json(); + return data['dist-tags'].latest || '*'; + } catch (err) { + log('Could not retrieve latest generator version', err); + return '*'; + } +} + export async function getTemplateReadMe(req: any, res: any) { try { const moduleName = req.query?.moduleName; diff --git a/Composer/packages/server/src/controllers/project.ts b/Composer/packages/server/src/controllers/project.ts index ff663785d0..5a181274f2 100644 --- a/Composer/packages/server/src/controllers/project.ts +++ b/Composer/packages/server/src/controllers/project.ts @@ -624,6 +624,14 @@ function createProjectV2(req: Request, res: Response) { }); } +function migrateProject(req: Request, res: Response) { + const jobId = BackgroundProcessManager.startProcess(202, 'create', 'Migrating Bot Project'); + BotProjectService.migrateProjectAsync(req, jobId); + res.status(202).json({ + jobId: jobId, + }); +} + async function getVariablesByProjectId(req: Request, res: Response) { const projectId = req.params.projectId; const user = await ExtensionContext.getUserFromRequest(req); @@ -661,6 +669,7 @@ export const ProjectController = { saveProjectAs, createProject, createProjectV2, + migrateProject, getAllProjects, getRecentProjects, getFeed, diff --git a/Composer/packages/server/src/locales/en-US.json b/Composer/packages/server/src/locales/en-US.json index c7d839e1d2..83aeb1dd01 100644 --- a/Composer/packages/server/src/locales/en-US.json +++ b/Composer/packages/server/src/locales/en-US.json @@ -887,6 +887,12 @@ "conversationupdate_activity_9e94bff5": { "message": "ConversationUpdate activity" }, + "convert_583eb59d": { + "message": "Convert" + }, + "convert_your_project_to_the_latest_format_a28e824c": { + "message": "Convert your project to the latest format" + }, "copy_9748f9f": { "message": "Copy" }, @@ -2348,6 +2354,9 @@ "microsoft_s_templates_offer_best_practices_for_dev_7793c3be": { "message": "Microsoft''s templates offer best practices for developing conversational bots." }, + "migrating_data_a35b3055": { + "message": "Migrating data" + }, "migrating_to_composer_bc304b5d": { "message": "Migrating to Composer" }, @@ -3143,6 +3152,9 @@ "runtime_config_a2904ff9": { "message": "Runtime Config" }, + "runtime_language_da49617a": { + "message": "Runtime Language" + }, "runtime_log_9069fda7": { "message": "Runtime log." }, @@ -3167,6 +3179,9 @@ "schema_24739a48": { "message": "Schema" }, + "schema_definition_not_found_in_sdk_schema_1102ce9b": { + "message": "Schema definition not found in sdk.schema." + }, "schemaid_doesn_t_exists_select_an_schema_to_edit_o_9cccc954": { "message": "{ schemaId } doesn''t exists, select an schema to edit or create a new one" }, @@ -3770,6 +3785,9 @@ "this_option_allows_your_users_to_give_multiple_val_d2dd0d58": { "message": "This option allows your users to give multiple values for this property." }, + "this_project_was_created_in_an_older_version_of_co_8b57954": { + "message": "This project was created in an older version of Composer. To open this project in Composer 2.0, we must copy your project and convert it to the latest format. Your original project will not be changed." + }, "this_publishing_profile_profilename_is_no_longer_s_eee0f447": { "message": "This publishing profile ({ profileName }) is no longer supported. You are a member of multiple Azure tenants and the profile needs to have a tenant id associated with it. You can either edit the profile by adding the `tenantId` property to it''s configuration or create a new one." }, diff --git a/Composer/packages/server/src/models/asset/assetManager.ts b/Composer/packages/server/src/models/asset/assetManager.ts index 96ce187b5f..6e2c298f86 100644 --- a/Composer/packages/server/src/models/asset/assetManager.ts +++ b/Composer/packages/server/src/models/asset/assetManager.ts @@ -99,6 +99,7 @@ export class AssetManager { jobId: string, runtimeType: RuntimeType, runtimeLanguage: FeedName, + yeomanOptions?: any, user?: UserIdentity ): Promise { try { @@ -125,6 +126,7 @@ export class AssetManager { templateGeneratorPath, runtimeType, runtimeLanguage, + yeomanOptions, }, (status, msg) => { BackgroundProcessManager.updateProcess(jobId, status, msg); diff --git a/Composer/packages/server/src/models/bot/botProject.ts b/Composer/packages/server/src/models/bot/botProject.ts index b25741e8ea..d1d0c62532 100644 --- a/Composer/packages/server/src/models/bot/botProject.ts +++ b/Composer/packages/server/src/models/bot/botProject.ts @@ -476,6 +476,26 @@ export class BotProject implements IBotProject { return await this._createFile(relativePath, content); }; + public migrateFile = async (name: string, content = '', rootDialogId) => { + const filename = name.trim(); + this.validateFileName(filename); + this._validateFileContent(name, content); + const botName = this.name; + const defaultLocale = this.settings?.defaultLanguage || defaultLanguage; + + // 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 relativePath = defaultFilePath(botName, defaultLocale, filename, { endpoint, rootDialogId }); + const file = this.files.get(filename); + if (file) { + throw new Error(`${filename} dialog already exist`); + } + + return await this._createFile(relativePath, content); + }; + public createManifestLuFile = async (name: string, content = '') => { const filename = name.trim(); this.validateFileName(filename); diff --git a/Composer/packages/server/src/models/bot/botStructure.ts b/Composer/packages/server/src/models/bot/botStructure.ts index 1d5fb3de7b..b3f07e2240 100644 --- a/Composer/packages/server/src/models/bot/botStructure.ts +++ b/Composer/packages/server/src/models/bot/botStructure.ts @@ -14,6 +14,7 @@ const BotStructureTemplate = { qna: 'knowledge-base/${LOCALE}/${BOTNAME}.${LOCALE}.qna', sourceQnA: 'knowledge-base/source/${FILENAME}.source.${LOCALE}.qna', dialogSchema: '${BOTNAME}.dialog.schema', + schemas: 'schemas/${FILENAME}', schema: '${FILENAME}', settings: 'settings/${FILENAME}', common: { @@ -62,7 +63,6 @@ const parseSourceFileName = (name: string) => { } else { fileId = id; } - return { fileId, dialogId, fileType, locale }; }; @@ -122,7 +122,8 @@ export const parseFileName = (name: string, defaultLocale: string) => { export const isRecognizer = (fileName: string) => fileName.endsWith('.lu.dialog') || fileName.endsWith('.qna.dialog'); export const isCrossTrainConfig = (fileName: string) => fileName.endsWith('cross-train.config.json'); - +export const isSchema = (fileName: string) => + (fileName.startsWith('sdk') || fileName.startsWith('app')) && fileName.endsWith('schema'); // catch sdk.schema and sdk.uischema export const defaultManifestFilePath = (botName: string, fileName: string): string => { return templateInterpolate(BotStructureTemplate.manifestLu, { BOTNAME: botName, @@ -146,6 +147,12 @@ export const defaultFilePath = ( const { fileId, locale, fileType, dialogId } = parseFileName(filename, defaultLocale); const LOCALE = locale; + if (isSchema(filename)) { + return templateInterpolate(Path.join(endpoint, BotStructureTemplate.schemas), { + FILENAME: filename, + }); + } + // now recognizer extension is .lu.dialog or .qna.dialog if (isRecognizer(filename)) { const dialogId = filename.split('.')[0]; @@ -181,6 +188,12 @@ export const defaultFilePath = ( }); } + if (fileType === FileExtensions.BotProject) { + return templateInterpolate(BotStructureTemplate.botProject, { + BOTNAME: rootDialogId, + }); + } + if (fileType === FileExtensions.FormDialogSchema) { return templateInterpolate(BotStructureTemplate.formDialogs, { FORMDIALOGNAME: filename, @@ -195,7 +208,7 @@ export const defaultFilePath = ( } const DIALOGNAME = dialogId; - const isRootFile = BOTNAME === DIALOGNAME.toLowerCase(); + const isRootFile = BOTNAME === DIALOGNAME.toLowerCase() || rootDialogId.toLowerCase() === DIALOGNAME.toLowerCase(); if (fileType === `.source.${locale}.qna`) { if (endpoint) { diff --git a/Composer/packages/server/src/router/api.ts b/Composer/packages/server/src/router/api.ts index 0ace4beded..ed7b6727e1 100644 --- a/Composer/packages/server/src/router/api.ts +++ b/Composer/packages/server/src/router/api.ts @@ -27,6 +27,7 @@ import { UtilitiesController } from './../controllers/utilities'; const router: Router = express.Router({}); router.post('/projects', ProjectController.createProject); +router.post('/v2/projects/migrate', ProjectController.migrateProject); router.post('/v2/projects', ProjectController.createProjectV2); router.get('/projects', ProjectController.getAllProjects); router.get('/projects/recent', ProjectController.getRecentProjects); diff --git a/Composer/packages/server/src/services/project.ts b/Composer/packages/server/src/services/project.ts index 6bd84b5d3a..fe7bd8fc77 100644 --- a/Composer/packages/server/src/services/project.ts +++ b/Composer/packages/server/src/services/project.ts @@ -7,7 +7,7 @@ import { promisify } from 'util'; import merge from 'lodash/merge'; import find from 'lodash/find'; import flatten from 'lodash/flatten'; -import { luImportResolverGenerator, ResolverResource } from '@bfc/shared'; +import { luImportResolverGenerator, ResolverResource, DialogSetting } from '@bfc/shared'; import extractMemoryPaths from '@bfc/indexers/lib/dialogUtils/extractMemoryPaths'; import { UserIdentity } from '@bfc/extension'; import { ensureDir, existsSync, remove } from 'fs-extra'; @@ -21,6 +21,8 @@ import { Store } from '../store/store'; import log from '../logger'; import { ExtensionContext } from '../models/extension/extensionContext'; import { getLocationRef, getNewProjRef, ejectAndMerge } from '../utility/project'; +import { isSchema } from '../models/bot/botStructure'; +import { getLatestGeneratorVersion } from '../controllers/asset'; import StorageService from './storage'; import { Path } from './../utility/path'; @@ -230,7 +232,8 @@ export class BotProjectService { public static openProject = async ( locationRef: LocationRef, user?: UserIdentity, - isRootBot?: boolean + isRootBot?: boolean, + options?: { allowPartialBots: boolean } ): Promise => { BotProjectService.initialize(); @@ -240,7 +243,10 @@ export class BotProjectService { throw new Error(`file ${locationRef.path} does not exist`); } - if (!(await StorageService.checkIsBotFolder(locationRef.storageId, locationRef.path, user))) { + if ( + !options?.allowPartialBots && + !(await StorageService.checkIsBotFolder(locationRef.storageId, locationRef.path, user)) + ) { throw new Error(`${locationRef.path} is not a bot project folder`); } @@ -459,6 +465,158 @@ export class BotProjectService { } }; + public static async migrateProjectAsync(req: Request, jobId: string) { + const { oldProjectId, name, description, location, storageId, runtimeType, runtimeLanguage } = req.body; + + const user = await ExtensionContext.getUserFromRequest(req); + + try { + const locationRef = getLocationRef(location, storageId, name); + + await BotProjectService.cleanProject(locationRef); + + log('Downloading adaptive generator'); + + // Update status for polling + BackgroundProcessManager.updateProcess(jobId, 202, formatMessage('Getting template')); + const baseGenerator = '@microsoft/generator-bot-adaptive'; + const latestVersion = await getLatestGeneratorVersion(baseGenerator); + + log(`Using version ${latestVersion} of ${baseGenerator} for migration`); + const newProjRef = await AssetService.manager.copyRemoteProjectTemplateToV2( + baseGenerator, + latestVersion, // use the @latest version + name, + locationRef, + jobId, + runtimeType, + runtimeLanguage, + { + applicationSettingsDirectory: 'settings', + }, + user + ); + + // update project ref to point at newly created folder + newProjRef.path = `${newProjRef.path}/${name}`; + + BackgroundProcessManager.updateProcess(jobId, 202, formatMessage('Migrating data')); + + log('Migrating files...'); + + const originalProject = await BotProjectService.getProjectById(oldProjectId, user); + + if (originalProject.settings) { + const originalFiles = originalProject.getProject().files; + + // pass in allowPartialBots = true so that this project can be opened even though + // it doesn't yet have a root dialog... + const id = await BotProjectService.openProject(newProjRef, user, true, { allowPartialBots: true }); + const currentProject = await BotProjectService.getProjectById(id, user); + + // add all original files to new project + for (let f = 0; f < originalFiles.length; f++) { + // exclude the schema files, so we start from scratch + if (!isSchema(originalFiles[f].name)) { + await currentProject.migrateFile( + originalFiles[f].name, + originalFiles[f].content, + originalProject.rootDialogId + ); + } + } + const newSettings: DialogSetting = { + ...currentProject.settings, + runtimeSettings: { + features: { + showTyping: originalProject.settings?.feature?.UseShowTypingMiddleware || false, + useInspection: originalProject.settings?.feature?.UseInspectionMiddleware || false, + removeRecipientMentions: originalProject.settings?.feature?.RemoveRecipientMention || false, + setSpeak: originalProject.settings?.feature?.useSetSpeakMiddleware + ? { voiceFontName: 'en-US-AriaNeural', fallbackToTextForSpeechIfEmpty: true } + : undefined, + blobTranscript: originalProject.settings?.blobStorage?.connectionString + ? originalProject.settings?.blobStorage + : {}, + }, + telemetry: { + options: { instrumentationKey: originalProject.settings?.applicationInsights?.InstrumentationKey }, + }, + skills: { + allowedCallers: originalProject.settings?.skillConfiguration?.allowedCallers, + }, + storage: originalProject.settings?.cosmosDb?.authKey ? 'CosmosDbPartitionedStorage' : undefined, + }, + CosmosDbPartitionedStorage: originalProject.settings?.cosmosDb?.authKey + ? originalProject.settings.cosmosDb + : undefined, + luis: { ...originalProject.settings.luis }, + luFeatures: { ...originalProject.settings.luFeatures }, + publishTargets: originalProject.settings.publishTargets?.map((target) => { + if (target.type === 'azureFunctionsPublish') target.type = 'azurePublish'; + return target; + }), + qna: { ...originalProject.settings.qna }, + downsampling: { ...originalProject.settings.downsampling }, + skill: { ...originalProject.settings.skill }, + speech: { ...originalProject.settings.speech }, + defaultLanguage: originalProject.settings.defaultLanguage, + languages: originalProject.settings.languages, + customFunctions: originalProject.settings.customfunctions, + importedLibraries: [], + MicrosoftAppId: originalProject.settings.MicrosoftAppId, + + runtime: currentProject.settings?.runtime + ? { ...currentProject.settings.runtime } + : { + customRuntime: true, + path: '../', + key: 'adaptive-runtime-dotnet-webapp', + command: `dotnet run --project ${name}.csproj`, + }, + }; + + log('Update settings...'); + + // adjust settings from old format to new format + await currentProject.updateEnvSettings(newSettings); + + log('Copy boilerplate...'); + await AssetService.manager.copyBoilerplate(currentProject.dataDir, currentProject.fileStorage); + + log('Update bot info...'); + await currentProject.updateBotInfo(name, description, true); + + const runtime = ExtensionContext.getRuntimeByProject(currentProject); + const runtimePath = currentProject.getRuntimePath(); + if (runtimePath) { + // install all dependencies and build the app + BackgroundProcessManager.updateProcess(jobId, 202, formatMessage('Building runtime')); + log('Build new runtime...'); + await runtime.build(runtimePath, currentProject); + } + + await ejectAndMerge(currentProject, jobId); + + const project = currentProject.getProject(); + + log('Project created successfully.'); + BackgroundProcessManager.updateProcess(jobId, 200, 'Migrated successfully', { + id, + ...project, + }); + } else { + BackgroundProcessManager.updateProcess(jobId, 500, 'Could not find source project to migrate.'); + } + } catch (err) { + BackgroundProcessManager.updateProcess(jobId, 500, err instanceof Error ? err.message : err, err); + TelemetryService.trackEvent('CreateNewBotProjectCompleted', { + template: '@microsoft/generator-microsoft-bot-adaptive', + status: 500, + }); + } + } + public static async createProjectAsync(req: Request, jobId: string) { const { templateId, @@ -520,6 +678,7 @@ export class BotProjectService { jobId, runtimeType, runtimeLanguage, + null, user );