diff --git a/Composer/packages/client/src/components/BotConvertDialog.tsx b/Composer/packages/client/src/components/BotConvertDialog.tsx
new file mode 100644
index 0000000000..b138a10853
--- /dev/null
+++ b/Composer/packages/client/src/components/BotConvertDialog.tsx
@@ -0,0 +1,43 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/** @jsx jsx */
+import { jsx, css } from '@emotion/core';
+import { OpenConfirmModal } from '@bfc/ui-shared';
+import formatMessage from 'format-message';
+import { Link } from 'office-ui-fabric-react/lib/Link';
+
+const contentContainer = css`
+ max-width: 444px;
+`;
+
+export const BotConvertConfirmDialog = (showSubContent: boolean) => {
+ return OpenConfirmModal(formatMessage('Convert your project to the latest format'), '', {
+ confirmText: formatMessage('Convert'),
+ onRenderContent: () => (
+
+
+ {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.'
+ )}
+
+ {showSubContent && (
+
+ {formatMessage.rich(
+ 'If you have created custom components, you might need to rebuild them. Learn more about custom components. ',
+ {
+ a: ({ children }) => (
+
+ {children}
+
+ ),
+ }
+ )}
+
+ )}
+
+ ),
+ });
+};
diff --git a/Composer/packages/client/src/components/GetStarted/GetStartedNextSteps.tsx b/Composer/packages/client/src/components/GetStarted/GetStartedNextSteps.tsx
index d557d0a436..06e6476e31 100644
--- a/Composer/packages/client/src/components/GetStarted/GetStartedNextSteps.tsx
+++ b/Composer/packages/client/src/components/GetStarted/GetStartedNextSteps.tsx
@@ -4,7 +4,7 @@
/** @jsx jsx */
import { jsx } from '@emotion/core';
import React, { useEffect, useState, useMemo } from 'react';
-import { useRecoilValue } from 'recoil';
+import { useRecoilValue, useSetRecoilState } from 'recoil';
import formatMessage from 'format-message';
import { TeachingBubble } from 'office-ui-fabric-react/lib/TeachingBubble';
import { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane';
@@ -12,7 +12,7 @@ import { DisplayMarkdownDialog } from '@bfc/ui-shared';
import TelemetryClient from '../../telemetry/TelemetryClient';
import { localBotsDataSelector } from '../../recoilModel/selectors/project';
-import { currentProjectIdState } from '../../recoilModel';
+import { currentProjectIdState, schemaDiagnosticsSelectorFamily } from '../../recoilModel';
import { ManageLuis } from '../ManageLuis/ManageLuis';
import { ManageQNA } from '../ManageQNA/ManageQNA';
import { dispatcherState, settingsState } from '../../recoilModel';
@@ -20,7 +20,7 @@ import { mergePropertiesManagedByRootBot } from '../../recoilModel/dispatchers/u
import { rootBotProjectIdSelector } from '../../recoilModel/selectors/project';
import { navigateTo } from '../../utils/navigation';
import { usePVACheck } from '../../hooks/usePVACheck';
-import { projectReadmeState } from '../../recoilModel/atoms';
+import { debugPanelActiveTabState, debugPanelExpansionState, projectReadmeState } from '../../recoilModel/atoms';
import { GetStartedTask } from './GetStartedTask';
import { NextStep } from './types';
@@ -42,7 +42,7 @@ export const GetStartedNextSteps: React.FC = (props) => {
const [displayManageQNA, setDisplayManageQNA] = useState(false);
const readme = useRecoilValue(projectReadmeState(projectId));
const [readmeHidden, setReadmeHidden] = useState(true);
-
+ const schemaDiagnostics = useRecoilValue(schemaDiagnosticsSelectorFamily(projectId));
const { setSettings, setQnASettings } = useRecoilValue(dispatcherState);
const rootBotProjectId = useRecoilValue(rootBotProjectIdSelector) || '';
const settings = useRecoilValue(settingsState(projectId));
@@ -50,6 +50,8 @@ export const GetStartedNextSteps: React.FC = (props) => {
const [requiredNextSteps, setRequiredNextSteps] = useState([]);
const [recommendedNextSteps, setRecommendedNextSteps] = useState([]);
const [optionalSteps, setOptionalSteps] = useState([]);
+ const setExpansion = useSetRecoilState(debugPanelExpansionState);
+ const setActiveTab = useSetRecoilState(debugPanelActiveTabState);
const [highlightLUIS, setHighlightLUIS] = useState(false);
const [highlightQNA, setHighlightQNA] = useState(false);
@@ -129,6 +131,28 @@ export const GetStartedNextSteps: React.FC = (props) => {
? true
: false;
+ if (schemaDiagnostics.length) {
+ newNextSteps.push({
+ key: 'customActions',
+ label: formatMessage('Review deactivated custom actions'),
+ description: formatMessage('We detected {length} custom {obj} that are not support for Composer 2.0.', {
+ length: schemaDiagnostics.length,
+ obj: `component${schemaDiagnostics.length > 1 ? 's' : ''}`,
+ }),
+ required: true,
+ checked: false,
+ onClick: (step) => {
+ TelemetryClient.track('GettingStartedActionClicked', {
+ taskName: 'customActionsCheck',
+ priority: 'required',
+ });
+ setExpansion(true);
+ setActiveTab('Diagnostics');
+ },
+ hideFeatureStep: false,
+ });
+ }
+
if (props.requiresLUIS) {
newNextSteps.push({
key: 'luis',
diff --git a/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/DiagnosticsTab/DiagnosticList.tsx b/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/DiagnosticsTab/DiagnosticList.tsx
index cd8c93d62e..dbe5dbbec6 100644
--- a/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/DiagnosticsTab/DiagnosticList.tsx
+++ b/Composer/packages/client/src/pages/design/DebugPanel/TabExtensions/DiagnosticsTab/DiagnosticList.tsx
@@ -68,6 +68,10 @@ const tableCell = css`
}
`;
+const blodText = css`
+ font-weight: bold !important;
+`;
+
const content = css`
outline: none;
`;
@@ -188,7 +192,17 @@ export const DiagnosticList: React.FC = ({ diagnosticItems
css={content}
tabIndex={-1}
>
- {item.message}
+ {item.title ?? ''}
+
+ {item.message}
+
+
+ {item.learnMore}
+
);
diff --git a/Composer/packages/client/src/pages/diagnostics/types.ts b/Composer/packages/client/src/pages/diagnostics/types.ts
index 7f1227b949..a3452820a4 100644
--- a/Composer/packages/client/src/pages/diagnostics/types.ts
+++ b/Composer/packages/client/src/pages/diagnostics/types.ts
@@ -3,6 +3,7 @@
import { createSingleMessage, isDiagnosticWithInRange } from '@bfc/indexers';
import { Diagnostic, DialogInfo, LuFile, LgFile, LgNamePattern } from '@bfc/shared';
import get from 'lodash/get';
+import formatMessage from 'format-message';
import { getBaseName } from '../../utils/fileUtil';
import { replaceDialogDiagnosticLabel } from '../../utils/dialogUtil';
@@ -32,6 +33,8 @@ export interface IDiagnosticInfo {
dialogPath?: string; //the data path in dialog
resourceId: string; // id without locale
getUrl: (hash?: string) => string;
+ learnMore?: string;
+ title?: string;
}
export abstract class DiagnosticInfo implements IDiagnosticInfo {
@@ -46,6 +49,8 @@ export abstract class DiagnosticInfo implements IDiagnosticInfo {
dialogPath?: string;
resourceId: string;
getUrl = () => '';
+ learnMore?: string;
+ title?: string;
constructor(rootProjectId: string, projectId: string, id: string, location: string, diagnostic: Diagnostic) {
this.rootProjectId = rootProjectId;
@@ -97,6 +102,12 @@ export class DialogDiagnostic extends DiagnosticInfo {
export class SchemaDiagnostic extends DialogDiagnostic {
type = DiagnosticType.SCHEMA;
+ constructor(rootProjectId: string, projectId: string, id: string, location: string, diagnostic: Diagnostic) {
+ super(rootProjectId, projectId, id, location, diagnostic);
+ this.message = diagnostic.message;
+ this.title = formatMessage('Deactivated action.');
+ this.learnMore = formatMessage('Learn more about custom actions');
+ }
}
export class SkillSettingDiagnostic extends DiagnosticInfo {
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/project.ts b/Composer/packages/client/src/recoilModel/dispatchers/project.ts
index 5783f9fcf5..ca834c28fd 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/project.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/project.ts
@@ -4,7 +4,6 @@
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';
@@ -47,6 +46,7 @@ import { botRuntimeOperationsSelector, rootBotProjectIdSelector } from '../selec
import { mergePropertiesManagedByRootBot, postRootBotCreation } from '../../recoilModel/dispatchers/utils/project';
import { projectDialogsMapSelector, botDisplayNameState } from '../../recoilModel';
import { deleteTrigger as DialogdeleteTrigger } from '../../utils/dialogUtil';
+import { BotConvertConfirmDialog } from '../../components/BotConvertDialog';
import { announcementState, boilerplateVersionState, recentProjectsState, templateIdState } from './../atoms';
import { logMessage, setError } from './../dispatchers/shared';
@@ -245,22 +245,16 @@ 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 forceMigrate = useRecoilCallback(
+ (callbackHelpers: CallbackInterface) => async (projectId: string, containEjectedRuntime: boolean) => {
+ if (await BotConvertConfirmDialog(containEjectedRuntime)) {
+ callbackHelpers.set(creationFlowStatusState, CreationFlowStatus.MIGRATE);
+ navigateTo(`/v2/projects/migrate/${projectId}`);
+ } else {
+ navigateTo(`/home`);
+ }
}
- });
+ );
const openProject = useRecoilCallback(
(callbackHelpers: CallbackInterface) => async (
@@ -275,14 +269,14 @@ export const projectDispatcher = () => {
set(botOpeningState, true);
await flushExistingTasks(callbackHelpers);
- const { projectId, mainDialog, requiresMigrate } = await openRootBotAndSkillsByPath(
+ const { projectId, mainDialog, requiresMigrate, hasOldCustomRuntime } = await openRootBotAndSkillsByPath(
callbackHelpers,
path,
storageId
);
if (requiresMigrate) {
- await forceMigrate(projectId);
+ await forceMigrate(projectId, hasOldCustomRuntime);
return;
}
@@ -345,9 +339,12 @@ export const projectDispatcher = () => {
try {
await flushExistingTasks(callbackHelpers);
set(botOpeningState, true);
- const { requiresMigrate } = await openRootBotAndSkillsByProjectId(callbackHelpers, projectId);
+ const { requiresMigrate, hasOldCustomRuntime } = await openRootBotAndSkillsByProjectId(
+ callbackHelpers,
+ projectId
+ );
if (requiresMigrate) {
- await forceMigrate(projectId);
+ await forceMigrate(projectId, hasOldCustomRuntime);
return;
}
// Post project creation
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts b/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts
index a87110d988..ad0e0df2a0 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts
@@ -467,6 +467,11 @@ export const isAdaptiveRuntime = (settings): boolean => {
return settings?.runtime?.key?.match(/^adaptive-runtime/) ? true : false;
};
+export const isOldCustomRuntime = (settings): boolean => {
+ const keys = ['node-azurewebapp', 'csharp-azurewebapp'];
+ return keys.includes(settings?.runtime?.key);
+};
+
export const isPVA = (settings): boolean => {
return settings?.publishTargets?.some((target) => target.type === 'pva-publish-composer');
};
@@ -836,6 +841,7 @@ export const openRootBotAndSkills = async (callbackHelpers: CallbackInterface, d
mainDialog,
projectId: rootBotProjectId,
requiresMigrate: !isAdaptiveRuntime(botFiles.mergedSettings) && !isPVA(botFiles.mergedSettings),
+ hasOldCustomRuntime: isOldCustomRuntime(botFiles.mergedSettings),
};
};
diff --git a/Composer/packages/client/src/recoilModel/selectors/diagnosticsPageSelector.ts b/Composer/packages/client/src/recoilModel/selectors/diagnosticsPageSelector.ts
index 9fc549c30d..ab84f705dd 100644
--- a/Composer/packages/client/src/recoilModel/selectors/diagnosticsPageSelector.ts
+++ b/Composer/packages/client/src/recoilModel/selectors/diagnosticsPageSelector.ts
@@ -5,6 +5,7 @@ import { BotIndexer, validateSchema } from '@bfc/indexers';
import { selectorFamily, selector } from 'recoil';
import lodashGet from 'lodash/get';
import formatMessage from 'format-message';
+import { getFriendlyName } from '@bfc/shared';
import { getReferredLuFiles } from '../../utils/luUtil';
import { INavTreeItem } from '../../components/NavTree';
@@ -202,7 +203,21 @@ export const schemaDiagnosticsSelectorFamily = selectorFamily({
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))
+ ...diagnostics.map((d) => {
+ let location = dialog.id;
+ if (d.path) {
+ const list = d.path.split('.');
+ let path = '';
+ location = [
+ location,
+ ...list.map((item) => {
+ path = `${path}${path ? '.' : ''}${item}`;
+ return getFriendlyName(lodashGet(dialog.content, path)) || '';
+ }),
+ ].join('>');
+ }
+ return new SchemaDiagnostic(rootProjectId, projectId, dialog.id, location, d);
+ })
);
});
return fullDiagnostics;
diff --git a/Composer/packages/lib/indexers/src/validations/schemaValidation/index.ts b/Composer/packages/lib/indexers/src/validations/schemaValidation/index.ts
index 081d23f40c..d50bc1363c 100644
--- a/Composer/packages/lib/indexers/src/validations/schemaValidation/index.ts
+++ b/Composer/packages/lib/indexers/src/validations/schemaValidation/index.ts
@@ -6,8 +6,6 @@ import { BaseSchema, DiagnosticSeverity, SchemaDefinitions } from '@botframework
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 ?? {};
@@ -15,7 +13,15 @@ export const validateSchema = (dialogId: string, dialogData: BaseSchema, schema:
walkAdaptiveDialog(dialogData, schemas, ($kind, data, path) => {
if (!schemas[$kind]) {
diagnostics.push(
- new Diagnostic(`${$kind}: ${SCHEMA_NOT_FOUND}`, `${dialogId}.dialog`, DiagnosticSeverity.Error, path)
+ new Diagnostic(
+ formatMessage(
+ 'Components of $kind "{kind}" are not supported. Replace with a different component or create a custom component.',
+ { kind: $kind }
+ ),
+ `${dialogId}.dialog`,
+ DiagnosticSeverity.Error,
+ path
+ )
);
}
return true;