diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectHeader.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectHeader.tsx
index 6a17eb65ad..2f79907ee4 100644
--- a/Composer/packages/client/src/components/ProjectTree/ProjectHeader.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ProjectHeader.tsx
@@ -113,7 +113,7 @@ export const ProjectHeader = (props: ProjectHeaderProps) => {
onClick: () => {},
},
{
- label: formatMessage('Create/edit skill manifest'),
+ label: formatMessage('Share as a skill'),
onClick: () => {
onBotEditManifest(projectId);
},
diff --git a/Composer/packages/client/src/pages/botProject/CreatePublishProfileDialog.tsx b/Composer/packages/client/src/pages/botProject/CreatePublishProfileDialog.tsx
new file mode 100644
index 0000000000..402d8e3918
--- /dev/null
+++ b/Composer/packages/client/src/pages/botProject/CreatePublishProfileDialog.tsx
@@ -0,0 +1,109 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+/** @jsx jsx */
+import React, { Fragment, useState, useEffect } from 'react';
+import { jsx } from '@emotion/core';
+import { useRecoilValue } from 'recoil';
+import { PublishTarget } from '@bfc/shared';
+import formatMessage from 'format-message';
+import { ActionButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
+import { useBoolean } from '@uifabric/react-hooks';
+import Dialog, { DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
+
+import { dispatcherState, settingsState, publishTypesState } from '../../recoilModel';
+import { AuthDialog } from '../../components/Auth/AuthDialog';
+import { isShowAuthDialog } from '../../utils/auth';
+
+import { PublishProfileDialog } from './create-publish-profile/PublishProfileDialog';
+import { actionButton } from './styles';
+
+// -------------------- CreatePublishProfileDialog -------------------- //
+
+type CreatePublishProfileDialogProps = {
+ projectId: string;
+};
+
+export const CreatePublishProfileDialog: React.FC = (props) => {
+ const { projectId } = props;
+ const { publishTargets } = useRecoilValue(settingsState(projectId));
+ const { getPublishTargetTypes, setPublishTargets } = useRecoilValue(dispatcherState);
+ const publishTypes = useRecoilValue(publishTypesState(projectId));
+
+ const [dialogHidden, setDialogHidden] = useState(true);
+ const [showAuthDialog, setShowAuthDialog] = useState(false);
+ const [hideDialog, { toggle: toggleHideDialog }] = useBoolean(false);
+
+ const dialogTitle = {
+ title: formatMessage('Create a publish profile to continue'),
+ subText: formatMessage(
+ 'To make your bot available as a remote skill you will need to provision Azure resources . This process may take a few minutes depending on the resources you select.'
+ ),
+ };
+ const [currentPublishProfile, setCurrentPublishProfile] = useState<{ index: number; item: PublishTarget } | null>(
+ null
+ );
+
+ useEffect(() => {
+ if (projectId) {
+ getPublishTargetTypes(projectId);
+ }
+ }, [projectId]);
+
+ return (
+
+
+ {showAuthDialog && (
+ {
+ setDialogHidden(false);
+ }}
+ onDismiss={() => {
+ setShowAuthDialog(false);
+ }}
+ />
+ )}
+ {!dialogHidden ? (
+ {
+ setDialogHidden(true);
+ setCurrentPublishProfile(null);
+ }}
+ current={currentPublishProfile}
+ projectId={projectId}
+ setPublishTargets={setPublishTargets}
+ targets={publishTargets || []}
+ types={publishTypes}
+ />
+ ) : null}
+
+ );
+};
diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/__tests__/generateSkillManifest.test.ts b/Composer/packages/client/src/pages/design/exportSkillModal/__tests__/generateSkillManifest.test.ts
index 47a857c5e7..0106f803e0 100644
--- a/Composer/packages/client/src/pages/design/exportSkillModal/__tests__/generateSkillManifest.test.ts
+++ b/Composer/packages/client/src/pages/design/exportSkillModal/__tests__/generateSkillManifest.test.ts
@@ -11,6 +11,14 @@ import {
generateDispatchModels,
} from '../generateSkillManifest';
+const projectId = '42345.23432';
+const currentTarget = {
+ configuration:
+ '{\n "name": "test",\n "environment": "composer",\n "tenantId": "aaa",\n "subscriptionId": "aaa",\n "resourceGroup": "testGroup",\n "botName": "test",\n "hostname": "test",\n "luisResource": "test",\n "runtimeIdentifier": "win-x64",\n "region": "westus",\n "settings": {\n "applicationInsights": {},\n "luis": {"authoringKey":"aaa", "endpointKey": "aaa",\n "endpoint": "https://westus.api.cognitive.microsoft.com/"\n },\n "qna": {},\n "MicrosoftAppId": "aaa",\n "MicrosoftAppPassword": "aaa"\n }\n}',
+ name: 'test',
+ type: 'azurePublish',
+ lastPublished: new Date('2021-04-08T08:38:17.566Z'),
+};
const dialogSchema = {
id: 'test',
content: {
@@ -188,7 +196,15 @@ describe('generateDispatchModels', () => {
const selectedTriggers = [];
const luFiles = [];
const qnaFiles = [];
- const result = generateDispatchModels(schema, dialogs, selectedTriggers, luFiles, qnaFiles);
+ const result = generateDispatchModels(
+ schema,
+ dialogs,
+ selectedTriggers,
+ luFiles,
+ qnaFiles,
+ currentTarget,
+ projectId
+ );
expect(result).toEqual({});
});
@@ -201,7 +217,15 @@ describe('generateDispatchModels', () => {
{ id: 'test.fr-FR', empty: false },
];
const qnaFiles = [];
- const result = generateDispatchModels(schema, dialogs, selectedTriggers, luFiles, qnaFiles);
+ const result = generateDispatchModels(
+ schema,
+ dialogs,
+ selectedTriggers,
+ luFiles,
+ qnaFiles,
+ currentTarget,
+ projectId
+ );
expect(result).toEqual({});
});
@@ -214,7 +238,15 @@ describe('generateDispatchModels', () => {
{ id: 'test.fr-FR', empty: false },
];
const qnaFiles = [];
- const result = generateDispatchModels(schema, dialogs, selectedTriggers, luFiles, qnaFiles);
+ const result = generateDispatchModels(
+ schema,
+ dialogs,
+ selectedTriggers,
+ luFiles,
+ qnaFiles,
+ currentTarget,
+ projectId
+ );
expect(result).toEqual({});
});
@@ -227,37 +259,45 @@ describe('generateDispatchModels', () => {
{ id: 'test.fr-FR', empty: false },
];
const qnaFiles: any = [{ id: 'test.es-es', empty: false }];
- const result = generateDispatchModels(schema, dialogs, selectedTriggers, luFiles, qnaFiles);
+ const result = generateDispatchModels(
+ schema,
+ dialogs,
+ selectedTriggers,
+ luFiles,
+ qnaFiles,
+ currentTarget,
+ projectId
+ );
expect(result).toEqual(
expect.objectContaining({
dispatchModels: {
+ intents: ['testIntent'],
languages: {
'en-us': [
{
- name: 'test',
contentType: 'application/lu',
- url: ``,
description: '',
+ name: 'test',
+ url: 'https://test.azurewebsites.net/manifests/skill-test.en-us.lu',
},
],
- 'fr-FR': [
+ 'es-es': [
{
- name: 'test',
- contentType: 'application/lu',
- url: ``,
+ contentType: 'application/qna',
description: '',
+ name: 'test',
+ url: 'https://test.azurewebsites.net/manifests/skill-test.es-es.qna',
},
],
- 'es-es': [
+ 'fr-FR': [
{
- name: 'test',
- contentType: 'application/qna',
- url: ``,
+ contentType: 'application/lu',
description: '',
+ name: 'test',
+ url: 'https://test.azurewebsites.net/manifests/skill-test.fr-FR.lu',
},
],
},
- intents: ['testIntent'],
},
})
);
diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/constants.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/constants.tsx
index 611914dcbc..18f7c5c883 100644
--- a/Composer/packages/client/src/pages/design/exportSkillModal/constants.tsx
+++ b/Composer/packages/client/src/pages/design/exportSkillModal/constants.tsx
@@ -3,23 +3,15 @@
import formatMessage from 'format-message';
import { JSONSchema7 } from '@bfc/extension-client';
-import { resolveRef } from '@bfc/adaptive-form';
import { SkillManifestFile } from '@bfc/shared';
import startCase from 'lodash/startCase';
import { SDKKinds } from '@bfc/shared';
import { nameRegex } from '../../../constants';
-import {
- Description,
- Endpoints,
- FetchManifestSchema,
- ReviewManifest,
- SaveManifest,
- SelectDialogs,
- SelectManifest,
- SelectTriggers,
-} from './content';
+import { Description, ReviewManifest, SaveManifest, SelectDialogs, SelectTriggers } from './content';
+import { SelectProfile } from './content/SelectProfile';
+import { AddCallers } from './content/AddCallers';
export const VERSION_REGEX = /\d\.\d+\.(\d+|preview-\d+)|\d\.\d+/i;
@@ -95,10 +87,14 @@ export interface ContentProps {
setSelectedTriggers: (selectedTriggers: any[]) => void;
setSkillManifest: (_: Partial) => void;
schema: JSONSchema7;
+ selectedDialogs: any[];
+ selectedTriggers: any[];
skillManifests: SkillManifestFile[];
value: { [key: string]: any };
onChange: (_: any) => void;
projectId: string;
+ callers: string[];
+ onUpdateCallers: (callers: string[]) => void;
}
interface Button {
@@ -127,25 +123,21 @@ interface EditorStep {
}
export enum ManifestEditorSteps {
- ENDPOINTS = 'ENDPOINTS',
- FETCH_MANIFEST_SCHEMA = 'FETCH_MANIFEST_SCHEMA',
MANIFEST_DESCRIPTION = 'MANIFEST_DESCRIPTION',
MANIFEST_REVIEW = 'MANIFEST_REVIEW',
SAVE_MANIFEST = 'SAVE_MANIFEST',
- SELECT_MANIFEST = 'SELECT_MANIFEST',
SELECT_DIALOGS = 'SELECT_DIALOGS',
SELECT_TRIGGERS = 'SELECT_TRIGGERS',
+ SELECT_PROFILE = 'SELECT_PROFILE',
+ ADD_CALLERS = 'ADD_CALLERS',
}
export const order: ManifestEditorSteps[] = [
- ManifestEditorSteps.SELECT_MANIFEST,
- ManifestEditorSteps.FETCH_MANIFEST_SCHEMA,
ManifestEditorSteps.MANIFEST_DESCRIPTION,
- ManifestEditorSteps.ENDPOINTS,
ManifestEditorSteps.SELECT_DIALOGS,
ManifestEditorSteps.SELECT_TRIGGERS,
- ManifestEditorSteps.MANIFEST_REVIEW,
- ManifestEditorSteps.SAVE_MANIFEST,
+ ManifestEditorSteps.ADD_CALLERS,
+ ManifestEditorSteps.SELECT_PROFILE,
];
const cancelButton: Button = {
@@ -159,6 +151,12 @@ const nextButton: Button = {
onClick: ({ onNext }) => onNext,
};
+const backButton: Button = {
+ primary: true,
+ text: () => formatMessage('Back'),
+ onClick: ({ onBack }) => onBack,
+};
+
const validate = ({ content, schema }) => {
const required = schema?.required || [];
@@ -177,61 +175,79 @@ const validate = ({ content, schema }) => {
};
export const editorSteps: { [key in ManifestEditorSteps]: EditorStep } = {
- [ManifestEditorSteps.SELECT_MANIFEST]: {
+ [ManifestEditorSteps.MANIFEST_DESCRIPTION]: {
+ buttons: [cancelButton, nextButton],
+ content: Description,
+ editJson: false,
+ title: () => formatMessage('Describe your skill'),
+ subText: () => formatMessage('To make your bot available for others as a skill, we need to generate a manifest.'),
+ validate,
+ },
+ [ManifestEditorSteps.SELECT_PROFILE]: {
buttons: [
cancelButton,
+ backButton,
{
- disabled: ({ manifest }) => !manifest?.id,
+ disabled: ({ publishTargets }) => {
+ try {
+ return (
+ publishTargets.findIndex((item) => {
+ const config = JSON.parse(item.configuration);
+ return (
+ config.settings &&
+ config.settings.MicrosoftAppId &&
+ config.hostname &&
+ config.settings.MicrosoftAppId.length > 0 &&
+ config.hostname.length > 0
+ );
+ }) < 0
+ );
+ } catch (err) {
+ console.log(err.message);
+ return true;
+ }
+ },
+
primary: true,
- text: () => formatMessage('Edit'),
- onClick: ({ onNext, manifest }) => () => {
- onNext({ id: manifest?.id });
+ text: () => formatMessage('Generate and Publish'),
+ onClick: ({ generateManifest, onNext, onPublish }) => () => {
+ generateManifest();
+ onNext({ dismiss: true, save: true });
+ onPublish();
},
},
],
- content: SelectManifest,
editJson: false,
- subText: () => formatMessage('Create a new skill manifest or select which one you want to edit'),
- title: () => formatMessage('Create or edit skill manifest'),
+ content: SelectProfile,
+ subText: () =>
+ formatMessage('We need to define the endpoints for the skill to allow other bots to interact with it.'),
+ title: () => formatMessage('Confirm skill endpoints'),
},
- [ManifestEditorSteps.FETCH_MANIFEST_SCHEMA]: {
- content: FetchManifestSchema,
+ [ManifestEditorSteps.ADD_CALLERS]: {
+ buttons: [
+ cancelButton,
+ backButton,
+ {
+ primary: true,
+ text: () => formatMessage('Next'),
+ onClick: ({ onNext, onSaveSkill }) => () => {
+ onSaveSkill();
+ onNext();
+ },
+ },
+ ],
editJson: false,
- title: () => formatMessage('Select manifest version'),
- },
- [ManifestEditorSteps.MANIFEST_DESCRIPTION]: {
- buttons: [cancelButton, nextButton],
- content: Description,
- editJson: true,
- title: () => formatMessage('Describe your skill'),
- subText: () => formatMessage('To make your bot available for others as a skill, we need to generate a manifest.'),
- validate,
- },
- [ManifestEditorSteps.ENDPOINTS]: {
- buttons: [cancelButton, nextButton],
- content: Endpoints,
- editJson: true,
+ content: AddCallers,
subText: () =>
- formatMessage('We need to define the endpoints for the skill to allow other bots to interact with it.'),
- title: () => formatMessage('Skill endpoints'),
- validate: ({ content, schema }) => {
- const { items, minItems } = schema.properties?.endpoints;
-
- if (!content.endpoints || content.endpoints.length < minItems) {
- return { endpoints: formatMessage('Please add at least {minItems} endpoint', { minItems }) };
- }
-
- const endpointSchema = resolveRef(items, schema.definitions);
- const endpoints = (content.endpoints || []).map((endpoint) =>
- validate({ content: endpoint, schema: endpointSchema })
- );
-
- return endpoints.some((endpoint) => Object.keys(endpoint).length) ? { endpoints } : {};
- },
+ formatMessage(
+ 'Add Microsoft App Ids of bots that can access this skill. You can skip this step and add this information later from the project settings tab.'
+ ),
+ title: () => formatMessage('Which bots are allowed to use this skill?'),
},
[ManifestEditorSteps.MANIFEST_REVIEW]: {
buttons: [
cancelButton,
+ backButton,
{
primary: true,
text: () => formatMessage('Next'),
@@ -245,6 +261,7 @@ export const editorSteps: { [key in ManifestEditorSteps]: EditorStep } = {
[ManifestEditorSteps.SELECT_DIALOGS]: {
buttons: [
cancelButton,
+ backButton,
{
primary: true,
text: () => formatMessage('Next'),
@@ -252,7 +269,7 @@ export const editorSteps: { [key in ManifestEditorSteps]: EditorStep } = {
},
],
content: SelectDialogs,
- editJson: true,
+ editJson: false,
subText: () =>
formatMessage(
'These tasks will be used to generate the manifest and describe the capabilities of this skill to those who may want to use it.'
@@ -262,17 +279,18 @@ export const editorSteps: { [key in ManifestEditorSteps]: EditorStep } = {
[ManifestEditorSteps.SELECT_TRIGGERS]: {
buttons: [
cancelButton,
+ backButton,
{
primary: true,
- text: () => formatMessage('Generate'),
- onClick: ({ generateManifest, onNext }) => () => {
- generateManifest();
+ text: () => formatMessage('Next'),
+ onClick: ({ onNext, generateManifest }) => () => {
+ // generateManifest();
onNext();
},
},
],
content: SelectTriggers,
- editJson: true,
+ editJson: false,
subText: () =>
formatMessage(
'These tasks will be used to generate the manifest and describe the capabilities of this skill to those who may want to use it.'
@@ -282,6 +300,7 @@ export const editorSteps: { [key in ManifestEditorSteps]: EditorStep } = {
[ManifestEditorSteps.SAVE_MANIFEST]: {
buttons: [
cancelButton,
+ backButton,
{
primary: true,
text: () => formatMessage('Save'),
@@ -291,7 +310,7 @@ export const editorSteps: { [key in ManifestEditorSteps]: EditorStep } = {
},
],
content: SaveManifest,
- editJson: true,
+ editJson: false,
subText: () => formatMessage('Name and save your skill manifest.'),
title: () => formatMessage('Save your skill manifest'),
validate: ({ editingId, id, skillManifests }) => {
diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/content/AddCallers.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/content/AddCallers.tsx
new file mode 100644
index 0000000000..4951b2ebae
--- /dev/null
+++ b/Composer/packages/client/src/pages/design/exportSkillModal/content/AddCallers.tsx
@@ -0,0 +1,84 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+/** @jsx jsx */
+import { css, jsx } from '@emotion/core';
+import { SharedColors } from '@uifabric/fluent-theme';
+import formatMessage from 'format-message';
+import { ActionButton } from 'office-ui-fabric-react/lib/Button';
+import { FontWeights } from 'office-ui-fabric-react/lib/Styling';
+import { TextField } from 'office-ui-fabric-react/lib/TextField';
+import React from 'react';
+
+import { tableColumnHeader, tableRow, tableRowItem } from '../../../botProject/styles';
+import { ContentProps } from '../constants';
+
+const header = css`
+ display: flex;
+ flex-direction: row;
+ height: 42px;
+`;
+
+const addNewAllowCallers = {
+ root: {
+ fontSize: 12,
+ fontWeight: FontWeights.regular,
+ color: SharedColors.cyanBlue10,
+ paddingLeft: 0,
+ marginLeft: 5,
+ },
+};
+
+const removeCaller = {
+ root: {
+ fontSize: 12,
+ fontWeight: FontWeights.regular,
+ color: SharedColors.cyanBlue10,
+ paddingLeft: 0,
+ paddingBottom: 5,
+ },
+};
+
+export const AddCallers: React.FC = ({ projectId, callers, onUpdateCallers }) => {
+ const handleRemove = (index) => {
+ onUpdateCallers(callers.filter((_, i) => i !== index));
+ };
+ const handleAddNewAllowedCallerClick = () => {
+ const currentCallers = callers.slice();
+ currentCallers?.push('0000-11111-00000-11111');
+ onUpdateCallers(currentCallers);
+ };
+
+ return (
+
+
+
{formatMessage('Allowed Callers')}
+
+ {callers?.map((caller, index) => {
+ return (
+
+
+ {
+ const currentCallers = callers.slice();
+ currentCallers[index] = newValue ?? '';
+ onUpdateCallers(currentCallers);
+ }}
+ />
+
+
+
handleRemove(index)}>
+ {formatMessage('Remove')}
+
+
+
+ );
+ })}
+
+ {formatMessage('Add allowed callers')}
+
+
+ );
+};
diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/content/Description.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/content/Description.tsx
index 5ff2dc68d4..ac6092a281 100644
--- a/Composer/packages/client/src/pages/design/exportSkillModal/content/Description.tsx
+++ b/Composer/packages/client/src/pages/design/exportSkillModal/content/Description.tsx
@@ -3,15 +3,19 @@
/** @jsx jsx */
import { css, jsx } from '@emotion/core';
-import React, { useMemo, useEffect } from 'react';
+import React, { useMemo, useEffect, Fragment, useState } from 'react';
import AdaptiveForm, { FieldLabel } from '@bfc/adaptive-form';
import { FieldProps, JSONSchema7, UIOptions } from '@bfc/extension-client';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { useRecoilValue } from 'recoil';
import { v4 as uuid } from 'uuid';
+import { Label } from 'office-ui-fabric-react/lib/Label';
+import formatMessage from 'format-message';
+import { Dropdown, IDropdownOption, ResponsiveMode } from 'office-ui-fabric-react/lib/Dropdown';
+import { LoadingSpinner } from '@bfc/ui-shared/lib/components/LoadingSpinner';
-import { ContentProps } from '../constants';
import { botDisplayNameState } from '../../../../recoilModel';
+import { ContentProps, SCHEMA_URIS, VERSION_REGEX } from '../constants';
const styles = {
row: css`
@@ -25,6 +29,12 @@ const styles = {
`,
};
+const chooseVersion = css`
+ display: flex;
+ width: 72%;
+ margin: 10px 18px;
+ justify-content: space-between;
+`;
const InlineLabelField: React.FC = (props) => {
const { id, placeholder, rawErrors, value = '', onChange } = props;
@@ -50,42 +60,94 @@ const InlineLabelField: React.FC = (props) => {
);
};
-export const Description: React.FC = ({ errors, value, schema, onChange, projectId }) => {
+export const Description: React.FC = ({
+ errors,
+ value,
+ schema,
+ skillManifests,
+ onChange,
+ projectId,
+ setSchema,
+ setSkillManifest,
+ editJson,
+}) => {
const botName = useRecoilValue(botDisplayNameState(projectId));
- const { $schema, ...rest } = value;
+ const [isFetchCompleted, setIsFetchCompleted] = useState(false);
+ const { $id, $schema, ...rest } = value;
- const { hidden, properties } = useMemo(
+ const { hidden, properties } = useMemo(() => {
+ if (!schema.properties) return { hidden: [], properties: {} } as any;
+ return Object.entries(schema.properties as JSONSchema7).reduce(
+ ({ hidden, properties }, [key, property]) => {
+ if (property.type === 'object' || (property.type === 'array' && property?.items?.type !== 'string')) {
+ return { hidden: [...hidden, key], properties };
+ }
+
+ const itemSchema = property?.items as JSONSchema7;
+ const serializer =
+ itemSchema?.type === 'string'
+ ? {
+ get: (value) => (Array.isArray(value) ? value.join(',') : value),
+ set: (value) => (typeof value === 'string' ? value.split(/\s*,\s*/) : value),
+ }
+ : null;
+
+ return {
+ hidden,
+ properties: { ...properties, [key]: { field: InlineLabelField, hideError: true, serializer } },
+ };
+ },
+ { hidden: [], properties: {} } as any
+ );
+ }, [schema]);
+
+ const options: IDropdownOption[] = useMemo(
() =>
- Object.entries(schema?.properties as JSONSchema7).reduce(
- ({ hidden, properties }, [key, property]) => {
- if (property.type === 'object' || (property.type === 'array' && property?.items?.type !== 'string')) {
- return { hidden: [...hidden, key], properties };
- }
-
- const itemSchema = property?.items as JSONSchema7;
- const serializer =
- itemSchema?.type === 'string'
- ? {
- get: (value) => (Array.isArray(value) ? value.join(',') : value),
- set: (value) => (typeof value === 'string' ? value.split(/\s*,\s*/) : value),
- }
- : null;
-
- return {
- hidden,
- properties: { ...properties, [key]: { field: InlineLabelField, hideError: true, serializer } },
- };
- },
- { hidden: [], properties: {} } as any
- ),
- []
+ SCHEMA_URIS.map((key, index) => {
+ const [version] = VERSION_REGEX.exec(key) || [];
+ let selected = false;
+ if ($schema) {
+ selected = $schema && key === $schema;
+ } else {
+ selected = !index;
+ }
+ return {
+ text: formatMessage('Version {version}', { version }),
+ key,
+ selected,
+ };
+ }),
+ [$schema]
);
useEffect(() => {
- if (!value.$id) {
- onChange({ $schema, $id: `${botName}-${uuid()}`, endpoints: [{}], name: botName, ...rest });
- }
- }, []);
+ const skillManifest = skillManifests.find(
+ (manifest) => manifest.content.$schema === ($schema || SCHEMA_URIS[0])
+ ) || {
+ content: {
+ $schema: $schema || SCHEMA_URIS[0],
+ $id: `${botName}-${uuid()}`,
+ endpoints: [{}],
+ name: botName,
+ ...rest,
+ },
+ };
+ setSkillManifest(skillManifest);
+ (async function () {
+ try {
+ if ($schema) {
+ const res = await fetch($schema);
+ const schema = await res.json();
+ setSchema(schema);
+ setIsFetchCompleted(true);
+ } else {
+ editJson();
+ }
+ } catch (error) {
+ editJson();
+ }
+ })();
+ }, [$schema]);
const required = schema?.required || [];
@@ -96,5 +158,45 @@ export const Description: React.FC = ({ errors, value, schema, onC
properties,
};
- return ;
+ const handleChange = (_e: React.FormEvent, option?: IDropdownOption) => {
+ if (option) {
+ const skillManifest = skillManifests.find((manifest) => manifest.content.$schema === option.key) || {
+ content: { $schema: option.key as string },
+ };
+ setIsFetchCompleted(false);
+ setSkillManifest(skillManifest);
+ }
+ };
+
+ return (
+
+
+
+
+
+ {isFetchCompleted ? (
+
+ ) : (
+
+ )}
+
+ );
};
diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/content/FetchManifestSchema.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/content/FetchManifestSchema.tsx
deleted file mode 100644
index ff2d1fc9d6..0000000000
--- a/Composer/packages/client/src/pages/design/exportSkillModal/content/FetchManifestSchema.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-import React, { useEffect } from 'react';
-
-import { LoadingSpinner } from '../../../../components/LoadingSpinner';
-import { ContentProps } from '../constants';
-
-export const FetchManifestSchema: React.FC = ({ completeStep, editJson, value, setSchema }) => {
- useEffect(() => {
- (async function () {
- try {
- if (value?.$schema) {
- const res = await fetch(value.$schema);
- const schema = await res.json();
- setSchema(schema);
- completeStep();
- } else {
- editJson();
- }
- } catch (error) {
- editJson();
- }
- })();
- }, [value]);
-
- return ;
-};
diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectDialogs.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectDialogs.tsx
index ee90f5fd68..882ed6b526 100644
--- a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectDialogs.tsx
+++ b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectDialogs.tsx
@@ -93,7 +93,7 @@ const DescriptionColumn: React.FC = (props) => {
);
};
-export const SelectDialogs: React.FC = ({ setSelectedDialogs, projectId }) => {
+export const SelectDialogs: React.FC = ({ selectedDialogs, setSelectedDialogs, projectId }) => {
const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const items = useMemo(() => dialogs.map(({ id, content, displayName }) => ({ id, content, displayName })), [
projectId,
@@ -138,16 +138,20 @@ export const SelectDialogs: React.FC = ({ setSelectedDialogs, proj
[projectId]
);
- const selection = useMemo(
- () =>
- new Selection({
- onSelectionChanged: () => {
- const selectedItems = selection.getSelection();
- setSelectedDialogs(selectedItems);
- },
- }),
- []
+ const selectionRef = useRef(
+ new Selection({
+ getKey: (item) => item.id,
+ onSelectionChanged: () => {
+ const selectedItems = selectionRef.current.getSelection();
+ setSelectedDialogs(selectedItems);
+ },
+ })
);
- return ;
+ useEffect(() => {
+ for (const item of selectedDialogs) {
+ selectionRef.current.setKeySelected(selectionRef.current.getKey(item), true, false);
+ }
+ }, []);
+ return ;
};
diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectItems.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectItems.tsx
index 1e69515a6c..6584e61922 100644
--- a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectItems.tsx
+++ b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectItems.tsx
@@ -77,7 +77,7 @@ export const SelectItems: React.FC = ({ items, selection, tabl
diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectManifest.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectManifest.tsx
deleted file mode 100644
index 3e4faca419..0000000000
--- a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectManifest.tsx
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-/** @jsx jsx */
-import { css, jsx } from '@emotion/core';
-import React, { useMemo, useState } from 'react';
-import { Dropdown, IDropdownOption, ResponsiveMode } from 'office-ui-fabric-react/lib/Dropdown';
-import { Label } from 'office-ui-fabric-react/lib/Label';
-import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
-import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky';
-import { ScrollablePane, ScrollbarVisibility } from 'office-ui-fabric-react/lib/ScrollablePane';
-import {
- CheckboxVisibility,
- DetailsList,
- DetailsListLayoutMode,
- SelectionMode,
-} from 'office-ui-fabric-react/lib/DetailsList';
-import { TooltipHost } from 'office-ui-fabric-react/lib/Tooltip';
-import formatMessage from 'format-message';
-
-import { calculateTimeDiff } from '../../../../utils/fileUtil';
-import { ContentProps, SCHEMA_URIS, VERSION_REGEX } from '../constants';
-
-const styles = {
- detailListContainer: css`
- position: relative;
- max-height: 40vh;
- padding-top: 10px;
- overflow: hidden;
- flex-grow: 1;
- min-height: 250px;
- `,
- create: css`
- display: flex;
- `,
-};
-
-export const SelectManifest: React.FC = ({ completeStep, skillManifests, setSkillManifest }) => {
- const [manifestVersion, setManifestVersion] = useState(SCHEMA_URIS[0]);
- const [errors, setErrors] = useState<{ version?: string }>({});
- const [version] = useMemo(() => VERSION_REGEX.exec(manifestVersion) || [''], []);
-
- const options: IDropdownOption[] = useMemo(
- () =>
- SCHEMA_URIS.map((key, index) => {
- const [version] = VERSION_REGEX.exec(key) || [];
-
- return {
- text: formatMessage('Version {version}', { version }),
- key,
- selected: !index,
- };
- }),
- []
- );
-
- const handleChange = (_e: React.FormEvent, option?: IDropdownOption) => {
- if (option) {
- setManifestVersion(option.key as string);
- }
- };
-
- const handleCreate = () => {
- if (!version) {
- setErrors({ version: formatMessage('Please select a version of the manifest schema') });
- return;
- }
- setSkillManifest({ content: { $schema: manifestVersion } });
- completeStep();
- };
-
- // for detail file list in open panel
- const tableColumns = [
- {
- key: 'column1',
- name: formatMessage('Name'),
- fieldName: 'id',
- minWidth: 300,
- maxWidth: 350,
- isRowHeader: true,
- isResizable: true,
- isSorted: true,
- isSortedDescending: false,
- sortAscendingAriaLabel: formatMessage('Sorted A to Z'),
- sortDescendingAriaLabel: formatMessage('Sorted Z to A'),
- data: 'string',
- onRender: (item) => {
- return {item.id};
- },
- isPadded: true,
- },
- {
- key: 'column2',
- name: formatMessage('Date modified'),
- fieldName: 'lastModified',
- minWidth: 60,
- maxWidth: 70,
- isResizable: true,
- data: 'number',
- onRender: (item) => {
- return {calculateTimeDiff(item.lastModified)};
- },
- isPadded: true,
- },
- ];
-
- function onRenderDetailsHeader(props, defaultRender) {
- return (
-
- {defaultRender({
- ...props,
- onRenderColumnHeaderTooltip: (tooltipHostProps) => ,
- })}
-
- );
- }
-
- return (
-
-
-
-
-
-
- {formatMessage('Create')}
-
-
-
-
-
- item.name}
- items={skillManifests}
- layoutMode={DetailsListLayoutMode.justified}
- selectionMode={SelectionMode.single}
- onActiveItemChanged={setSkillManifest}
- onRenderDetailsHeader={onRenderDetailsHeader}
- />
-
-
-
- );
-};
diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectProfile.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectProfile.tsx
new file mode 100644
index 0000000000..51bf07dc99
--- /dev/null
+++ b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectProfile.tsx
@@ -0,0 +1,207 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+/** @jsx jsx */
+import { css, jsx } from '@emotion/core';
+import { PublishTarget, SkillManifestFile } from '@bfc/shared';
+import formatMessage from 'format-message';
+import React, { useEffect, useMemo, useState } from 'react';
+import { useRecoilValue } from 'recoil';
+import { TooltipHost } from 'office-ui-fabric-react/lib/Tooltip';
+import { Icon } from 'office-ui-fabric-react/lib/Icon';
+import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
+import { TextField } from 'office-ui-fabric-react/lib/TextField';
+
+import { botDisplayNameState, dispatcherState, settingsState, skillManifestsState } from '../../../../recoilModel';
+import { CreatePublishProfileDialog } from '../../../botProject/CreatePublishProfileDialog';
+import { iconStyle } from '../../../botProject/runtime-settings/style';
+import { ContentProps, VERSION_REGEX } from '../constants';
+
+const styles = {
+ container: css`
+ height: 350px;
+ overflow: auto;
+ `,
+};
+
+const onRenderLabel = (props) => {
+ return (
+
+ );
+};
+
+export const getManifestId = (
+ botName: string,
+ skillManifests: SkillManifestFile[],
+ { content: { $schema } = {} }: Partial
+): string => {
+ const [version] = VERSION_REGEX.exec($schema) || [''];
+
+ let fileId = version ? `${botName}-${version.replace(/\./g, '-')}-manifest` : `${botName}-manifest`;
+ let i = -1;
+
+ while (skillManifests.some(({ id }) => id === fileId) && i < skillManifests.length) {
+ if (i < 0) {
+ fileId = fileId.concat(`-${++i}`);
+ } else {
+ fileId = fileId.substr(0, fileId.lastIndexOf('-')).concat(`-${++i}`);
+ }
+ }
+
+ return fileId;
+};
+
+export const SelectProfile: React.FC = ({ manifest, setSkillManifest, value, onChange, projectId }) => {
+ const [publishingTargets, setPublishingTargets] = useState([]);
+ const [currentTarget, setCurrentTarget] = useState();
+ const { updateCurrentTarget } = useRecoilValue(dispatcherState);
+ const settings = useRecoilValue(settingsState(projectId));
+ const [endpointUrl, setEndpointUrl] = useState();
+ const [appId, setAppId] = useState();
+ const { id, content } = manifest;
+ const botName = useRecoilValue(botDisplayNameState(projectId));
+ const skillManifests = useRecoilValue(skillManifestsState(projectId));
+
+ const handleCurrentProfileChange = useMemo(
+ () => (_e, option?: IDropdownOption) => {
+ const target = publishingTargets.find((t) => {
+ return t.name === option?.key;
+ });
+ setCurrentTarget(target);
+ },
+ [publishingTargets]
+ );
+
+ useEffect(() => {
+ try {
+ if (currentTarget) {
+ updateCurrentTarget(projectId, currentTarget);
+ const config = JSON.parse(currentTarget.configuration);
+ setEndpointUrl(`https://${config.hostname}.azurewebsites.net/api/messages`);
+ setAppId(config.settings.MicrosoftAppId);
+
+ setSkillManifest({
+ content: {
+ ...content,
+ endpoints: [
+ {
+ protocol: 'BotFrameworkV3',
+ name: currentTarget.name,
+ endpointUrl: `https://${config.hostname}.azurewebsites.net/api/messages`,
+ description: '',
+ msAppId: config.settings.MicrosoftAppId,
+ },
+ ],
+ },
+ id: id,
+ });
+ }
+ } catch (err) {
+ console.log(err.message);
+ }
+ }, [currentTarget]);
+ const isProfileValid = useMemo(() => {
+ try {
+ if (!publishingTargets) {
+ return false;
+ }
+ const filteredProfile = publishingTargets.filter((item) => {
+ const config = JSON.parse(item.configuration);
+ return (
+ config.settings &&
+ config.settings.MicrosoftAppId &&
+ config.hostname &&
+ config.settings.MicrosoftAppId.length > 0 &&
+ config.hostname.length > 0
+ );
+ });
+ return filteredProfile.length > 0;
+ } catch (err) {
+ console.log(err.message);
+ return false;
+ }
+ }, [publishingTargets]);
+
+ const publishingOptions = useMemo(() => {
+ return publishingTargets.map((t) => ({
+ key: t.name,
+ text: t.name,
+ }));
+ }, [publishingTargets]);
+
+ useEffect(() => {
+ setPublishingTargets(settings.publishTargets || []);
+ setCurrentTarget((settings.publishTargets || [])[0]);
+ }, [settings]);
+
+ useEffect(() => {
+ if (!id) {
+ const fileId = getManifestId(botName, skillManifests, manifest);
+ setSkillManifest({ ...manifest, id: fileId });
+ }
+ }, [id]);
+
+ return isProfileValid ? (
+
+
+
+
+
+ ) : (
+
+
+
+ );
+};
diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectTriggers.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectTriggers.tsx
index f1a5127be7..ccf13b1ccc 100644
--- a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectTriggers.tsx
+++ b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectTriggers.tsx
@@ -3,7 +3,7 @@
/** @jsx jsx */
import { jsx } from '@emotion/core';
-import React, { useMemo } from 'react';
+import React, { useEffect, useMemo, useRef } from 'react';
import { DialogInfo, ITrigger, SDKKinds, getFriendlyName } from '@bfc/shared';
import { Selection } from 'office-ui-fabric-react/lib/DetailsList';
import { useRecoilValue } from 'recoil';
@@ -20,7 +20,7 @@ const getLabel = (kind: SDKKinds, uiSchema) => {
return label || kind.replace('Microsoft.', '');
};
-export const SelectTriggers: React.FC = ({ setSelectedTriggers, projectId }) => {
+export const SelectTriggers: React.FC = ({ selectedTriggers, setSelectedTriggers, projectId }) => {
const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const schemas = useRecoilValue(schemasState(projectId));
@@ -80,16 +80,21 @@ export const SelectTriggers: React.FC = ({ setSelectedTriggers, pr
},
];
- const selection = useMemo(
- () =>
- new Selection({
- onSelectionChanged: () => {
- const selectedItems = selection.getSelection();
- setSelectedTriggers(selectedItems);
- },
- }),
- []
+ const selectionRef = useRef(
+ new Selection({
+ getKey: (item) => item.id,
+ onSelectionChanged: () => {
+ const selectedItems = selectionRef.current.getSelection();
+ setSelectedTriggers(selectedItems);
+ },
+ })
);
- return ;
+ useEffect(() => {
+ for (const item of selectedTriggers) {
+ selectionRef.current.setKeySelected(selectionRef.current.getKey(item), true, false);
+ }
+ }, []);
+
+ return ;
};
diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/content/index.ts b/Composer/packages/client/src/pages/design/exportSkillModal/content/index.ts
index 94ea08db69..d0ea0a1c97 100644
--- a/Composer/packages/client/src/pages/design/exportSkillModal/content/index.ts
+++ b/Composer/packages/client/src/pages/design/exportSkillModal/content/index.ts
@@ -3,9 +3,7 @@
export * from './Description';
export * from './Endpoints';
-export * from './FetchManifestSchema';
export * from './ReviewManifest';
export * from './SaveManifest';
-export * from './SelectManifest';
export * from './SelectDialogs';
export * from './SelectTriggers';
diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/generateSkillManifest.ts b/Composer/packages/client/src/pages/design/exportSkillModal/generateSkillManifest.ts
index 79a160e931..6984908364 100644
--- a/Composer/packages/client/src/pages/design/exportSkillModal/generateSkillManifest.ts
+++ b/Composer/packages/client/src/pages/design/exportSkillModal/generateSkillManifest.ts
@@ -2,8 +2,20 @@
// Licensed under the MIT License.
import get from 'lodash/get';
-import { DialogInfo, DialogSchemaFile, ITrigger, SDKKinds, SkillManifestFile, LuFile, QnAFile } from '@bfc/shared';
+import {
+ DialogInfo,
+ DialogSchemaFile,
+ ITrigger,
+ SDKKinds,
+ SkillManifestFile,
+ LuFile,
+ QnAFile,
+ PublishTarget,
+} from '@bfc/shared';
import { JSONSchema7 } from '@bfc/extension-client';
+import { luIndexer } from '@bfc/indexers';
+
+import { createManifestFile } from '../../../utils/manifestFileUtil';
import { Activities, Activity, activityHandlerMap, ActivityTypes, DispatchModels } from './constants';
@@ -19,7 +31,9 @@ export const generateSkillManifest = (
luFiles: LuFile[],
qnaFiles: QnAFile[],
selectedTriggers: ITrigger[],
- selectedDialogs: Partial[]
+ selectedDialogs: Partial[],
+ currentTarget: PublishTarget,
+ projectId: string
) => {
const {
activities: previousActivities,
@@ -40,7 +54,7 @@ export const generateSkillManifest = (
const triggers = selectedTriggers.map((tr) => get(content, tr.id) as ITrigger).filter(Boolean);
const activities = generateActivities(dialogSchemas, triggers, resolvedDialogs);
- const dispatchModels = generateDispatchModels(schema, dialogs, triggers, luFiles, qnaFiles);
+ const dispatchModels = generateDispatchModels(schema, dialogs, triggers, luFiles, qnaFiles, currentTarget, projectId);
const definitions = getDefinitions(dialogSchemas, resolvedDialogs);
return {
@@ -104,7 +118,9 @@ export const generateDispatchModels = (
dialogs: DialogInfo[],
selectedTriggers: any[],
luFiles: LuFile[],
- qnaFiles: QnAFile[]
+ qnaFiles: QnAFile[],
+ target: PublishTarget,
+ projectId: string
): { dispatchModels?: DispatchModels } => {
const intents = selectedTriggers.filter(({ $kind }) => $kind === SDKKinds.OnIntent).map(({ intent }) => intent);
const { id: rootId } = dialogs.find((dialog) => dialog?.isRoot) || {};
@@ -123,6 +139,21 @@ export const generateDispatchModels = (
return {};
}
+ const config = JSON.parse(target.configuration);
+ const baseEndpointUrl = `https://${config.hostname}.azurewebsites.net/manifests`;
+
+ for (const rootLuFile of rootLuFiles) {
+ const currentFileName = `skill-${rootLuFile.id}`;
+ const parsedLuFile = luIndexer.parse(rootLuFile.content, rootLuFile.id, {});
+ const contents = parsedLuFile.intents.map((x) => {
+ if (intents.findIndex((intent) => intent == x.Name) !== -1) {
+ return [`# ${x.Name}`, x.Body].join('\n');
+ }
+ });
+ const mergedContents = contents.join('\n');
+ createManifestFile(projectId, currentFileName, mergedContents);
+ }
+
const luLanguages = intents.length
? rootLuFiles.reduce((acc, { empty, id }) => {
const [name, locale] = id.split('.');
@@ -140,7 +171,7 @@ export const generateDispatchModels = (
{
name,
contentType: 'application/lu',
- url: `<${id}.lu url>`,
+ url: `${baseEndpointUrl}/skill-${id}.lu`,
description: '',
},
],
@@ -164,7 +195,7 @@ export const generateDispatchModels = (
{
name,
contentType: 'application/qna',
- url: `<${id}.qna url>`,
+ url: `${baseEndpointUrl}/skill-${id}.qna`,
description: '',
},
],
diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/index.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/index.tsx
index 55fcbc39a5..5efb3939d9 100644
--- a/Composer/packages/client/src/pages/design/exportSkillModal/index.tsx
+++ b/Composer/packages/client/src/pages/design/exportSkillModal/index.tsx
@@ -11,6 +11,9 @@ import { JSONSchema7 } from '@bfc/extension-client';
import { Link } from 'office-ui-fabric-react/lib/components/Link';
import { useRecoilValue } from 'recoil';
import { SkillManifestFile } from '@bfc/shared';
+import { navigate } from '@reach/router';
+import { isUsingAdaptiveRuntime } from '@bfc/shared';
+import cloneDeep from 'lodash/cloneDeep';
import {
dispatcherState,
@@ -18,12 +21,16 @@ import {
qnaFilesSelectorFamily,
dialogsSelectorFamily,
dialogSchemasState,
+ currentPublishTargetState,
luFilesSelectorFamily,
+ settingsState,
+ rootBotProjectIdSelector,
} from '../../../recoilModel';
+import { mergePropertiesManagedByRootBot } from '../../../recoilModel/dispatchers/utils/project';
-import { editorSteps, ManifestEditorSteps, order } from './constants';
-import { generateSkillManifest } from './generateSkillManifest';
import { styles } from './styles';
+import { generateSkillManifest } from './generateSkillManifest';
+import { editorSteps, ManifestEditorSteps, order } from './constants';
interface ExportSkillModalProps {
isOpen: boolean;
@@ -35,18 +42,17 @@ interface ExportSkillModalProps {
const ExportSkillModal: React.FC = ({ onSubmit, onDismiss: handleDismiss, projectId }) => {
const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const dialogSchemas = useRecoilValue(dialogSchemasState(projectId));
+ const currentPublishTarget = useRecoilValue(currentPublishTargetState(projectId));
const luFiles = useRecoilValue(luFilesSelectorFamily(projectId));
const qnaFiles = useRecoilValue(qnaFilesSelectorFamily(projectId));
const skillManifests = useRecoilValue(skillManifestsState(projectId));
const { updateSkillManifest } = useRecoilValue(dispatcherState);
- const [editingId, setEditingId] = useState();
const [currentStep, setCurrentStep] = useState(0);
const [errors, setErrors] = useState({});
const [schema, setSchema] = useState({});
const [skillManifest, setSkillManifest] = useState>({});
-
const { content = {}, id } = skillManifest;
const [selectedDialogs, setSelectedDialogs] = useState([]);
@@ -55,6 +61,32 @@ const ExportSkillModal: React.FC = ({ onSubmit, onDismiss
const editorStep = order[currentStep];
const { buttons = [], content: Content, editJson, helpLink, subText, title, validate } = editorSteps[editorStep];
+ const settings = useRecoilValue(settingsState(projectId));
+ const rootBotProjectId = useRecoilValue(rootBotProjectIdSelector) || '';
+ const mergedSettings = mergePropertiesManagedByRootBot(projectId, rootBotProjectId, settings);
+ const { skillConfiguration, runtime, runtimeSettings, publishTargets } = mergedSettings;
+ const { setSettings } = useRecoilValue(dispatcherState);
+ const isAdaptive = isUsingAdaptiveRuntime(runtime);
+ const [callers, setCallers] = useState(
+ !isAdaptive ? skillConfiguration?.allowedCallers : runtimeSettings?.skills?.allowedCallers ?? []
+ );
+
+ const updateAllowedCallers = React.useCallback(
+ (allowedCallers: string[] = []) => {
+ const updatedSetting = isAdaptive
+ ? {
+ ...cloneDeep(mergedSettings),
+ runtimeSettings: { ...runtimeSettings, skills: { ...runtimeSettings?.skills, allowedCallers } },
+ }
+ : {
+ ...cloneDeep(mergedSettings),
+ skillConfiguration: { ...skillConfiguration, allowedCallers },
+ };
+ setSettings(projectId, updatedSetting);
+ },
+ [mergedSettings, projectId, isAdaptive, skillConfiguration, runtimeSettings]
+ );
+
const handleGenerateManifest = () => {
const manifest = generateSkillManifest(
schema,
@@ -64,9 +96,14 @@ const ExportSkillModal: React.FC = ({ onSubmit, onDismiss
luFiles,
qnaFiles,
selectedTriggers,
- selectedDialogs
+ selectedDialogs,
+ currentPublishTarget,
+ projectId
);
setSkillManifest(manifest);
+ if (manifest.content && manifest.id) {
+ updateSkillManifest(manifest as SkillManifestFile, projectId);
+ }
};
const handleEditJson = () => {
@@ -77,20 +114,41 @@ const ExportSkillModal: React.FC = ({ onSubmit, onDismiss
}
};
+ const handleTriggerPublish = () => {
+ const filePath = `https://${JSON.parse(currentPublishTarget.configuration).hostname}.azurewebsites.net/manifests/${
+ skillManifest.id
+ }.json`;
+ navigate(`/bot/${projectId}/publish/all?publishTargetName=${currentPublishTarget.name}&url=${filePath}`);
+ };
+
const handleSave = () => {
- if (skillManifest.content && skillManifest.id) {
- updateSkillManifest(skillManifest as SkillManifestFile, projectId);
+ const manifest = generateSkillManifest(
+ schema,
+ skillManifest,
+ dialogs,
+ dialogSchemas,
+ luFiles,
+ qnaFiles,
+ selectedTriggers,
+ selectedDialogs,
+ currentPublishTarget,
+ projectId
+ );
+ if (manifest.content && manifest.id) {
+ updateSkillManifest(manifest as SkillManifestFile, projectId);
}
};
+ const onSaveSkill = () => {
+ updateAllowedCallers(callers);
+ };
+
const handleNext = (options?: { dismiss?: boolean; id?: string; save?: boolean }) => {
- const validated =
- typeof validate === 'function' ? validate({ content, editingId, id, schema, skillManifests }) : errors;
+ const validated = typeof validate === 'function' ? validate({ content, id, schema, skillManifests }) : errors;
if (!Object.keys(validated).length) {
setCurrentStep((current) => (current + 1 < order.length ? current + 1 : current));
options?.save && handleSave();
- options?.id && setEditingId(options.id);
options?.dismiss && handleDismiss();
setErrors({});
} else {
@@ -98,6 +156,10 @@ const ExportSkillModal: React.FC = ({ onSubmit, onDismiss
}
};
+ const handleBack = () => {
+ setCurrentStep((current) => (current > 0 ? current - 1 : current));
+ };
+
return (
-
+
= ({ onSubmit, onDismiss
skillManifests={skillManifests}
value={content}
onChange={(manifestContent) => setSkillManifest({ ...skillManifest, content: manifestContent })}
+ onUpdateCallers={setCallers}
/>
@@ -147,7 +218,8 @@ const ExportSkillModal: React.FC = ({ onSubmit, onDismiss
{buttons.map(({ disabled, primary, text, onClick }, index) => {
const Button = primary ? PrimaryButton : DefaultButton;
- const isDisabled = typeof disabled === 'function' ? disabled({ manifest: skillManifest }) : !!disabled;
+
+ const isDisabled = typeof disabled === 'function' ? disabled({ publishTargets }) : !!disabled;
return (