Skip to content

Commit

Permalink
feat: Refer to settings.skill inside dialog files (microsoft#4065)
Browse files Browse the repository at this point in the history
  • Loading branch information
srinaath authored Sep 11, 2020
1 parent 37a9348 commit c4212a5
Show file tree
Hide file tree
Showing 24 changed files with 381 additions and 142 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const state = {
content: 'test',
luFile: 'test',
referredLuIntents: [],
skills: ['https://yuesuemailskill0207-gjvga67.azurewebsites.net/manifest/manifest-1.0.json'],
skills: [`=settings.skill['Email-Skill'].endpointUrl`],
},
],
luFiles: [
Expand Down Expand Up @@ -86,12 +86,13 @@ const state = {
},
],
settings: {
skill: [
{
skill: {
'Email-Skill': {
manifestUrl: 'https://yuesuemailskill0207-gjvga67.azurewebsites.net/manifest/manifest-1.0.json',
name: 'Email Skill',
endpointUrl: 'https://yuesuemailskill0207-gjvga67.azurewebsites.net/api/messages',
name: 'Email-Skill',
},
],
},
},
};

Expand Down
21 changes: 18 additions & 3 deletions Composer/packages/client/src/recoilModel/dispatchers/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { useRecoilCallback, CallbackInterface } from 'recoil';
import { dereferenceDefinitions, LuFile, QnAFile, DialogInfo, SensitiveProperties, DialogSetting } from '@bfc/shared';
import {
dereferenceDefinitions,
LuFile,
QnAFile,
DialogInfo,
SensitiveProperties,
DialogSetting,
convertSkillsToDictionary,
} from '@bfc/shared';
import { indexer, validateDialog } from '@bfc/indexers';
import objectGet from 'lodash/get';
import objectSet from 'lodash/set';
import isArray from 'lodash/isArray';
import formatMessage from 'format-message';

