Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fe4acb6
add includeTopics query when downloading bot content
a-b-r-o-w-n Apr 1, 2021
4876d8e
include topics when loading bots
a-b-r-o-w-n Apr 2, 2021
419be31
Merge branch 'main' into abrown/pva-topics
a-b-r-o-w-n Apr 2, 2021
c7a3cde
do not show topics in the main dialog navigation
a-b-r-o-w-n Apr 5, 2021
56d138e
move icons into shared
a-b-r-o-w-n Apr 5, 2021
b366b65
add more icons
a-b-r-o-w-n Apr 5, 2021
3f24d74
add isTopic to DialogInfo
a-b-r-o-w-n Apr 5, 2021
e336816
expose topics through shell api
a-b-r-o-w-n Apr 5, 2021
f8bfecc
show topics in begin dialog action
a-b-r-o-w-n Apr 5, 2021
b48b097
link to PVA topic
a-b-r-o-w-n Apr 5, 2021
5d22e83
include pva topics by default for electron task
a-b-r-o-w-n Apr 5, 2021
bf81624
update query string to be boolean value
a-b-r-o-w-n Apr 6, 2021
5f52657
do not include etag when publishing with topics
a-b-r-o-w-n Apr 6, 2021
074764b
make icons in dropdowns blue
a-b-r-o-w-n Apr 7, 2021
7bca260
Merge branch 'main' into abrown/pva-topics
a-b-r-o-w-n Apr 7, 2021
27be2e5
revert If-Match header change
a-b-r-o-w-n Apr 7, 2021
4902974
Merge branch 'main' into abrown/pva-topics
a-b-r-o-w-n Apr 7, 2021
75d9f5c
Merge branch 'main' into abrown/pva-topics
a-b-r-o-w-n Apr 7, 2021
f74375a
default to using oneauth in electron launch task
a-b-r-o-w-n Apr 7, 2021
7febc83
Merge branch 'main' into abrown/pva-topics
a-b-r-o-w-n Apr 8, 2021
cda33de
Merge branch 'main' into abrown/pva-topics
a-b-r-o-w-n Apr 8, 2021
2a42611
Merge branch 'main' into abrown/pva-topics
a-b-r-o-w-n Apr 8, 2021
ee53974
Merge branch 'main' into abrown/pva-topics
a-b-r-o-w-n Apr 13, 2021
775c22f
silence console output for tests in CI
a-b-r-o-w-n Apr 13, 2021
e47356c
Merge branch 'main' into abrown/pva-topics
a-b-r-o-w-n Apr 13, 2021
330d720
Merge branch 'main' into abrown/pva-topics
a-b-r-o-w-n Apr 14, 2021
ce9b921
Merge branch 'main' into abrown/pva-topics
a-b-r-o-w-n Apr 14, 2021
1d1fd54
fix select dialog test
a-b-r-o-w-n Apr 14, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@
"NODE_ENV": "development",
"DEBUG": "composer*",
"COMPOSER_DEV_TOOLS": "true",
"COMPOSER_ENABLE_ONEAUTH": "true"
"COMPOSER_ENABLE_ONEAUTH": "true",
"COMPOSER_PVA_TOPICS": "true"
},
"outputCapture": "std",
"preLaunchTask": "electron: build",
Expand Down
2 changes: 1 addition & 1 deletion Composer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"runtime": "cd ../runtime/dotnet/azurewebapp && dotnet build && dotnet run",
"test": "cross-env NODE_OPTIONS=--max-old-space-size=4096 yarn typecheck && jest",
"test:watch": "yarn typecheck && jest --watch",
"test:coverage": "yarn test --coverage --no-cache --forceExit --reporters=default",
"test:coverage": "yarn test --coverage --no-cache --forceExit --reporters=default --silent",
"test:integration": "cypress run --browser edge",
"test:integration:start-server": "node scripts/e2e.js",
"test:integration:open": "cypress open",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ const VisualDesigner: React.FC<VisualDesignerProps> = ({ onFocus, onBlur, schema
hosted,
schemas,
flowZoomRate,
topics,
dialogs,
} = shellData;

const { updateFlowZoomRate } = shellApi;
Expand Down Expand Up @@ -104,6 +106,8 @@ const VisualDesigner: React.FC<VisualDesignerProps> = ({ onFocus, onBlur, schema
clipboardActions: clipboardActions || [],
dialogFactory: new DialogFactory(schema),
customSchemas: customActionSchema ? [customActionSchema] : [],
topics,
dialogs,
};

const customFlowSchema: FlowUISchema = nodeContext.customSchemas.reduce((result, s) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.

import React from 'react';
import { DialogFactory, JSONSchema7 } from '@bfc/shared';
import { DialogFactory, JSONSchema7, DialogInfo } from '@bfc/shared';

export interface NodeRendererContextValue {
focusedId?: string;
Expand All @@ -11,6 +11,8 @@ export interface NodeRendererContextValue {
clipboardActions: any[];
dialogFactory: DialogFactory;
customSchemas: JSONSchema7[];
dialogs: DialogInfo[];
topics: DialogInfo[];
}

export const defaultRendererContextValue = {
Expand All @@ -20,5 +22,7 @@ export const defaultRendererContextValue = {
clipboardActions: [],
dialogFactory: new DialogFactory({}),
customSchemas: [],
dialogs: [],
topics: [],
};
export const NodeRendererContext = React.createContext<NodeRendererContextValue>(defaultRendererContextValue);
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,61 @@

/** @jsx jsx */
import { jsx } from '@emotion/core';
import React from 'react';
import get from 'lodash/get';
import { LinkBtn, FixedInfo } from '@bfc/ui-shared';
import { useContext } from 'react';
import { useContext, useMemo } from 'react';
import formatMessage from 'format-message';
import { WidgetContainerProps, WidgetComponent } from '@bfc/extension-client';
import { Icon } from 'office-ui-fabric-react/lib/Icon';

import { NodeEventTypes } from '../constants/NodeEventTypes';
import { RendererContext } from '../contexts/RendererContext';
import { ElementWrapperTag } from '../types/PluggableComponents.types';
import { NodeRendererContext } from '../../adaptive-flow-editor/contexts/NodeRendererContext';

export interface DialogRefCardProps extends WidgetContainerProps {
dialog: string | object;
}

export const DialogRef: WidgetComponent<DialogRefCardProps> = ({ id, onEvent, dialog }) => {
export const DialogRef: WidgetComponent<DialogRefCardProps> = ({ id, onEvent, dialog, data }) => {
const { ElementWrapper } = useContext(RendererContext);
const { dialogs, topics } = useContext(NodeRendererContext);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yeze322 Can you take a look at this and let me know your thoughts?

I wanted to do 2 things:

  1. Use the dialog's display name instead of its id
  2. Differentiate from dialogs and topics

This is your domain so I want to make sure that this approach is fine, or work with you to do something better.

const calleeDialog = typeof dialog === 'object' ? get(dialog, '$ref') : dialog;

const isTopic = useMemo(() => {
return topics.some((t) => t.content?.id === calleeDialog);
}, [dialog]);

const linkContent = useMemo(() => {
if (isTopic) {
const topic = topics.find((t) => t.content?.id === calleeDialog);
return (
<React.Fragment>
{get(topic?.content, '$designer.name') || calleeDialog}
&nbsp;
<Icon iconName="NavigateExternalInline" />
</React.Fragment>
);
}

const dialogData = dialogs.find((d) => d.id === calleeDialog);

if (dialogData) {
return get(dialogData.content, '$designer.name') || calleeDialog;
}

return calleeDialog;
}, [dialog]);

const fixedInfoContent = useMemo(() => {
if (isTopic) {
return formatMessage('(Topic)');
}

return formatMessage('(Dialog)');
}, [dialog]);

const dialogRef = calleeDialog ? (
<ElementWrapper nodeId={id} tagId={ElementWrapperTag.Link}>
<LinkBtn
Expand All @@ -28,15 +66,15 @@ export const DialogRef: WidgetComponent<DialogRefCardProps> = ({ id, onEvent, di
onEvent(NodeEventTypes.OpenDialog, { caller: id, callee: calleeDialog });
}}
>
{calleeDialog}
{linkContent}
</LinkBtn>
</ElementWrapper>
) : (
'?'
);
return (
<div>
{dialogRef} <FixedInfo>{formatMessage('(Dialog)')}</FixedInfo>
{dialogRef} <FixedInfo>{fixedInfoContent}</FixedInfo>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { css, jsx } from '@emotion/core';
import { FontIcon } from 'office-ui-fabric-react/lib/Icon';
import React from 'react';
import formatMessage from 'format-message';
import { SharedColors } from '@uifabric/fluent-theme';

const styles = {
fieldTypeText: css`
Expand All @@ -29,6 +30,9 @@ const styles = {
`,
icon: css`
margin-right: 5px;
// center icon with text
margin-top: 2px;
color: ${SharedColors.cyanBlue10};
`,
};

Expand All @@ -43,7 +47,7 @@ export const ExpressionSwitchWindow = (props: ExpressionSwitchWindowProps) => {

<div css={styles.switchToExpressionText} onClick={onSwitchToExpression}>
<FontIcon css={styles.icon} iconName={'CalculatorEqualTo'} />
{formatMessage(`Write an expression`)}
<span>{formatMessage(`Write an expression`)}</span>
</div>
</React.Fragment>
);
Expand Down
28 changes: 14 additions & 14 deletions Composer/packages/client/src/components/ProjectTree/treeItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { NeutralColors, SharedColors } from '@uifabric/fluent-theme';
import { IButtonStyles } from 'office-ui-fabric-react/lib/Button';
import { IContextualMenuStyles } from 'office-ui-fabric-react/lib/ContextualMenu';
import { ICalloutContentStyles, Callout } from 'office-ui-fabric-react/lib/Callout';
import { DiagnosticSeverity, Diagnostic } from '@bfc/shared';
import { DiagnosticSeverity, Diagnostic, Icons } from '@bfc/shared';
import isEmpty from 'lodash/isEmpty';
import uniqueId from 'lodash/uniqueId';

Expand Down Expand Up @@ -237,17 +237,17 @@ type TreeObject =
| 'lu' // used on other pages
| 'external skill'; // used with multi-bot authoring

const icons: { [key in TreeObject]: string | null } = {
bot: 'CubeShape',
dialog: 'Org',
trigger: 'LightningBolt',
const TreeIcons: { [key in TreeObject]: string | null } = {
bot: Icons.BOT,
dialog: Icons.DIALOG,
trigger: Icons.TRIGGER,
'trigger group': null,
'form dialog': 'Table',
'form field': 'Variable2', // x in parentheses
'form trigger': 'TriggerAuto', // lightning bolt with gear
lg: 'Robot',
lu: 'People',
'external skill': 'Globe',
'form dialog': Icons.FORM_DIALOG,
'form field': Icons.FORM_FIELD, // x in parentheses
'form trigger': Icons.FORM_TRIGGER, // lightning bolt with gear
lg: Icons.LG,
lu: Icons.LU,
'external skill': Icons.EXTERNAL_SKILL,
};

const objectNames: { [key in TreeObject]: () => string } = {
Expand Down Expand Up @@ -469,9 +469,9 @@ export const TreeItem: React.FC<ITreeItemProps> = ({
onFocus={item.onFocus}
>
<div css={projectTreeItem} role="presentation" tabIndex={-1}>
{item.itemType != null && icons[item.itemType] != null && (
{item.itemType != null && TreeIcons[item.itemType] != null && (
<Icon
iconName={icons[item.itemType]}
iconName={TreeIcons[item.itemType]}
styles={{
root: {
width: '12px',
Expand Down Expand Up @@ -585,7 +585,7 @@ export const TreeItem: React.FC<ITreeItemProps> = ({
items={[
{
key: linkString,
icon: isBroken ? 'RemoveLink' : icons[itemType],
icon: isBroken ? 'RemoveLink' : TreeIcons[itemType],
itemType,
...link,
},
Expand Down
1 change: 1 addition & 0 deletions Composer/packages/client/src/recoilModel/atoms/botState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const emptyDialog: DialogInfo = {
intentTriggers: [],
skills: [],
isFormDialog: false,
isTopic: false,
};

const emptyLg: LgFile = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const dialogsDispatcher = () => {
const { set, snapshot } = callbackHelpers;
const fixedContent = JSON.parse(autofixReferInDialog(id, JSON.stringify(content)));
const schemas = await snapshot.getPromise(schemasState(projectId));
const dialog = { isRoot: false, ...dialogIndexer.parse(id, fixedContent) };
const dialog = { isRoot: false, isTopic: false, ...dialogIndexer.parse(id, fixedContent) };

if (typeof dialog.content === 'object') {
dialog.content.id = id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import isEqual from 'lodash/isEqual';

import { currentProjectIdState } from '../atoms';
import { encodeArrayPathToDesignerPath } from '../../utils/convertUtils/designerPathEncoder';
import { dialogsSelectorFamily, rootBotProjectIdSelector } from '../selectors';
import { dialogsSelectorFamily, rootBotProjectIdSelector, topicsSelectorFamily } from '../selectors';
import { DesignPageLocation } from '../types';

import { getSelected } from './../../utils/dialogUtil';
Expand Down Expand Up @@ -52,9 +52,21 @@ export const navigationDispatcher = () => {
if (rootBotProjectId == null) return;

const projectId = skillId ?? rootBotProjectId;
const topics = await snapshot.getPromise(topicsSelectorFamily(projectId));

await setCurrentProjectId(callbackInterface, projectId);

// check to see if navigating to PVA topic
const topic = topics.find((t) => t.content?.id === dialogId);
if (topic) {
if (topic?.content?.$designer?.link) {
// eslint-disable-next-line security/detect-non-literal-fs-filename
window.open(topic.content.$designer.link as string, '_blank');
}
// no-op even if topic has no link
return;
}

const currentUri =
trigger == null
? convertPathToUrl(rootBotProjectId, skillId, dialogId)
Expand Down
21 changes: 18 additions & 3 deletions Composer/packages/client/src/recoilModel/selectors/dialogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ export const dialogsSelectorFamily = selectorFamily<DialogInfo[], string>({
get: (projectId: string) => ({ get }) => {
const dialogIds = get(dialogIdsState(projectId));

return dialogIds.map((dialogId) => {
return get(dialogState({ projectId, dialogId }));
});
return dialogIds
.map((dialogId) => {
return get(dialogState({ projectId, dialogId }));
})
.filter((d) => !d.isTopic);
},
set: (projectId: string) => ({ set }, newDialogs) => {
const newDialogArray = newDialogs as DialogInfo[];
Expand All @@ -26,6 +28,19 @@ export const dialogsSelectorFamily = selectorFamily<DialogInfo[], string>({
},
});

export const topicsSelectorFamily = selectorFamily<DialogInfo[], string>({
key: 'topics',
get: (projectId: string) => ({ get }) => {
const dialogIds = get(dialogIdsState(projectId));

return dialogIds
.map((dialogId) => {
return get(dialogState({ projectId, dialogId }));
})
.filter((d) => d.isTopic);
},
});

export const currentDialogState = selectorFamily<DialogInfo | undefined, { projectId: string; dialogId?: string }>({
key: 'currentDialog',
get: ({ projectId, dialogId }) => ({ get }) => {
Expand Down
3 changes: 3 additions & 0 deletions Composer/packages/client/src/recoilModel/selectors/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
} from '../selectors';

import { lgFilesSelectorFamily } from './lg';
import { topicsSelectorFamily } from './dialogs';
// Selector return types
export type TreeDataPerProject = {
isRemote: boolean;
Expand Down Expand Up @@ -293,6 +294,7 @@ export const projectTreeSelectorFamily = selector<TreeDataPerProject[]>({
return projectIds.map((projectId: string) => {
const { isRemote, isRootBot } = get(projectMetaDataState(projectId));
const dialogs = get(dialogsSelectorFamily(projectId));
const topics = get(topicsSelectorFamily(projectId));
const sortedDialogs = [...dialogs].sort((x, y) => {
if (x.isRoot) {
return -1;
Expand Down Expand Up @@ -340,6 +342,7 @@ export const projectTreeSelectorFamily = selector<TreeDataPerProject[]>({
isRemote,
isRootBot,
sortedDialogs,
topics,
luImports,
lgImports,
lgImportsList,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ export const mockDialog: (id: string) => DialogInfo = (id: string) => ({
triggers: [],
intentTriggers: [],
skills: [],
isTopic: false,
});
9 changes: 8 additions & 1 deletion Composer/packages/client/src/shell/useShell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ import {
featureFlagsState,
} from '../recoilModel';
import { undoFunctionState } from '../recoilModel/undo/history';
import { dialogsWithLuProviderSelectorFamily, skillsStateSelector } from '../recoilModel/selectors';
import {
dialogsWithLuProviderSelectorFamily,
skillsStateSelector,
topicsSelectorFamily,
} from '../recoilModel/selectors';
import { navigateTo } from '../utils/navigation';
import TelemetryClient from '../telemetry/TelemetryClient';
import { lgFilesSelectorFamily } from '../recoilModel/selectors/lg';
Expand Down Expand Up @@ -73,13 +77,15 @@ const stubDialog = (): DialogInfo => ({
intentTriggers: [],
skills: [],
isFormDialog: false,
isTopic: false,
});

export function useShell(source: EventSource, projectId: string): Shell {
const dialogMapRef = useRef({});

const schemas = useRecoilValue(schemasState(projectId));
const dialogs = useRecoilValue(dialogsWithLuProviderSelectorFamily(projectId));
const topics = useRecoilValue(topicsSelectorFamily(projectId));
const focusPath = useRecoilValue(focusPathState(projectId));
const skills = useRecoilValue(skillsStateSelector);
const locale = useRecoilValue(localeState(projectId));
Expand Down Expand Up @@ -296,6 +302,7 @@ export function useShell(source: EventSource, projectId: string): Shell {
projectId,
projectCollection,
dialogs,
topics,
dialogSchemas,
dialogId,
focusPath,
Expand Down
1 change: 1 addition & 0 deletions Composer/packages/lib/indexers/src/dialogIndexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,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,
isTopic: file.relativePath.startsWith('topics/'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the answer is yes, but just confirming: We are fine with not indexing the topic if the user decides to muck around with the folder structure right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should engage the PVA team on that. My assumption is that the folder structure is necessary when publishing back to PVA, but I don't know for sure.

...parse(id, dialogJson, botName),
};
dialogs.push(dialog);
Expand Down
1 change: 1 addition & 0 deletions Composer/packages/lib/indexers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class Indexer {
const result = this.classifyFile(files);
const { dialogs, recognizers } = this.separateDialogsAndRecognizers(result[FileExtensions.Dialog]);
const { skillManifestFiles, crossTrainConfigs } = this.separateConfigAndManifests(result[FileExtensions.Manifest]);

const assets = {
dialogs: dialogIndexer.index(dialogs, botName),
dialogSchemas: dialogSchemaIndexer.index(result[FileExtensions.DialogSchema]),
Expand Down
Loading