From 7198c7ffd217a3be4b366a44664c4fa7acf8b764 Mon Sep 17 00:00:00 2001 From: liweitian Date: Mon, 2 Mar 2020 01:04:38 +0800 Subject: [PATCH] feat: update new trigger modal according to design (#1786) * update new trigger modal according to design * handle comments * auto-saved vscode lint setting * handle comments * css update * use luUtil api * feat: [Form Editor]inline lu editor in trigger (#1872) * lu in form editor * fix naming * bugfix * fix circular reference in shared and indexer * add start and end line for each intent * add diagnostics * delete lu editor in dialog * remove notice * fix bug: composer crashed when switch page to lu page * fix tslint error * support navigation back for lu error in notification * remove unused referrence * update the lib Co-authored-by: Zhixiang Zhan Co-authored-by: liweitian Co-authored-by: leileizhang * switch tsx back to js * switch js to tsx * add editor in trigger wizard * fix unit test * when lu is not valid, don't do update * handle comments * rebase code * resolve rebase conflict * enable inline mode * refactor luEditorWidget * replace third-part npm registry * update LgNotification class * handle comments * handle comments * fix lu editor widget * Configure Inline LU Editor to match Inline LG Editor Co-authored-by: Long Alan Co-authored-by: Zhixiang Zhan Co-authored-by: leileizhang Co-authored-by: zeye <2295905420@qq.com> Co-authored-by: Chris Whitten --- .../__tests__/components/design.test.js | 7 ++ Composer/packages/client/src/ShellApi.ts | 12 +- .../ProjectTree/TriggerCreationModal.tsx | 113 +++++++++++++----- .../src/components/ProjectTree/styles.ts | 18 ++- .../ExtensionContainer.tsx | 12 ++ .../client/src/pages/design/index.tsx | 11 +- .../client/src/pages/design/styles.js | 4 +- .../{styles.js => styles.ts} | 0 .../language-understanding/table-view.tsx | 2 +- .../client/src/pages/notifications/index.tsx | 36 +++--- .../client/src/pages/notifications/types.ts | 75 +++++++++++- .../pages/notifications/useNotifications.tsx | 40 ++----- .../packages/client/src/utils/dialogUtil.ts | 5 +- .../packages/client/src/utils/navigation.ts | 11 +- .../obiformeditor/demo/src/index.tsx | 3 + .../fields/RecognizerField/InlineLuEditor.tsx | 8 +- .../src/Form/fields/RecognizerField/index.tsx | 38 +----- .../src/Form/widgets/IntentWidget.tsx | 66 +++++----- .../src/Form/widgets/LuEditorWidget.tsx | 107 +++++++++++++++++ .../lib/indexers/src/dialogIndexer.ts | 16 ++- .../packages/lib/indexers/src/luIndexer.ts | 3 +- Composer/packages/lib/indexers/src/type.ts | 17 ++- .../lib/indexers/src/utils/diagnosticUtil.ts | 25 +++- .../packages/lib/indexers/src/utils/luUtil.ts | 3 +- .../packages/lib/shared/src/types/shell.ts | 20 ++++ Composer/yarn.lock | 54 ++++++--- 26 files changed, 483 insertions(+), 223 deletions(-) rename Composer/packages/client/src/pages/language-understanding/{styles.js => styles.ts} (100%) create mode 100644 Composer/packages/extensions/obiformeditor/src/Form/widgets/LuEditorWidget.tsx diff --git a/Composer/packages/client/__tests__/components/design.test.js b/Composer/packages/client/__tests__/components/design.test.js index ead441d63f..dbd3ce46fa 100644 --- a/Composer/packages/client/__tests__/components/design.test.js +++ b/Composer/packages/client/__tests__/components/design.test.js @@ -10,6 +10,13 @@ import { dialogs } from '../constants.json'; import { TriggerCreationModal } from './../../src/components/ProjectTree/TriggerCreationModal'; import { ProjectTree } from './../../src/components/ProjectTree'; import { CreateDialogModal } from './../../src/pages/design/createDialogModal'; + +jest.mock('@bfc/code-editor', () => { + return { + LuEditor: () =>
, + }; +}); + describe('', () => { it('should render the ProjectTree', async () => { const dialogId = 'Main'; diff --git a/Composer/packages/client/src/ShellApi.ts b/Composer/packages/client/src/ShellApi.ts index ddbacb8775..ed3dd20dea 100644 --- a/Composer/packages/client/src/ShellApi.ts +++ b/Composer/packages/client/src/ShellApi.ts @@ -217,9 +217,9 @@ export const ShellApi: React.FC = () => { if (!file) throw new Error(`lu file ${id} not found`); if (!intentName) throw new Error(`intentName is missing or empty`); - const newLuContent = luUtil.updateIntent(file.content, intentName, intent); + const content = luUtil.updateIntent(file.content, intentName, intent); - return await updateLuFile({ id, newLuContent }); + return await updateLuFile({ id, content }); } async function addLuIntentHandler({ id, intent }, event) { @@ -227,9 +227,9 @@ export const ShellApi: React.FC = () => { const file = luFiles.find(file => file.id === id); if (!file) throw new Error(`lu file ${id} not found`); - const newLuContent = luUtil.addIntent(file.content, intent); + const content = luUtil.addIntent(file.content, intent); - return await updateLuFile({ id, newLuContent }); + return await updateLuFile({ id, content }); } async function removeLuIntentHandler({ id, intentName }, event) { @@ -238,9 +238,9 @@ export const ShellApi: React.FC = () => { if (!file) throw new Error(`lu file ${id} not found`); if (!intentName) throw new Error(`intentName is missing or empty`); - const newLuContent = luUtil.removeIntent(file.content, intentName); + const content = luUtil.removeIntent(file.content, intentName); - return await updateLuFile({ id, newLuContent }); + return await updateLuFile({ id, content }); } async function fileHandler(fileTargetType, fileChangeType, { id, content }, event) { diff --git a/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx b/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx index ade8dd9461..472df83534 100644 --- a/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx @@ -7,11 +7,14 @@ import React, { useState, useContext } from 'react'; import formatMessage from 'format-message'; import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog'; import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button'; +import { Label } from 'office-ui-fabric-react/lib/Label'; import { Stack } from 'office-ui-fabric-react/lib/Stack'; import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; import { Dropdown } from 'office-ui-fabric-react/lib/Dropdown'; +import { TextField } from 'office-ui-fabric-react/lib/TextField'; +import { DialogInfo, luIndexer, combineMessage } from '@bfc/indexers'; import get from 'lodash/get'; -import { DialogInfo } from '@bfc/indexers'; +import { LuEditor } from '@bfc/code-editor'; import { addNewTrigger, @@ -25,15 +28,16 @@ import { getEventTypes, getActivityTypes, getMessageTypes, - regexRecognizerKey, } from '../../utils/dialogUtil'; +import { addIntent } from '../../utils/luUtil'; import { StoreContext } from '../../store'; -import { styles, dropdownStyles, dialogWindow } from './styles'; +import { styles, dropdownStyles, dialogWindow, intent } from './styles'; +const nameRegex = /^[a-zA-Z0-9-_.]+$/; const validateForm = (data: TriggerFormData): TriggerFormDataErrors => { const errors: TriggerFormDataErrors = {}; - const { $type, specifiedType } = data; + const { $type, specifiedType, intent, triggerPhrases } = data; if ($type === eventTypeKey && !specifiedType) { errors.specifiedType = formatMessage('Please select a event type'); @@ -46,21 +50,40 @@ const validateForm = (data: TriggerFormData): TriggerFormDataErrors => { if (!$type) { errors.$type = formatMessage('Please select a trigger type'); } + + if (!intent || !nameRegex.test(intent)) { + errors.intent = formatMessage( + 'Spaces and special characters are not allowed. Use letters, numbers, -, or _., numbers, -, and _' + ); + } + + if (!triggerPhrases) { + errors.triggerPhrases = formatMessage('Please input trigger phrases'); + } + if (data.errors.triggerPhrases) { + errors.triggerPhrases = data.errors.triggerPhrases; + } return errors; }; +interface LuFilePayload { + id: string; + content: string; +} + interface TriggerCreationModalProps { dialogId: string; isOpen: boolean; onDismiss: () => void; - onSubmit: (dialog: DialogInfo) => void; + onSubmit: (dialog: DialogInfo, luFilePayload: LuFilePayload) => void; } const initialFormData: TriggerFormData = { errors: {}, $type: intentTypeKey, - intent: '', specifiedType: '', + intent: '', + triggerPhrases: '', }; const triggerTypeOptions: IDropdownOption[] = getTriggerTypes(); @@ -71,7 +94,7 @@ export const TriggerCreationModal: React.FC = props = const { state } = useContext(StoreContext); const { dialogs, luFiles } = state; const luFile = luFiles.find(lu => lu.id === dialogId); - const dialogFile = dialogs.find(dialog => dialog.id === dialogId); + const onClickSubmitButton = e => { e.preventDefault(); const errors = validateForm(formData); @@ -83,8 +106,15 @@ export const TriggerCreationModal: React.FC = props = }); return; } + + const content = get(luFile, 'content', ''); + const newContent = addIntent(content, { Name: formData.intent, Body: formData.triggerPhrases }); + const updateLuFile = { + id: dialogId, + content: newContent, + }; const newDialog = addNewTrigger(dialogs, dialogId, formData); - onSubmit(newDialog); + onSubmit(newDialog, updateLuFile); onDismiss(); }; @@ -92,29 +122,27 @@ export const TriggerCreationModal: React.FC = props = setFormData({ ...initialFormData, $type: option.key }); }; - const onSelectIntent = (e, option) => { - setFormData({ ...formData, intent: option.key }); - }; - const onSelectSpecifiedTypeType = (e, option) => { setFormData({ ...formData, specifiedType: option.key }); }; + const onNameChange = (e, name) => { + setFormData({ ...formData, intent: name }); + }; + + const onTriggerPhrasesChange = (body: string) => { + const errors = formData.errors; + const content = '#' + formData.intent + '\n' + body; + const { diagnostics } = luIndexer.parse(content); + errors.triggerPhrases = combineMessage(diagnostics); + setFormData({ ...formData, triggerPhrases: body, errors }); + }; + const eventTypes: IDropdownOption[] = getEventTypes(); const activityTypes: IDropdownOption[] = getActivityTypes(); const messageTypes: IDropdownOption[] = getMessageTypes(); - const isRegEx = get(dialogFile, 'content.recognizer.$type', '') === regexRecognizerKey; - - const regexIntents = get(dialogFile, 'content.recognizer.intents', []); - const luisIntents = get(luFile, 'intents', []); - const intents = isRegEx ? regexIntents : luisIntents; - - const intentOptions = intents.map(t => { - return { key: t.name || t.Name || t.intent, text: t.name || t.Name || t.intent }; - }); - - const showIntentDropDown = formData.$type === intentTypeKey; + const showIntentFields = formData.$type === intentTypeKey; const showEventDropDown = formData.$type === eventTypeKey; const showActivityDropDown = formData.$type === activityTypeKey; const showMessageDropDown = formData.$type === messageTypeKey; @@ -178,15 +206,38 @@ export const TriggerCreationModal: React.FC = props = data-testid={'messageTypeDropDown'} /> )} - {showIntentDropDown && ( - + )} + {showIntentFields && } + {showIntentFields && ( + )} diff --git a/Composer/packages/client/src/components/ProjectTree/styles.ts b/Composer/packages/client/src/components/ProjectTree/styles.ts index afb5647bd4..0d53763b9a 100644 --- a/Composer/packages/client/src/components/ProjectTree/styles.ts +++ b/Composer/packages/client/src/components/ProjectTree/styles.ts @@ -137,7 +137,7 @@ export const dropdownStyles = { fontWeight: FontWeights.semibold, }, dropdown: { - width: '300px', + width: '400px', }, root: { paddingBottom: '20px', @@ -148,7 +148,7 @@ export const dialogWindow = css` display: flex; flex-direction: column; width: 400px; - height: 250px; + min-height: 300px; `; export const textFieldlabel = { @@ -162,11 +162,17 @@ export const textFieldlabel = { }; export const intent = { - fieldGroup: { - width: 200, + root: { + width: '400px', + paddingBottom: '20px', }, +}; + +export const triggerPhrases = { root: { - height: '90px', + width: '400px', + }, + fieldGroup: { + height: 80, }, - subComponentStyles: textFieldlabel, }; diff --git a/Composer/packages/client/src/extension-container/ExtensionContainer.tsx b/Composer/packages/client/src/extension-container/ExtensionContainer.tsx index bb37bc25b1..9ad680cfd9 100644 --- a/Composer/packages/client/src/extension-container/ExtensionContainer.tsx +++ b/Composer/packages/client/src/extension-container/ExtensionContainer.tsx @@ -98,6 +98,18 @@ const shellApi: ShellApi = { }); }, + addLuIntent: (id, intent) => { + return apiClient.apiCall('addLuIntent', { id, intent }); + }, + + updateLuIntent: (id, intentName, intent) => { + return apiClient.apiCall('updateLuIntent', { id, intentName, intent }); + }, + + removeLuIntent: (id, intentName) => { + return apiClient.apiCall('removeLuIntent', { id, intentName }); + }, + createDialog: () => { return apiClient.apiCall('createDialog'); }, diff --git a/Composer/packages/client/src/pages/design/index.tsx b/Composer/packages/client/src/pages/design/index.tsx index 778be7846f..25655f874f 100644 --- a/Composer/packages/client/src/pages/design/index.tsx +++ b/Composer/packages/client/src/pages/design/index.tsx @@ -171,14 +171,19 @@ function DesignPage(props) { setTriggerModalVisibility(true); }; - const onTriggerCreationSubmit = dialog => { - const payload = { + const onTriggerCreationSubmit = (dialog, luFile) => { + const dialogPayload = { id: dialog.id, content: dialog.content, }; + const luFilePayload = { + id: luFile.id, + content: luFile.content, + }; const index = get(dialog, 'content.triggers', []).length - 1; actions.selectTo(`triggers[${index}]`); - actions.updateDialog(payload); + actions.updateLuFile(luFilePayload); + actions.updateDialog(dialogPayload); }; function handleSelect(id, selected = '') { diff --git a/Composer/packages/client/src/pages/design/styles.js b/Composer/packages/client/src/pages/design/styles.js index 5b267a00eb..a155b225fd 100644 --- a/Composer/packages/client/src/pages/design/styles.js +++ b/Composer/packages/client/src/pages/design/styles.js @@ -113,9 +113,9 @@ export const middleTriggerContainer = css` display: flex; justify-content: center; align-items: center; - background: #e5e5e5; + background: #f6f6f6; width: 100%; - margin-top: 48px; + margin-top: 55px; height: calc(100% - 48px); position: absolute; `; diff --git a/Composer/packages/client/src/pages/language-understanding/styles.js b/Composer/packages/client/src/pages/language-understanding/styles.ts similarity index 100% rename from Composer/packages/client/src/pages/language-understanding/styles.js rename to Composer/packages/client/src/pages/language-understanding/styles.ts diff --git a/Composer/packages/client/src/pages/language-understanding/table-view.tsx b/Composer/packages/client/src/pages/language-understanding/table-view.tsx index b04afef158..2edbf09e13 100644 --- a/Composer/packages/client/src/pages/language-understanding/table-view.tsx +++ b/Composer/packages/client/src/pages/language-understanding/table-view.tsx @@ -79,7 +79,7 @@ const TableView: React.FC = props => { name, phrases, fileId: luFile.id, - used: luDialog ? luDialog.luIntents.includes(name) : false, // used by it's dialog or not + used: !!luDialog && luDialog.referredLuIntents.some(lu => lu.name === name), // used by it's dialog or not state, }); }); diff --git a/Composer/packages/client/src/pages/notifications/index.tsx b/Composer/packages/client/src/pages/notifications/index.tsx index cccac44dc3..093238013a 100644 --- a/Composer/packages/client/src/pages/notifications/index.tsx +++ b/Composer/packages/client/src/pages/notifications/index.tsx @@ -3,49 +3,41 @@ /** @jsx jsx */ import { jsx } from '@emotion/core'; -import { useState, useContext } from 'react'; +import { useState } from 'react'; import { RouteComponentProps } from '@reach/router'; -import { StoreContext } from '../../store'; - import { ToolBar } from './../../components/ToolBar/index'; import useNotifications from './useNotifications'; import { NotificationList } from './NotificationList'; import { NotificationHeader } from './NotificationHeader'; import { root } from './styles'; -import { INotification } from './types'; +import { INotification, NotificationType } from './types'; import { navigateTo } from './../../utils'; -import { convertDialogDiagnosticToUrl, toUrlUtil } from './../../utils/navigation'; +import { convertPathToUrl, toUrlUtil } from './../../utils/navigation'; const Notifications: React.FC = () => { const [filter, setFilter] = useState(''); - const { state } = useContext(StoreContext); - const { dialogs } = state; const notifications = useNotifications(filter); const navigations = { - lg: (item: INotification) => { + [NotificationType.LG]: (item: INotification) => { let url = `/language-generation/${item.id}/edit#L=${item.diagnostic.range?.start.line || 0}`; - const dividerIndex = item.id.indexOf('#'); //the format of item.id is lgFile#inlineTemplateId - if (dividerIndex > -1) { - const templateId = item.id.substring(dividerIndex + 1); - const lgFile = item.id.substring(0, dividerIndex); - const dialog = dialogs.find(d => d.lgFile === lgFile); - const lgTemplate = dialog ? dialog.lgTemplates.find(lg => lg.name === templateId) : null; - const path = lgTemplate ? lgTemplate.path : ''; - if (path && dialog) { - url = toUrlUtil(dialog.id, path); - } + if (item.dialogPath) { + url = toUrlUtil(item.dialogPath); } navigateTo(url); }, - lu: (item: INotification) => { - navigateTo(`/dialogs/${item.id}`); + [NotificationType.LU]: (item: INotification) => { + let uri = `/language-understanding/${item.id}`; + if (item.dialogPath) { + uri = convertPathToUrl(item.id, item.dialogPath); + } + navigateTo(uri); }, - dialog: (item: INotification) => { + [NotificationType.DIALOG]: (item: INotification) => { //path is like main.trigers[0].actions[0] //uri = id?selected=triggers[0]&focused=triggers[0].actions[0] - const uri = convertDialogDiagnosticToUrl(item.diagnostic); + const uri = convertPathToUrl(item.id, item.dialogPath); navigateTo(uri); }, }; diff --git a/Composer/packages/client/src/pages/notifications/types.ts b/Composer/packages/client/src/pages/notifications/types.ts index bce5eef0a0..707e3823da 100644 --- a/Composer/packages/client/src/pages/notifications/types.ts +++ b/Composer/packages/client/src/pages/notifications/types.ts @@ -1,13 +1,84 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { Diagnostic, createSingleMessage, DialogInfo, LuFile, isDiagnosticWithInRange } from '@bfc/indexers'; + +import { replaceDialogDiagnosticLabel } from '../../utils'; +export const DiagnosticSeverity = ['Error', 'Warning']; //'Information', 'Hint' + +export enum NotificationType { + DIALOG, + LG, + LU, + GENERAL, +} export interface INotification { id: string; severity: string; - type: string; + type: NotificationType; location: string; message: string; diagnostic: any; + dialogPath?: string; //the data path in dialog } -export const DiagnosticSeverity = ['Error', 'Warning']; //'Information', 'Hint' +export abstract class Notification implements INotification { + id: string; + severity: string; + type = NotificationType.GENERAL; + location: string; + message = ''; + diagnostic: Diagnostic; + dialogPath?: string; + constructor(id: string, location: string, diagnostic: Diagnostic) { + this.id = id; + this.severity = DiagnosticSeverity[diagnostic.severity] || ''; + this.diagnostic = diagnostic; + this.location = location; + } +} + +export class DialogNotification extends Notification { + type = NotificationType.DIALOG; + constructor(id: string, location: string, diagnostic: Diagnostic) { + super(id, location, diagnostic); + this.message = `In ${replaceDialogDiagnosticLabel(diagnostic.path)} ${diagnostic.message}`; + this.dialogPath = diagnostic.path; + } +} + +export class LgNotification extends Notification { + type = NotificationType.LG; + constructor(id: string, lgTemplateName: string, location: string, diagnostic: Diagnostic, dialogs: DialogInfo[]) { + super(id, location, diagnostic); + this.message = createSingleMessage(diagnostic); + this.dialogPath = this.findDialogPath(dialogs, id, lgTemplateName); + } + private findDialogPath(dialogs: DialogInfo[], id: string, lgTemplateName: string) { + if (lgTemplateName) { + const dialog = dialogs.find(d => d.lgFile === id); + const lgTemplate = dialog ? dialog.lgTemplates.find(lg => lg.name === lgTemplateName) : null; + const path = lgTemplate ? lgTemplate.path : ''; + return path; + } + } +} + +export class LuNotification extends Notification { + type = NotificationType.LU; + constructor(id: string, location: string, diagnostic: Diagnostic, luFile: LuFile, dialogs: DialogInfo[]) { + super(id, location, diagnostic); + this.dialogPath = this.findDialogPath(luFile, dialogs, diagnostic); + this.message = createSingleMessage(diagnostic); + } + + private findDialogPath(luFile: LuFile, dialogs: DialogInfo[], d: Diagnostic) { + const intentName = luFile.intents.find(intent => { + const { range } = intent; + if (!range) return false; + return isDiagnosticWithInRange(d, range); + })?.Name; + + return dialogs.find(dialog => dialog.id === luFile.id)?.referredLuIntents.find(lu => lu.name === intentName)?.path; + } +} diff --git a/Composer/packages/client/src/pages/notifications/useNotifications.tsx b/Composer/packages/client/src/pages/notifications/useNotifications.tsx index 8865060dc7..91441951de 100644 --- a/Composer/packages/client/src/pages/notifications/useNotifications.tsx +++ b/Composer/packages/client/src/pages/notifications/useNotifications.tsx @@ -2,44 +2,28 @@ // Licensed under the MIT License. import { useContext, useMemo } from 'react'; -import { createSingleMessage } from '@bfc/indexers'; import get from 'lodash/get'; import { LgNamePattern } from '@bfc/shared'; import { StoreContext } from '../../store'; -import { replaceDialogDiagnosticLabel } from '../../utils'; -import { INotification, DiagnosticSeverity } from './types'; +import { Notification, DialogNotification, LuNotification, LgNotification } from './types'; import { getReferredFiles } from './../../utils/luUtil'; export default function useNotifications(filter?: string) { const { state } = useContext(StoreContext); const { dialogs, luFiles, lgFiles } = state; const memoized = useMemo(() => { - const notifactions: INotification[] = []; + const notifactions: Notification[] = []; dialogs.forEach(dialog => { dialog.diagnostics.map(diagnostic => { const location = `${dialog.id}.dialog`; - notifactions.push({ - type: 'dialog', - location, - message: `In ${replaceDialogDiagnosticLabel(diagnostic.path)} ${diagnostic.message}`, - severity: DiagnosticSeverity[diagnostic.severity] || '', - diagnostic, - id: dialog.id, - }); + notifactions.push(new DialogNotification(dialog.id, location, diagnostic)); }); }); getReferredFiles(luFiles, dialogs).forEach(lufile => { lufile.diagnostics.map(diagnostic => { const location = `${lufile.id}.lu`; - notifactions.push({ - type: 'lu', - location, - message: createSingleMessage(diagnostic), - severity: 'Error', - diagnostic, - id: lufile.id, - }); + notifactions.push(new LuNotification(lufile.id, location, diagnostic, lufile, dialogs)); }); }); lgFiles.forEach(lgFile => { @@ -53,26 +37,20 @@ export default function useNotifications(filter?: string) { get(diagnostic, 'range.start.line') >= get(t, 'range.startLineNumber') && get(diagnostic, 'range.end.line') <= get(t, 'range.endLineNumber') ); - let id = lgFile.id; + const id = lgFile.id; const location = `${lgFile.id}.lg`; + let lgTemplateName = ''; if (mappedTemplate && mappedTemplate.name.match(LgNamePattern)) { //should navigate to design page - id = `${lgFile.id}#${mappedTemplate.name}`; + lgTemplateName = mappedTemplate.name; } - notifactions.push({ - type: 'lg', - severity: DiagnosticSeverity[diagnostic.severity] || '', - location, - message: createSingleMessage(diagnostic), - diagnostic, - id, - }); + notifactions.push(new LgNotification(id, lgTemplateName, location, diagnostic, dialogs)); }); }); return notifactions; }, [dialogs, luFiles, lgFiles]); - const notifications: INotification[] = !filter ? memoized : memoized.filter(x => x.severity === filter); + const notifications: Notification[] = filter ? memoized.filter(x => x.severity === filter) : memoized; return notifications; } diff --git a/Composer/packages/client/src/utils/dialogUtil.ts b/Composer/packages/client/src/utils/dialogUtil.ts index e388decfb0..e54934034c 100644 --- a/Composer/packages/client/src/utils/dialogUtil.ts +++ b/Composer/packages/client/src/utils/dialogUtil.ts @@ -21,14 +21,16 @@ interface DialogsMap { export interface TriggerFormData { errors: TriggerFormDataErrors; $type: string; - intent: string; specifiedType: string; + intent: string; + triggerPhrases: string; } export interface TriggerFormDataErrors { $type?: string; intent?: string; specifiedType?: string; + triggerPhrases?: string; } export function getDialog(dialogs: DialogInfo[], dialogId: string) { @@ -67,7 +69,6 @@ export function insert(content, path: string, position: number | undefined, data if (data.intent) { optionalAttributes.intent = data.intent; } - const newStep = { $type: data.$type, ...seedNewDialog(data.$type, {}, optionalAttributes), diff --git a/Composer/packages/client/src/utils/navigation.ts b/Composer/packages/client/src/utils/navigation.ts index 415f6285a1..117734326b 100644 --- a/Composer/packages/client/src/utils/navigation.ts +++ b/Composer/packages/client/src/utils/navigation.ts @@ -3,7 +3,6 @@ import cloneDeep from 'lodash/cloneDeep'; import { navigate, NavigateOptions } from '@reach/router'; -import { Diagnostic } from '@bfc/indexers'; import { BreadcrumbItem, DesignPageLocation } from '../store/types'; @@ -77,13 +76,11 @@ interface NavigationState { breadcrumb: BreadcrumbItem[]; } -export function convertDialogDiagnosticToUrl(diagnostic: Diagnostic): string { +export function convertPathToUrl(id: string, path?: string): string { //path is like main.trigers[0].actions[0] //uri = id?selected=triggers[0]&focused=triggers[0].actions[0] - const { path, source } = diagnostic; - if (!source) return ''; - let uri = `/dialogs/${source}`; + let uri = `/dialogs/${id}`; if (!path) return uri; const items = path.split('#'); @@ -107,8 +104,10 @@ export function convertDialogDiagnosticToUrl(diagnostic: Diagnostic): string { return uri; } -export function toUrlUtil(dialogId: string, path: string): string { +export function toUrlUtil(path: string): string { const tokens = path.split('#'); + const firstDotIndex = tokens[0].indexOf('.'); + const dialogId = tokens[0].substring(0, firstDotIndex); const focusedPath = parsePathToFocused(tokens[0]); const selectedPath = parsePathToSelected(tokens[0]); const type = tokens[1]; diff --git a/Composer/packages/extensions/obiformeditor/demo/src/index.tsx b/Composer/packages/extensions/obiformeditor/demo/src/index.tsx index 8897c9d207..a399e293d7 100644 --- a/Composer/packages/extensions/obiformeditor/demo/src/index.tsx +++ b/Composer/packages/extensions/obiformeditor/demo/src/index.tsx @@ -173,6 +173,9 @@ const mockShellApi = [ 'getLgTemplates', 'createLgTemplate', 'updateLgTemplate', + 'addLuIntent', + 'updateLuIntent', + 'removeLuIntent', 'validateExpression', 'onFocusSteps', 'onFocusEvent', diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/InlineLuEditor.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/InlineLuEditor.tsx index 65f989aabf..2652028a8e 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/InlineLuEditor.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/InlineLuEditor.tsx @@ -20,8 +20,12 @@ const InlineLuEditor: React.FC = props => { setLocalContent(value); onSave(value); }; - - return ; + const luOption = { + fileId: file.id, + }; + return ( + + ); }; export default InlineLuEditor; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/index.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/index.tsx index a4ba6f1048..5a490cfda5 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/index.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/index.tsx @@ -1,28 +1,24 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import React, { useState, ReactElement, Suspense, useEffect } from 'react'; +import React, { useState, ReactElement } from 'react'; import formatMessage from 'format-message'; import { FieldProps } from '@bfcomposer/react-jsonschema-form'; import { Dropdown, ResponsiveMode, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner'; import { MicrosoftIRecognizer } from '@bfc/shared'; -import { LuFile, combineMessage } from '@bfc/indexers'; +import { LuFile } from '@bfc/indexers'; import { BaseField } from '../BaseField'; -import { LoadingSpinner } from '../../../LoadingSpinner'; import ToggleEditor from './ToggleEditor'; import RegexEditor from './RegexEditor'; import './styles.css'; -const InlineLuEditor = React.lazy(() => import('./InlineLuEditor')); - export const RecognizerField: React.FC> = props => { const { formData } = props; const [loading, setLoading] = useState(false); - const [errorMsg, setErrorMsg] = useState(''); const { formContext: { luFiles, shellApi, currentDialog }, @@ -32,19 +28,6 @@ export const RecognizerField: React.FC> = props const isRegex = typeof formData === 'object' && formData.$type === 'Microsoft.RegexRecognizer'; const currentDialogId = currentDialog.id; const selectedFile: LuFile | void = luFiles.find(f => f.id === currentDialogId); - const isLuFileSelected = Boolean( - selectedFile && typeof props.formData === 'string' && props.formData.startsWith(selectedFile.id) - ); - - //make the inline editor show error message - useEffect(() => { - if (selectedFile && selectedFile.diagnostics.length > 0) { - const msg = combineMessage(selectedFile.diagnostics); - setErrorMsg(msg); - } else { - setErrorMsg(''); - } - }, [selectedFile]); const handleChange = (_, option?: IDropdownOption): void => { if (option) { @@ -144,23 +127,8 @@ export const RecognizerField: React.FC> = props responsiveMode={ResponsiveMode.large} onRenderTitle={onRenderTitle} /> - + {() => { - if (selectedFile && isLuFileSelected) { - const updateLuFile = (newValue?: string): void => { - shellApi.updateLuFile({ id: selectedFile.id, content: newValue }).catch(setErrorMsg); - }; - - return ( - }> - - - ); - } if (isRegex) { return ; } diff --git a/Composer/packages/extensions/obiformeditor/src/Form/widgets/IntentWidget.tsx b/Composer/packages/extensions/obiformeditor/src/Form/widgets/IntentWidget.tsx index dbe9bef687..bc3579bb6d 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/widgets/IntentWidget.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/widgets/IntentWidget.tsx @@ -5,11 +5,12 @@ import React from 'react'; import { Dropdown, ResponsiveMode, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; import formatMessage from 'format-message'; import { RegexRecognizer } from '@bfc/shared'; -import { LuFile, DialogInfo } from '@bfc/indexers'; +import { DialogInfo } from '@bfc/indexers'; -import { BFDWidgetProps, FormContext } from '../types'; +import { BFDWidgetProps } from '../types'; import { WidgetLabel } from './WidgetLabel'; +import { LuEditorWidget } from './LuEditorWidget'; const EMPTY_OPTION = { key: '', text: '' }; @@ -48,42 +49,26 @@ function regexIntentOptions(currentDialog: DialogInfo): IDropdownOption[] { return options; } -function luIntentOptions(formContext: FormContext): IDropdownOption[] { - const luFile: LuFile | void = formContext.luFiles.find(f => f.id === formContext.currentDialog.id); - let options: IDropdownOption[] = [EMPTY_OPTION]; - - if (luFile) { - const intents: { name: string }[] = luFile.intents.map(({ Name: name }) => { - return { - name, - }; - }); - - options = options.concat( - intents.map(i => ({ - key: i.name, - text: i.name, - })) - ); - } - - return options; -} - export const IntentWidget: React.FC = props => { const { disabled, onChange, id, onFocus, onBlur, value, formContext, placeholder, label, schema } = props; const { description } = schema; + const { currentDialog } = formContext; let options: IDropdownOption[] = []; + let widgetLabel = label; + let isLuisSelected = false; - switch (recognizerType(formContext.currentDialog)) { + switch (recognizerType(currentDialog)) { case RecognizerType.regex: - options = regexIntentOptions(formContext.currentDialog); + options = regexIntentOptions(currentDialog); + isLuisSelected = false; break; case RecognizerType.luis: - options = luIntentOptions(formContext); + widgetLabel = `Trigger phrases(intent name: #${value || ''})`; + isLuisSelected = true; break; default: options = [EMPTY_OPTION]; + isLuisSelected = false; break; } @@ -95,18 +80,21 @@ export const IntentWidget: React.FC = props => { return ( <> - - onBlur && onBlur(id, value)} - onChange={handleChange} - onFocus={() => onFocus && onFocus(id, value)} - options={options} - selectedKey={value || null} - responsiveMode={ResponsiveMode.large} - disabled={disabled || options.length === 1} - placeholder={options.length > 1 ? placeholder : formatMessage('No intents configured for this dialog')} - /> + + {!isLuisSelected && ( + onBlur && onBlur(id, value)} + onChange={handleChange} + onFocus={() => onFocus && onFocus(id, value)} + options={options} + selectedKey={value || null} + responsiveMode={ResponsiveMode.large} + disabled={disabled || options.length === 1} + placeholder={options.length > 1 ? placeholder : formatMessage('No intents configured for this dialog')} + /> + )} + {isLuisSelected && } ); }; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/widgets/LuEditorWidget.tsx b/Composer/packages/extensions/obiformeditor/src/Form/widgets/LuEditorWidget.tsx new file mode 100644 index 0000000000..0d05aac956 --- /dev/null +++ b/Composer/packages/extensions/obiformeditor/src/Form/widgets/LuEditorWidget.tsx @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import React from 'react'; +import { LuEditor } from '@bfc/code-editor'; +import debounce from 'lodash/debounce'; +import { LuIntentSection } from '@bfc/shared'; +import { LuFile, filterSectionDiagnostics } from '@bfc/indexers'; + +import { FormContext } from '../types'; + +interface LuEditorWidgetProps { + formContext: FormContext; + name: string; + height?: number | string; + onChange: (template?: string) => void; +} + +export class LuEditorWidget extends React.Component { + constructor(props) { + super(props); + this.debounceUpdate = debounce(this.updateLuIntent, 500); + this.name = this.props.name; + this.formContext = this.props.formContext; + this.luFileId = this.formContext.currentDialog.id; + this.luFile = this.formContext.luFiles.find(f => f.id === this.luFileId); + this.luIntent = (this.luFile && this.luFile.intents.find(intent => intent.Name === this.name)) || { + Name: this.name, + Body: '', + }; + } + + formContext: FormContext; + name: string; + luFileId: string; + luFile: LuFile | null; + luIntent: LuIntentSection; + state = { localValue: '' }; + debounceUpdate; + updateLuIntent = (body: string) => { + this.formContext.shellApi.updateLuIntent(this.luFileId, this.name, { Name: this.name, Body: body }).catch(() => {}); + }; + + static getDerivedStateFromProps(nextProps, prevState) { + const name = nextProps.name; + const formContext = nextProps.formContext; + const luFileId = formContext.currentDialog.id; + const luFile = formContext.luFiles.find(f => f.id === luFileId); + const luIntent = (luFile && luFile.intents.find(intent => intent.Name === name)) || { + Name: name, + Body: '', + }; + if (!prevState.localValue) { + return { + localValue: luIntent.Body, + }; + } + return null; + } + + onChange = (body: string) => { + this.setState({ + localValue: body, + }); + if (this.luFileId) { + if (body) { + this.updateLuIntent(body); + } else { + this.formContext.shellApi.removeLuIntent(this.luFileId, this.name); + } + } + }; + render() { + const diagnostic = this.luFile && filterSectionDiagnostics(this.luFile.diagnostics, this.luIntent)[0]; + + const errorMsg = diagnostic + ? diagnostic.message.split('error message: ')[diagnostic.message.split('error message: ').length - 1] + : ''; + const luOption = { + fileId: this.luFileId, + sectionId: this.luIntent?.Name, + }; + const height = this.props.height || 250; + + return ( + + ); + } +} diff --git a/Composer/packages/lib/indexers/src/dialogIndexer.ts b/Composer/packages/lib/indexers/src/dialogIndexer.ts index 0940379b96..eb36ee6ae6 100644 --- a/Composer/packages/lib/indexers/src/dialogIndexer.ts +++ b/Composer/packages/lib/indexers/src/dialogIndexer.ts @@ -5,8 +5,9 @@ import has from 'lodash/has'; import uniq from 'lodash/uniq'; import { extractLgTemplateRefs } from '@bfc/shared'; +import { createPath } from './dialogUtils/dialogChecker'; import { checkerFuncs } from './dialogUtils/dialogChecker'; -import { ITrigger, DialogInfo, FileInfo, LgTemplateJsonPath } from './type'; +import { ITrigger, DialogInfo, FileInfo, LgTemplateJsonPath, ReferredLuIntents } from './type'; import { JsonWalk, VisitorFunc } from './utils/jsonWalk'; import { getBaseName } from './utils/help'; import { Diagnostic } from './diagnostic'; @@ -70,8 +71,8 @@ function ExtractLgTemplates(id, dialog): LgTemplateJsonPath[] { } // find out all lu intents given dialog -function ExtractLuIntents(dialog): string[] { - const intents: string[] = []; +function ExtractLuIntents(dialog, id: string): ReferredLuIntents[] { + const intents: ReferredLuIntents[] = []; /** * * @param path , jsonPath string * @param value , current node value * @@ -81,11 +82,14 @@ function ExtractLuIntents(dialog): string[] { // it's a valid schema dialog node. if (has(value, '$type') && value.$type === 'Microsoft.OnIntent') { const intentName = value.intent; - intents.push(intentName); + intents.push({ + name: intentName, + path: createPath(path, value.$type), + }); } return false; }; - JsonWalk('$', dialog, visitor); + JsonWalk(id, dialog, visitor); return uniq(intents); } @@ -195,8 +199,8 @@ function parse(id: string, content: any, schema: any) { diagnostics: validate(id, content, schema), referredDialogs: ExtractReferredDialogs(content), lgTemplates: ExtractLgTemplates(id, content), - luIntents: ExtractLuIntents(content), userDefinedVariables: ExtractMemoryPaths(content), + referredLuIntents: ExtractLuIntents(content, id), luFile: getBaseName(luFile, '.lu'), lgFile: getBaseName(lgFile, '.lg'), triggers: ExtractTriggers(content), diff --git a/Composer/packages/lib/indexers/src/luIndexer.ts b/Composer/packages/lib/indexers/src/luIndexer.ts index 49a579bcc0..164ea2d89c 100644 --- a/Composer/packages/lib/indexers/src/luIndexer.ts +++ b/Composer/packages/lib/indexers/src/luIndexer.ts @@ -3,8 +3,9 @@ import { sectionHandler } from '@bfcomposer/bf-lu/lib/parser'; import get from 'lodash/get'; +import { LuIntentSection } from '@bfc/shared'; -import { FileInfo, LuFile, LuParsed, LuSectionTypes, LuIntentSection } from './type'; +import { FileInfo, LuFile, LuParsed, LuSectionTypes } from './type'; import { getBaseName } from './utils/help'; import { Diagnostic, Position, Range, DiagnosticSeverity } from './diagnostic'; import { FileExtensions } from './utils/fileExtensions'; diff --git a/Composer/packages/lib/indexers/src/type.ts b/Composer/packages/lib/indexers/src/type.ts index 5e9f3d62ce..5b47641a57 100644 --- a/Composer/packages/lib/indexers/src/type.ts +++ b/Composer/packages/lib/indexers/src/type.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { LuIntentSection } from '@bfc/shared'; + import { Diagnostic } from './diagnostic'; export interface FileInfo { @@ -17,6 +19,11 @@ export interface ITrigger { isIntent: boolean; } +export interface ReferredLuIntents { + name: string; + path: string; +} + export interface DialogInfo { content: any; diagnostics: Diagnostic[]; @@ -26,7 +33,7 @@ export interface DialogInfo { lgFile: string; lgTemplates: LgTemplateJsonPath[]; luFile: string; - luIntents: string[]; + referredLuIntents: ReferredLuIntents[]; referredDialogs: string[]; relativePath: string; userDefinedVariables: string[]; @@ -62,14 +69,6 @@ export interface LuEntity { Name: string; } -export interface LuIntentSection { - Name: string; - Body: string; - Entities?: LuEntity[]; - Children?: LuIntentSection[]; - range?: CodeRange; -} - export interface LuFile { id: string; relativePath: string; diff --git a/Composer/packages/lib/indexers/src/utils/diagnosticUtil.ts b/Composer/packages/lib/indexers/src/utils/diagnosticUtil.ts index 41695327c2..92c16061f3 100644 --- a/Composer/packages/lib/indexers/src/utils/diagnosticUtil.ts +++ b/Composer/packages/lib/indexers/src/utils/diagnosticUtil.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { LuIntentSection } from '@bfc/shared'; + import { Diagnostic, DiagnosticSeverity, Range, Position } from '../diagnostic'; import { CodeRange } from '../type'; @@ -28,10 +30,15 @@ export function offsetRange(range: Range, offset: number): Range { ); } +export function isDiagnosticWithInRange(diagnostic: Diagnostic, range: CodeRange): boolean { + if (!diagnostic.range) return false; + return diagnostic.range.start.line >= range.startLineNumber && diagnostic.range.end.line <= range.endLineNumber; +} + export function filterTemplateDiagnostics(diagnostics: Diagnostic[], { range }: { range?: CodeRange }): Diagnostic[] { if (!range) return diagnostics; const filteredDiags = diagnostics.filter(d => { - return d.range && d.range.start.line >= range.startLineNumber && d.range.end.line <= range.endLineNumber; + return d.range && isDiagnosticWithInRange(d, range); }); const offset = range.startLineNumber; return filteredDiags.map(d => { @@ -46,6 +53,22 @@ export function filterTemplateDiagnostics(diagnostics: Diagnostic[], { range }: }); } +export function filterSectionDiagnostics(diagnostics: Diagnostic[], section: LuIntentSection): Diagnostic[] { + const { range } = section; + if (!range) return diagnostics; + const filteredDiags = diagnostics.filter(d => { + return isDiagnosticWithInRange(d, range); + }); + const offset = range.startLineNumber; + return filteredDiags.map(d => { + const { range } = d; + if (range) { + d.range = offsetRange(range, offset); + } + return d; + }); +} + export function findErrors(diagnostics: Diagnostic[]): Diagnostic[] { return diagnostics.filter(d => d.severity === DiagnosticSeverity.Error); } diff --git a/Composer/packages/lib/indexers/src/utils/luUtil.ts b/Composer/packages/lib/indexers/src/utils/luUtil.ts index ef0867686d..0baac955ff 100644 --- a/Composer/packages/lib/indexers/src/utils/luUtil.ts +++ b/Composer/packages/lib/indexers/src/utils/luUtil.ts @@ -9,8 +9,9 @@ import { sectionHandler } from '@bfcomposer/bf-lu/lib/parser'; import isEmpty from 'lodash/isEmpty'; +import { LuIntentSection } from '@bfc/shared'; -import { LuIntentSection, LuSectionTypes } from '../type'; +import { LuSectionTypes } from '../type'; import { luIndexer } from '../luIndexer'; import { Diagnostic } from '../diagnostic'; const { parse } = luIndexer; diff --git a/Composer/packages/lib/shared/src/types/shell.ts b/Composer/packages/lib/shared/src/types/shell.ts index 0077441749..521d3600a1 100644 --- a/Composer/packages/lib/shared/src/types/shell.ts +++ b/Composer/packages/lib/shared/src/types/shell.ts @@ -3,6 +3,23 @@ import { LGTemplate as LgTemplate } from 'botbuilder-lg'; +export interface LuIntentSection { + Name: string; + Body: string; + Entities?: LuEntity[]; + Children?: LuIntentSection[]; + range?: CodeRange; +} + +export interface CodeRange { + startLineNumber: number; + endLineNumber: number; +} + +export interface LuEntity { + Name: string; +} + export interface EditorSchema { content?: { fieldTemplateOverrides?: any; @@ -53,6 +70,9 @@ export interface ShellApi { updateLgTemplate: (id: string, templateName: string, templateStr: string) => Promise; removeLgTemplate: (id: string, templateName: string) => Promise; removeLgTemplates: (id: string, templateNames: string[]) => Promise; + addLuIntent: (id: string, intent: LuIntentSection | null) => Promise; + updateLuIntent: (id: string, intentName: string, intent: LuIntentSection | null) => Promise; + removeLuIntent: (id: string, intentName: string) => Promise; createDialog: () => Promise; validateExpression: (expression?: string) => Promise; // TODO: fix these types diff --git a/Composer/yarn.lock b/Composer/yarn.lock index 160659b32a..be76d64dd2 100644 --- a/Composer/yarn.lock +++ b/Composer/yarn.lock @@ -1972,6 +1972,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.2.0", "@babel/runtime@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308" + integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4": version "7.4.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.2.tgz#f5ab6897320f16decd855eed70b705908a313fe8" @@ -2014,13 +2021,6 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308" - integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ== - dependencies: - regenerator-runtime "^0.13.2" - "@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.2.2", "@babel/template@^7.4.0": version "7.4.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.0.tgz#12474e9c077bae585c5d835a95c0b0b790c25c8b" @@ -8554,7 +8554,7 @@ findup-sync@3.0.0: findup-sync@^2.0.0: version "2.0.0" - resolved "https://registry.npm.taobao.org/findup-sync/download/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" integrity sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw= dependencies: detect-file "^1.0.0" @@ -8869,6 +8869,11 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-node-dimensions@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-node-dimensions/-/get-node-dimensions-1.2.1.tgz#fb7b4bb57060fb4247dd51c9d690dfbec56b0823" + integrity sha512-2MSPMu7S1iOTL+BOa6K1S62hB2zUAYNF/lV0gSVlOaacd087lc6nR1H1r0e3B1CerTo+RceOmi1iJW+vp21xcQ== + get-own-enumerable-property-symbols@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz#b877b49a5c16aefac3655f2ed2ea5b684df8d203" @@ -9561,7 +9566,7 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -https-proxy-agent@^2.2.1: +https-proxy-agent@^2.2.1, https-proxy-agent@^2.2.3: version "2.2.4" resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" integrity sha1-TuenN6vZJniik9mzShr00NCMeHs= @@ -14474,6 +14479,16 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-measure@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/react-measure/-/react-measure-2.3.0.tgz#75835d39abec9ae13517f35a819c160997a7a44e" + integrity sha512-dwAvmiOeblj5Dvpnk8Jm7Q8B4THF/f1l1HtKVi0XDecsG6LXwGvzV5R1H32kq3TW6RW64OAf5aoQxpIgLa4z8A== + dependencies: + "@babel/runtime" "^7.2.0" + get-node-dimensions "^1.2.1" + prop-types "^15.6.2" + resize-observer-polyfill "^1.5.0" + react-testing-library@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/react-testing-library/-/react-testing-library-6.0.2.tgz#afd7ddaa174e21cf672605e4e4f6f8156c4c9ef9" @@ -14622,8 +14637,8 @@ realpath-native@^1.1.0: reconnecting-websocket@^3.2.2: version "3.2.2" - resolved "https://registry.npm.taobao.org/reconnecting-websocket/download/reconnecting-websocket-3.2.2.tgz#8097514e926e9855e03c39e76efa2e3d1f371bee" - integrity sha1-gJdRTpJumFXgPDnnbvouPR83G+4= + resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-3.2.2.tgz#8097514e926e9855e03c39e76efa2e3d1f371bee" + integrity sha512-SWSfoXiaHVOqXuPWFgGWeUxKnb5HIY7I/Fh5C/hy4wUOgeOh7YIMXEiv5/eHBlNs4tNzCrO5YDR9AH62NWle0Q== recursive-readdir@2.2.2: version "2.2.2" @@ -14834,13 +14849,13 @@ replace-ext@1.0.0: integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= request-light@^0.2.2: - version "0.2.4" - resolved "https://registry.npm.taobao.org/request-light/download/request-light-0.2.4.tgz#3cea29c126682e6bcadf7915353322eeba01a755" - integrity sha1-POopwSZoLmvK33kVNTMi7roBp1U= + version "0.2.5" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.5.tgz#38a3da7b2e56f7af8cbba57e8a94930ee2380746" + integrity sha512-eBEh+GzJAftUnex6tcL6eV2JCifY0+sZMIUpUPOVXbs2nV5hla4ZMmO3icYKGuGVuQ2zHE9evh4OrRcH4iyYYw== dependencies: http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.1" - vscode-nls "^4.0.0" + https-proxy-agent "^2.2.3" + vscode-nls "^4.1.1" request-progress@3.0.0: version "3.0.0" @@ -14926,6 +14941,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= +resize-observer-polyfill@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" + integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" @@ -17102,7 +17122,7 @@ vscode-languageserver@^5.3.0-next: vscode-languageserver-protocol "^3.15.0-next.8" vscode-textbuffer "^1.0.0" -vscode-nls@^4.0.0, vscode-nls@^4.1.1: +vscode-nls@^4.1.1: version "4.1.1" resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" integrity sha1-+ZFrZOSUeyAyLe+x5naklYYfEzw=