import lgWorker from '../parsers/lgWorker';
Expand Down Expand Up @@ -193,6 +200,14 @@ export const projectDispatcher = () => {
set(projectIdState, projectId);
refreshLocalStorage(projectId, settings);
const mergedSettings = mergeLocalStorage(projectId, settings);
if (Array.isArray(mergedSettings.skill)) {
const skillsArr = mergedSettings.skill.map((skillData) => {
return {
...skillData,
};
});
mergedSettings.skill = convertSkillsToDictionary(skillsArr);
}
set(settingsState, mergedSettings);
});
gotoSnapshot(newSnapshot);
Expand Down Expand Up @@ -347,7 +362,7 @@ export const projectDispatcher = () => {
const { set } = callbackHelpers;
try {
const response = await httpClient.get(`/runtime/templates`);
if (isArray(response.data)) {
if (Array.isArray(response.data)) {
set(runtimeTemplatesState, [...response.data]);
}
} catch (ex) {
Expand Down
22 changes: 21 additions & 1 deletion Composer/packages/client/src/recoilModel/dispatchers/setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/* eslint-disable react-hooks/rules-of-hooks */

import { CallbackInterface, useRecoilCallback } from 'recoil';
import { SensitiveProperties, DialogSetting, PublishTarget } from '@bfc/shared';
import { SensitiveProperties, DialogSetting, PublishTarget, Skill } from '@bfc/shared';
import get from 'lodash/get';
import has from 'lodash/has';

Expand Down Expand Up @@ -89,12 +89,32 @@ export const settingsDispatcher = () => {
}
);

const updateSkillsInSetting = useRecoilCallback(
({ set, snapshot }: CallbackInterface) => async (skillName: string, skillInfo: Partial<Skill>) => {
const currentSettings: DialogSetting = await snapshot.getPromise(settingsState);
const matchedSkill = get(currentSettings, `skill[${skillName}]`, undefined);
if (matchedSkill) {
set(settingsState, {
...currentSettings,
skill: {
...currentSettings.skill,
[skillName]: {
...matchedSkill,
...skillInfo,
},
},
});
}
}
);

return {
setSettings,
setRuntimeSettings,
setPublishTargets,
setRuntimeField,
setCustomRuntime,
setQnASettings,
updateSkillsInSetting,
};
};
6 changes: 2 additions & 4 deletions Composer/packages/client/src/recoilModel/dispatchers/skill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/* eslint-disable react-hooks/rules-of-hooks */

import { CallbackInterface, useRecoilCallback } from 'recoil';
import { SkillManifest } from '@bfc/shared';
import { SkillManifest, convertSkillsToDictionary } from '@bfc/shared';

import httpClient from '../../utils/httpUtil';

Expand Down Expand Up @@ -76,9 +76,7 @@ export const skillDispatcher = () => {
set(onAddSkillDialogCompleteState, { func: undefined });
set(settingsState, (settings) => ({
...settings,
skill: skills.map(({ manifestUrl, name }) => {
return { manifestUrl, name };
}),
skill: convertSkillsToDictionary(skills),
}));
set(skillsState, skills);
} catch (err) {
Expand Down
11 changes: 9 additions & 2 deletions Composer/packages/client/src/shell/useShell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.

import { useMemo, useRef } from 'react';
import { ShellApi, ShellData, Shell } from '@bfc/shared';
import { ShellApi, ShellData, Shell, fetchFromSettings } from '@bfc/shared';
import { useRecoilValue } from 'recoil';
import formatMessage from 'format-message';

Expand All @@ -26,6 +26,7 @@ import {
focusPathState,
userSettingsState,
clipboardActionsState,
settingsState,
} from '../recoilModel';
import { validatedDialogsSelector } from '../recoilModel/selectors/validatedDialogs';

Expand Down Expand Up @@ -56,6 +57,7 @@ export function useShell(source: EventSource): Shell {
const userSettings = useRecoilValue(userSettingsState);
const clipboardActions = useRecoilValue(clipboardActionsState);
const { undo, redo, commitChanges } = useRecoilValue(undoFunctionState);
const settings = useRecoilValue(settingsState);
const {
updateDialog,
updateDialogSchema,
Expand All @@ -70,6 +72,7 @@ export function useShell(source: EventSource): Shell {
updateUserSettings,
setMessage,
displayManifestModal,
updateSkillsInSetting,
} = useRecoilValue(dispatcherState);
const lgApi = useLgApi();
const luApi = useLuApi();
Expand Down Expand Up @@ -177,7 +180,7 @@ export function useShell(source: EventSource): Shell {
},
addSkillDialog: () => {
return new Promise((resolve) => {
addSkillDialogBegin((newSkill: { manifestUrl: string } | null) => {
addSkillDialogBegin((newSkill: { manifestUrl: string; name: string } | null) => {
resolve(newSkill);
});
});
Expand All @@ -190,6 +193,10 @@ export function useShell(source: EventSource): Shell {
announce: setMessage,
displayManifestModal: displayManifestModal,
updateDialogSchema,
skillsInSettings: {
get: (path: string) => fetchFromSettings(path, settings),
set: updateSkillsInSetting,
},
};

const currentDialog = useMemo(() => dialogs.find((d) => d.id === dialogId), [dialogs, dialogId]);
Expand Down
13 changes: 8 additions & 5 deletions Composer/packages/lib/indexers/__tests__/botIndexer.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { BotAssets, DialogSetting, DialogInfo, DiagnosticSeverity, LuFile } from '@bfc/shared';
import { BotAssets, DialogSetting, DialogInfo, DiagnosticSeverity, LuFile, ILuisConfig, IQnAConfig } from '@bfc/shared';

import { BotIndexer } from '../src/botIndexer';
const { checkSkillSetting, checkLUISLocales, filterLUISFilesToPublish } = BotIndexer;
Expand Down Expand Up @@ -32,12 +32,15 @@ const botAssets: BotAssets = {
defaultLanguage: 'en-us',
botId: '',
skillHostEndpoint: '',
skill: [
{
name: 'Email Skill',
skill: {
'Email-Skill': {
name: 'Email-Skill',
manifestUrl: 'skill1',
},
],
},
luis: {} as ILuisConfig,
qna: {} as IQnAConfig,
runtime: {} as any,
} as DialogSetting,
};

Expand Down
38 changes: 27 additions & 11 deletions Composer/packages/lib/indexers/src/botIndexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,18 @@
* Verify bot settings, files meet LUIS/QnA requirments.
*/

import { BotAssets, BotInfo, LUISLocales, Diagnostic, DiagnosticSeverity, LuFile } from '@bfc/shared';
import {
BotAssets,
BotInfo,
LUISLocales,
Diagnostic,
DiagnosticSeverity,
LuFile,
fetchFromSettings,
getSkillNameFromSetting,
} from '@bfc/shared';
import difference from 'lodash/difference';
import map from 'lodash/map';

import { getLocale } from './utils/help';

Expand All @@ -29,7 +39,7 @@ const checkLUISLocales = (assets: BotAssets): Diagnostic[] => {
// Verify bot skill setting.
const checkSkillSetting = (assets: BotAssets): Diagnostic[] => {
const {
setting: { skill = [], botId, skillHostEndpoint },
setting: { skill = {}, botId, skillHostEndpoint },
dialogs,
} = assets;
const diagnostics: Diagnostic[] = [];
Expand All @@ -38,9 +48,15 @@ const checkSkillSetting = (assets: BotAssets): Diagnostic[] => {
dialogs.forEach((dialog) => {
// used skill not existed in setting
dialog.skills.forEach((skillId) => {
if (skill.findIndex(({ manifestUrl }) => manifestUrl === skillId) === -1) {
const endpointUrlCollection = map(skill, ({ endpointUrl }) => endpointUrl);
if (!endpointUrlCollection.includes(fetchFromSettings(skillId, assets.setting))) {
const skillName = getSkillNameFromSetting(skillId) || skillId;
diagnostics.push(
new Diagnostic(`skill '${skillId}' is not existed in appsettings.json`, dialog.id, DiagnosticSeverity.Error)
new Diagnostic(
`The skill '${skillName}' does not exist in in appsettings.json`,
dialog.id,
DiagnosticSeverity.Error
)
);
}
});
Expand All @@ -61,13 +77,6 @@ const checkSkillSetting = (assets: BotAssets): Diagnostic[] => {
return diagnostics;
};

const filterLUISFilesToPublish = (luFiles: LuFile[]): LuFile[] => {
return luFiles.filter((file) => {
const locale = getLocale(file.id);
return locale && LUISLocales.includes(locale);
});
};

const index = (name: string, assets: BotAssets): BotInfo => {
const diagnostics: Diagnostic[] = [];
diagnostics.push(...checkLUISLocales(assets), ...checkSkillSetting(assets));
Expand All @@ -79,6 +88,13 @@ const index = (name: string, assets: BotAssets): BotInfo => {
};
};

const filterLUISFilesToPublish = (luFiles: LuFile[]): LuFile[] => {
return luFiles.filter((file) => {
const locale = getLocale(file.id);
return locale && LUISLocales.includes(locale);
});
};

export const BotIndexer = {
index,
checkLUISLocales,
Expand Down
3 changes: 1 addition & 2 deletions Composer/packages/lib/indexers/src/dialogIndexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,7 @@ function extractReferredSkills(dialog): string[] {
const visitor: VisitorFunc = (path: string, value: any): boolean => {
// it's a valid schema dialog node.
if (has(value, '$kind') && value.$kind === SDKKinds.BeginSkill) {
const skillId = value.id;
skills.push(skillId);
skills.push(value.skillEndpoint);
}
return false;
};
Expand Down
1 change: 1 addition & 0 deletions Composer/packages/lib/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ export * from './schemaUtils';
export * from './types';
export * from './viewUtils';
export * from './walkerUtils';
export * from './skillsUtils';
export const DialogUtils = dialogUtils;
36 changes: 36 additions & 0 deletions Composer/packages/lib/shared/src/skillsUtils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import get from 'lodash/get';
import keyBy from 'lodash/keyBy';

import { DialogSetting, Skill } from '../types';

export function fetchFromSettings(path: string, settings: DialogSetting): string {
if (path) {
const trimmed = path.replace(/=settings.(.*?)/gi, '');
return get(settings, trimmed, '');
}
return '';
}

export const convertSkillsToDictionary = (skills: Skill[]) => {
const mappedSkills = skills.map(({ msAppId, endpointUrl, manifestUrl, name }: Skill) => {
return {
name,
msAppId,
endpointUrl,
manifestUrl,
};
});

return keyBy(mappedSkills, 'name');
};

export const getSkillNameFromSetting = (value: string) => {
const matched = value.match(/\['(.*?)'\]/);
if (matched && matched.length > 1) {
return matched[1];
}
return '';
};
10 changes: 7 additions & 3 deletions Composer/packages/lib/shared/src/types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ export interface DialogSetting {
defaultLanguage: string;
languages: string[];
skill?: {
name: string;
manifestUrl: string;
}[];
[skillName: string]: {
name: string;
manifestUrl: string;
msAppId: string;
endpointUrl: string;
};
};
botId?: string;
skillHostEndpoint?: string;
[key: string]: any;
Expand Down
10 changes: 7 additions & 3 deletions Composer/packages/lib/shared/src/types/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.
/* eslint-disable @typescript-eslint/no-explicit-any */

import { DialogInfo, LuFile, LgFile, QnAFile, LuIntentSection, LgTemplate, DialogSchemaFile } from './indexers';
import { DialogInfo, LuFile, LgFile, QnAFile, LuIntentSection, LgTemplate, DialogSchemaFile, Skill } from './indexers';
import { UserSettings } from './settings';
import { OBISchema } from './schema';

Expand Down Expand Up @@ -57,7 +57,7 @@ export interface ShellData {
luFiles: LuFile[];
qnaFiles: QnAFile[];
userSettings: UserSettings;
skills: any[];
skills: Skill[];
// TODO: remove
schemas: BotSchemas;
}
Expand Down Expand Up @@ -95,11 +95,15 @@ export interface ShellApi {
redo: () => void;
commitChanges: () => void;
updateUserSettings: (settings: AllPartial<UserSettings>) => void;
addSkillDialog: () => Promise<{ manifestUrl: string } | null>;
addSkillDialog: () => Promise<{ manifestUrl: string; name: string } | null>;
announce: (message: string) => void;
displayManifestModal: (manifestId: string) => void;
updateDialogSchema: (_: DialogSchemaFile) => Promise<void>;
createTrigger: (id: string, formData, url?: string) => void;
skillsInSettings: {
get: (path: string) => any;
set: (skillName: string, skillsData: Partial<Skill>) => Promise<void>;
};
}

export interface Shell {
Expand Down
Loading

0 comments on commit c4212a5

Please sign in to comment.