diff --git a/Composer/cypress/integration/RemoveDialog.spec.ts b/Composer/cypress/integration/RemoveDialog.spec.ts index 6e419dc6aa..fb212a2cca 100644 --- a/Composer/cypress/integration/RemoveDialog.spec.ts +++ b/Composer/cypress/integration/RemoveDialog.spec.ts @@ -9,7 +9,7 @@ context('RemoveDialog', () => { it('can remove dialog', () => { cy.findByTestId('ProjectTree').within(() => { - cy.findByTestId('$Root_additem').within(() => { + cy.findByTestId('$Root_AddItem').within(() => { cy.findByTestId('dialogMoreButton').first().invoke('attr', 'style', 'visibility: visible').click(); }); }); diff --git a/Composer/packages/adaptive-flow/src/adaptive-flow-renderer/widgets/TriggerSummary/TriggerSummary.tsx b/Composer/packages/adaptive-flow/src/adaptive-flow-renderer/widgets/TriggerSummary/TriggerSummary.tsx index e03c63b357..6ae3f09ae2 100644 --- a/Composer/packages/adaptive-flow/src/adaptive-flow-renderer/widgets/TriggerSummary/TriggerSummary.tsx +++ b/Composer/packages/adaptive-flow/src/adaptive-flow-renderer/widgets/TriggerSummary/TriggerSummary.tsx @@ -3,10 +3,9 @@ /** @jsx jsx */ -import { conceptLabels } from '@bfc/shared'; +import { conceptLabels, getFriendlyName } from '@bfc/shared'; import { jsx } from '@emotion/core'; import { Icon } from 'office-ui-fabric-react/lib/Icon'; -import get from 'lodash/get'; import { triggerContainerStyle, @@ -25,15 +24,8 @@ function getLabel(data: any): string { return data.$kind; } -function getName(data: any): string { - return ( - data.intent || - get(data, '$designer.name', conceptLabels()[data.$kind] ? conceptLabels()[data.$kind].title : data.$kind) - ); -} - export const TriggerSummary = ({ data, onClick = () => {} }): JSX.Element => { - const name = getName(data); + const name = getFriendlyName(data); const label = getLabel(data); return ( diff --git a/Composer/packages/client/__tests__/utils/dialogUtil.test.js b/Composer/packages/client/__tests__/utils/dialogUtil.test.js index 1438f5df6e..8fc353545c 100644 --- a/Composer/packages/client/__tests__/utils/dialogUtil.test.js +++ b/Composer/packages/client/__tests__/utils/dialogUtil.test.js @@ -9,7 +9,6 @@ import { updateRegExIntent, createSelectedPath, deleteTrigger, - getFriendlyName, getBreadcrumbLabel, getSelected, } from '../../src/utils/dialogUtil'; @@ -178,13 +177,6 @@ describe('deleteTrigger', () => { }); }); -describe('getFriendlyName', () => { - it('return friendly name', () => { - const name = getFriendlyName(dialogs[0].content); - expect(name).toBe('kind1'); - }); -}); - describe('getBreadcrumbLabel', () => { it('return breadcrumb label', () => { const name = getBreadcrumbLabel(dialogs, 'id1', null, null); diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx index b20adc223d..c1fb2a85a6 100644 --- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx +++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx @@ -7,7 +7,7 @@ import { jsx, css } from '@emotion/core'; import { SearchBox } from 'office-ui-fabric-react/lib/SearchBox'; import { FocusZone, FocusZoneDirection } from 'office-ui-fabric-react/lib/FocusZone'; import formatMessage from 'format-message'; -import { DialogInfo, ITrigger, Diagnostic, DiagnosticSeverity, LanguageFileImport } from '@bfc/shared'; +import { DialogInfo, ITrigger, Diagnostic, DiagnosticSeverity, LanguageFileImport, getFriendlyName } from '@bfc/shared'; import debounce from 'lodash/debounce'; import throttle from 'lodash/throttle'; import { useRecoilValue } from 'recoil'; @@ -23,7 +23,6 @@ import { pageElementState, projectTreeSelectorFamily, } from '../../recoilModel'; -import { getFriendlyName } from '../../utils/dialogUtil'; import { triggerNotSupported } from '../../utils/dialogValidator'; import { useFeatureFlag } from '../../utils/hooks'; import { LoadingSpinner } from '../LoadingSpinner'; diff --git a/Composer/packages/client/src/pages/design/VisualPanelHeader.tsx b/Composer/packages/client/src/pages/design/VisualPanelHeader.tsx index c019fe8a89..6fed7931d1 100644 --- a/Composer/packages/client/src/pages/design/VisualPanelHeader.tsx +++ b/Composer/packages/client/src/pages/design/VisualPanelHeader.tsx @@ -9,17 +9,12 @@ import { Breadcrumb, IBreadcrumbItem } from 'office-ui-fabric-react/lib/Breadcru import { ActionButton } from 'office-ui-fabric-react/lib/Button'; import { useRecoilValue } from 'recoil'; import { PluginConfig } from '@bfc/extension-client'; -import { DialogInfo } from '@bfc/shared'; +import { DialogInfo, getFriendlyName } from '@bfc/shared'; import get from 'lodash/get'; import { TreeLink } from '../../components/ProjectTree/ProjectTree'; -import { - designPageLocationState, - dialogsSelectorFamily, - dispatcherState, - visualEditorSelectionState, -} from '../../recoilModel'; -import { getDialogData, getFriendlyName } from '../../utils/dialogUtil'; +import { designPageLocationState, dialogsSelectorFamily, dispatcherState } from '../../recoilModel'; +import { getDialogData } from '../../utils/dialogUtil'; import { decodeDesignerPathToArrayPath } from '../../utils/convertUtils/designerPathEncoder'; import { getFocusPath } from '../../utils/navigation'; @@ -40,6 +35,21 @@ type VisualPanelHeaderProps = { pluginConfig?: PluginConfig; }; +// field types +const BreadcrumbKeyPrefix = { + Dialog: 'D', + Trigger: 'T', + Action: 'A', +}; + +const buildKey = (prefix: string, name: string | number): string => { + return `${prefix}-${name}`; +}; + +const parseKey = (key: string): { prefix: string; name: string } => { + return { prefix: key.charAt(0), name: key.substr(2) }; +}; + const parseTriggerId = (triggerId: string | undefined): number | undefined => { if (triggerId == null) return undefined; const indexString = triggerId.match(/\d+/)?.[0]; @@ -71,7 +81,6 @@ const useBreadcrumbs = (projectId: string, pluginConfig?: PluginConfig) => { const designPageLocation = useRecoilValue(designPageLocationState(projectId)); const dialogs = useRecoilValue(dialogsSelectorFamily(projectId)); const { navTo } = useRecoilValue(dispatcherState); - const visualEditorSelection = useRecoilValue(visualEditorSelectionState); const { dialogId, selected: encodedSelect, focused: encodedFocused } = designPageLocation; const dialogMap = dialogs.reduce((acc, { content, id }) => ({ ...acc, [id]: content }), {}); @@ -85,10 +94,10 @@ const useBreadcrumbs = (projectId: string, pluginConfig?: PluginConfig) => { const focusPath = getFocusPath(selected, focused); const trigger = triggerIndex != null && dialogData.triggers[triggerIndex]; - let breadcrumbArray: Array = []; + const initialBreadcrumbArray: Array = []; - breadcrumbArray.push({ - key: 'dialog-' + dialogId, + initialBreadcrumbArray.push({ + key: buildKey(BreadcrumbKeyPrefix.Dialog, dialogId), label: dialogMap[dialogId]?.$designer?.name ?? dialogMap[dialogId]?.$designer?.$designer?.name, link: { projectId: projectId, @@ -98,8 +107,8 @@ const useBreadcrumbs = (projectId: string, pluginConfig?: PluginConfig) => { }); if (triggerIndex != null && trigger != null) { - breadcrumbArray.push({ - key: 'trigger-' + triggerIndex, + initialBreadcrumbArray.push({ + key: buildKey(BreadcrumbKeyPrefix.Trigger, triggerIndex), label: trigger.$designer?.name || getFriendlyName(trigger), link: { projectId: projectId, @@ -115,30 +124,36 @@ const useBreadcrumbs = (projectId: string, pluginConfig?: PluginConfig) => { if (encodedFocused) { // we've linked to an action, so put that in too - breadcrumbArray.push({ - key: 'action-' + focusPath, + initialBreadcrumbArray.push({ + key: buildKey(BreadcrumbKeyPrefix.Action, focusPath), label: getActionName(possibleAction, pluginConfig), }); } const currentDialog = (dialogs.find(({ id }) => id === dialogId) ?? dialogs[0]) as DialogInfo; - const selectedActions = useMemo(() => { - const actionSelected = Array.isArray(visualEditorSelection) && visualEditorSelection.length > 0; - if (!actionSelected) return []; - - const selectedActions = visualEditorSelection.map((id) => get(currentDialog?.content, id)); - - return selectedActions; - }, [visualEditorSelection, currentDialog?.content]); - - if (selectedActions.length === 1 && selectedActions[0] != null) { - const action = selectedActions[0] as any; - const actionName = getActionName(action, pluginConfig); - - breadcrumbArray = [...breadcrumbArray.slice(0, 2), { key: 'action-' + actionName, label: actionName }]; - } - + // get newest label for breadcrumbs + const breadcrumbArray = useMemo(() => { + if (currentDialog.content) { + initialBreadcrumbArray.map((b) => { + const { prefix, name } = parseKey(b.key); + + switch (prefix) { + case BreadcrumbKeyPrefix.Dialog: + b.label = getFriendlyName(currentDialog.content); + break; + case BreadcrumbKeyPrefix.Trigger: + b.label = getFriendlyName(get(currentDialog.content, `triggers[${name}]`)); + break; + case BreadcrumbKeyPrefix.Action: + b.label = getActionName(get(currentDialog.content, name)); + break; + } + return b; + }); + } + return initialBreadcrumbArray; + }, [currentDialog?.content, initialBreadcrumbArray]); return breadcrumbArray; }; 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 56d8cd156d..f1a5127be7 100644 --- a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectTriggers.tsx +++ b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectTriggers.tsx @@ -4,13 +4,12 @@ /** @jsx jsx */ import { jsx } from '@emotion/core'; import React, { useMemo } from 'react'; -import { DialogInfo, ITrigger, SDKKinds } from '@bfc/shared'; +import { DialogInfo, ITrigger, SDKKinds, getFriendlyName } from '@bfc/shared'; import { Selection } from 'office-ui-fabric-react/lib/DetailsList'; import { useRecoilValue } from 'recoil'; import formatMessage from 'format-message'; import { ContentProps } from '../constants'; -import { getFriendlyName } from '../../../../utils/dialogUtil'; import { isSupportedTrigger } from '../generateSkillManifest'; import { dialogsSelectorFamily, schemasState } from '../../../../recoilModel'; diff --git a/Composer/packages/client/src/utils/dialogUtil.ts b/Composer/packages/client/src/utils/dialogUtil.ts index 886c38639e..bb67bf276d 100644 --- a/Composer/packages/client/src/utils/dialogUtil.ts +++ b/Composer/packages/client/src/utils/dialogUtil.ts @@ -3,6 +3,7 @@ import { conceptLabels as conceptLabelsFn, + getFriendlyName, SDKKinds, DialogInfo, DialogFactory, @@ -190,19 +191,6 @@ function getDialogsMap(dialogs: DialogInfo[]): DialogsMap { }, {}); } -export function getFriendlyName(data): string { - const conceptLabels = conceptLabelsFn(); - if (data?.$designer?.name) { - return data?.$designer?.name; - } - - if (data?.intent) { - return `${data?.intent}`; - } - - return conceptLabels[data.$kind]?.title ?? data.$kind; -} - const getLabel = (dialog: DialogInfo, dataPath: string) => { const data = get(dialog, dataPath); if (!data) return ''; diff --git a/Composer/packages/lib/indexers/src/dialogIndexer.ts b/Composer/packages/lib/indexers/src/dialogIndexer.ts index 80a0deec54..323b29c8d3 100644 --- a/Composer/packages/lib/indexers/src/dialogIndexer.ts +++ b/Composer/packages/lib/indexers/src/dialogIndexer.ts @@ -3,6 +3,7 @@ import has from 'lodash/has'; import uniq from 'lodash/uniq'; +import get from 'lodash/get'; import { extractLgTemplateRefs, SDKKinds, @@ -221,7 +222,7 @@ function index(files: FileInfo[], botName: string): DialogInfo[] { const isRoot = file.relativePath.includes('/') === false; // root dialog should be in root path const dialog: DialogInfo = { isRoot, - displayName: isRoot ? `${botName}` : id, + displayName: get(dialogJson, '$designer.name', isRoot ? `${botName}` : id), ...parse(id, dialogJson), }; dialogs.push(dialog); diff --git a/Composer/packages/lib/shared/src/dialogFactory.ts b/Composer/packages/lib/shared/src/dialogFactory.ts index ccb45d1eee..b5b57ee6ac 100644 --- a/Composer/packages/lib/shared/src/dialogFactory.ts +++ b/Composer/packages/lib/shared/src/dialogFactory.ts @@ -10,6 +10,7 @@ import { copyAdaptiveAction } from './copyUtils'; import { deleteAdaptiveAction, deleteAdaptiveActionList } from './deleteUtils'; import { FieldProcessorAsync } from './copyUtils/ExternalApi'; import { generateDesignerId } from './generateUniqueId'; +import { conceptLabels } from './labelMap'; interface DesignerAttributes { name: string; @@ -24,6 +25,18 @@ const initialInputDialog = { defaultValueResponse: '', }; +export function getFriendlyName(data): string { + if (data?.$designer?.name) { + return data?.$designer?.name; + } + + if (data?.intent) { + return `${data?.intent}`; + } + + return conceptLabels()[data.$kind]?.title ?? data.$kind; +} + export function getNewDesigner(name: string, description: string) { return { $designer: {