diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/__tests__/generateSkillManifest.test.ts b/Composer/packages/client/src/pages/design/exportSkillModal/__tests__/generateSkillManifest.test.ts index 845ee32279..ce0aa923dc 100644 --- a/Composer/packages/client/src/pages/design/exportSkillModal/__tests__/generateSkillManifest.test.ts +++ b/Composer/packages/client/src/pages/design/exportSkillModal/__tests__/generateSkillManifest.test.ts @@ -79,7 +79,7 @@ describe('generateOtherActivities', () => { describe('generateActivity', () => { it('should return simple activity when there is no dialog schema', () => { const dialogSchemas = []; - const dialog: any = { id: 'test' }; + const dialog: any = { id: 'test', content: { $designer: {} } }; const result = generateActivity(dialogSchemas, dialog); expect(result).toEqual( expect.objectContaining({ @@ -93,13 +93,14 @@ describe('generateActivity', () => { it('should return activity that includes value and result when dialog schema is available', () => { const dialogSchemas = [dialogSchema]; - const dialog: any = { id: 'test' }; + const dialog: any = { id: 'test', content: { $designer: { description: 'Test Dialog' } } }; const result = generateActivity(dialogSchemas, dialog); expect(result).toEqual( expect.objectContaining({ test: { type: 'event', name: 'test', + description: 'Test Dialog', value: { type: 'object', properties: { @@ -134,7 +135,7 @@ describe('generateActivities', () => { it('should return an object containing activities', () => { const dialogSchemas = [dialogSchema]; const triggers: any = [{ $kind: SDKKinds.OnTypingActivity }, { $kind: SDKKinds.OnHandoffActivity }]; - const dialogs: any = [{ id: 'test' }]; + const dialogs: any = [{ id: 'test', content: { $designer: {} } }]; const result = generateActivities(dialogSchemas, triggers, dialogs); expect(result).toEqual( expect.objectContaining({ diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/constants.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/constants.tsx index 784e95bfab..9e94baa24a 100644 --- a/Composer/packages/client/src/pages/design/exportSkillModal/constants.tsx +++ b/Composer/packages/client/src/pages/design/exportSkillModal/constants.tsx @@ -78,7 +78,9 @@ export interface Language { } export interface DispatchModels { - languages?: Language[]; + languages?: { + [local: string]: Language[]; + }; intents?: string[]; } diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/content/Endpoints.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/content/Endpoints.tsx index 3cf8d37884..6e4868d145 100644 --- a/Composer/packages/client/src/pages/design/exportSkillModal/content/Endpoints.tsx +++ b/Composer/packages/client/src/pages/design/exportSkillModal/content/Endpoints.tsx @@ -24,7 +24,8 @@ export const Endpoints: React.FC = ({ errors, value, schema, onCha label: false, properties: { endpoints: { - order: [['name', 'endpointUrl'], ['description', 'protocol'], '*'], + hidden: ['protocol'], + order: [['name', 'endpointUrl'], ['description', 'msAppId'], '*'], }, }, }; diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectDialogs.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectDialogs.tsx index db720acfef..c7d13500d0 100644 --- a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectDialogs.tsx +++ b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectDialogs.tsx @@ -1,61 +1,134 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; -import React, { useMemo } from 'react'; -import { - CheckboxVisibility, - DetailsList, - DetailsListLayoutMode, - IDetailsRowProps, - SelectionMode, -} from 'office-ui-fabric-react/lib/DetailsList'; +import React, { useMemo, useState, useRef, useEffect } from 'react'; import { DialogInfo } from '@bfc/shared'; -import { IRenderFunction } from 'office-ui-fabric-react/lib/Utilities'; +import { NeutralColors } from '@uifabric/fluent-theme/lib/fluent/FluentColors'; import { Selection } from 'office-ui-fabric-react/lib/DetailsList'; -import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'; -import { ScrollablePane, ScrollbarVisibility } from 'office-ui-fabric-react/lib/ScrollablePane'; -import { TooltipHost } from 'office-ui-fabric-react/lib/Tooltip'; +import { TextField } from 'office-ui-fabric-react/lib/TextField'; import { useRecoilValue } from 'recoil'; +import debounce from 'lodash/debounce'; import formatMessage from 'format-message'; import { ContentProps } from '../constants'; -import { dialogsState } from '../../../../recoilModel'; +import { dispatcherState } from '../../../../recoilModel'; +import { validatedDialogsSelector } from '../../../../recoilModel/selectors/validatedDialogs'; -const styles = { - detailListContainer: css` - flex-grow: 1; - height: 350px; - position: relative; - padding-top: 10px; - overflow: hidden; - `, +import { SelectItems } from './SelectItems'; + +const textFieldStyles = (focused: boolean) => ({ + fieldGroup: { + margin: '-5px 0', + transition: 'border-color 0.1s linear', + borderColor: 'transparent', + backgroundColor: focused ? undefined : 'transparent', + selectors: { + ':hover': { + borderColor: NeutralColors.gray30, + }, + }, + }, +}); + +const DescriptionColumn: React.FC = ({ id, displayName }: DialogInfo) => { + const items = useRecoilValue(validatedDialogsSelector); + const { content } = items.find(({ id: dialogId }) => dialogId === id) || {}; + + const [value, setValue] = useState(content?.$designer?.description); + const [focused, setFocused] = useState(false); + const { updateDialog } = useRecoilValue(dispatcherState); + + const sync = useRef( + debounce((updateDialog: any, description: string, content: any) => { + updateDialog({ + id, + content: { + ...content, + $designer: { + ...content.$designer, + description, + }, + }, + }); + }, 400) + ).current; + + useEffect(() => { + if (value !== content.$designer?.description) { + sync(updateDialog, value, content); + } + }, [value, updateDialog]); + + const handleChange = (_, newValue?: string) => { + if (typeof newValue === 'string') { + setValue(newValue); + } + }; + + return ( +
+ { + setFocused(false); + }} + onChange={handleChange} + onClick={(e) => { + e.stopPropagation(); + e.preventDefault(); + }} + onFocus={() => { + setFocused(true); + }} + /> +
+ ); }; -export const SelectDialogs: React.FC = ({ editJson, schema, setSelectedDialogs }) => { - const items = useRecoilValue(dialogsState); +export const SelectDialogs: React.FC = ({ setSelectedDialogs }) => { + const dialogs = useRecoilValue(validatedDialogsSelector); + const items = useMemo(() => dialogs.map(({ id, content, displayName }) => ({ id, content, displayName })), []); // for detail file list in open panel - const tableColumns = [ - { - key: 'column1', - name: formatMessage('Name'), - fieldName: 'id', - minWidth: 300, - maxWidth: 350, - isRowHeader: true, - isResizable: true, - isSortedDescending: false, - sortAscendingAriaLabel: formatMessage('Sorted A to Z'), - sortDescendingAriaLabel: formatMessage('Sorted Z to A'), - data: 'string', - onRender: (item: DialogInfo) => { - return {item.displayName}; + const tableColumns = useMemo( + () => [ + { + key: 'column1', + name: formatMessage('Name'), + fieldName: 'id', + minWidth: 300, + maxWidth: 350, + isRowHeader: true, + isResizable: true, + isSortedDescending: false, + sortAscendingAriaLabel: formatMessage('Sorted A to Z'), + sortDescendingAriaLabel: formatMessage('Sorted Z to A'), + data: 'string', + onRender: (item: DialogInfo) => { + return {item.displayName}; + }, + isPadded: true, }, - isPadded: true, - }, - ]; + { + key: 'column2', + name: formatMessage('Description'), + fieldName: 'description', + minWidth: 300, + maxWidth: 350, + isRowHeader: true, + isResizable: true, + isSortedDescending: false, + data: 'string', + onRender: DescriptionColumn, + isPadded: true, + }, + ], + [] + ); const selection = useMemo( () => @@ -68,38 +141,5 @@ export const SelectDialogs: React.FC = ({ editJson, schema, setSel [] ); - function onRenderDetailsHeader(props, defaultRender) { - return ( - - {defaultRender({ - ...props, - onRenderColumnHeaderTooltip: (tooltipHostProps) => , - })} - - ); - } - - const onRenderRow = (props?: IDetailsRowProps, defaultRender?: IRenderFunction): JSX.Element => { - return
{defaultRender && defaultRender(props)}
; - }; - - return ( -
- - item.id} - items={items} - layoutMode={DetailsListLayoutMode.justified} - selection={selection} - selectionMode={SelectionMode.multiple} - onRenderDetailsHeader={onRenderDetailsHeader} - onRenderRow={onRenderRow} - /> - -
- ); + return ; }; diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectItems.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectItems.tsx new file mode 100644 index 0000000000..9ddc574a3e --- /dev/null +++ b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectItems.tsx @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** @jsx jsx */ +import { css, jsx } from '@emotion/core'; +import React from 'react'; +import { + CheckboxVisibility, + DetailsList, + DetailsListLayoutMode, + IDetailsRowProps, + SelectionMode, + IColumn, + SelectAllVisibility, +} from 'office-ui-fabric-react/lib/DetailsList'; +import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox'; +import { IRenderFunction, ISelection, IObjectWithKey } from 'office-ui-fabric-react/lib/Utilities'; +import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'; +import { ScrollablePane, ScrollbarVisibility } from 'office-ui-fabric-react/lib/ScrollablePane'; +import { TooltipHost } from 'office-ui-fabric-react/lib/Tooltip'; +import formatMessage from 'format-message'; + +const styles = { + detailListContainer: css` + flex-grow: 1; + height: 350px; + position: relative; + overflow: hidden; + `, +}; + +interface SelectItemsProps { + items: any[]; + selection: ISelection; + tableColumns: IColumn[]; +} + +export const SelectItems: React.FC = ({ items, selection, tableColumns }) => { + const onRenderDetailsHeader = (props, defaultRender) => { + return ( + + {defaultRender({ + ...props, + selectAllVisibility: SelectAllVisibility.hidden, + onRenderColumnHeaderTooltip: (tooltipHostProps) => , + })} + + ); + }; + + const onRenderRow = (props?: IDetailsRowProps, defaultRender?: IRenderFunction): JSX.Element => { + return
{defaultRender && defaultRender(props)}
; + }; + + const handleToggleSelectAll = () => { + selection.setAllSelected(!selection.isAllSelected()); + }; + + return ( + +
+ + item.id} + items={items} + layoutMode={DetailsListLayoutMode.justified} + selection={selection} + selectionMode={SelectionMode.multiple} + onRenderDetailsHeader={onRenderDetailsHeader} + onRenderRow={onRenderRow} + /> + +
+ +
+ ); +}; 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 9c6e163a09..e86db6fe2e 100644 --- a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectTriggers.tsx +++ b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectTriggers.tsx @@ -2,21 +2,10 @@ // Licensed under the MIT License. /** @jsx jsx */ -import { css, jsx } from '@emotion/core'; +import { jsx } from '@emotion/core'; import React, { useMemo } from 'react'; -import { - CheckboxVisibility, - DetailsList, - DetailsListLayoutMode, - IDetailsRowProps, - SelectionMode, -} from 'office-ui-fabric-react/lib/DetailsList'; import { DialogInfo, ITrigger, SDKKinds } from '@bfc/shared'; -import { IRenderFunction } from 'office-ui-fabric-react/lib/Utilities'; import { Selection } from 'office-ui-fabric-react/lib/DetailsList'; -import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'; -import { ScrollablePane, ScrollbarVisibility } from 'office-ui-fabric-react/lib/ScrollablePane'; -import { TooltipHost } from 'office-ui-fabric-react/lib/Tooltip'; import { useRecoilValue } from 'recoil'; import formatMessage from 'format-message'; @@ -25,15 +14,7 @@ import { dialogsState, schemasState } from '../../../../recoilModel'; import { getFriendlyName } from '../../../../utils/dialogUtil'; import { isSupportedTrigger } from '../generateSkillManifest'; -const styles = { - detailListContainer: css` - flex-grow: 1; - height: 350px; - position: relative; - padding-top: 10px; - overflow: hidden; - `, -}; +import { SelectItems } from './SelectItems'; const getLabel = (kind: SDKKinds, uiSchema) => { const { label } = uiSchema?.[kind]?.form || {}; @@ -111,38 +92,5 @@ export const SelectTriggers: React.FC = ({ setSelectedTriggers }) [] ); - function onRenderDetailsHeader(props, defaultRender) { - return ( - - {defaultRender({ - ...props, - onRenderColumnHeaderTooltip: (tooltipHostProps) => , - })} - - ); - } - - const onRenderRow = (props?: IDetailsRowProps, defaultRender?: IRenderFunction): JSX.Element => { - return
{defaultRender && defaultRender(props)}
; - }; - - return ( -
- - item.id} - items={items} - layoutMode={DetailsListLayoutMode.justified} - selection={selection} - selectionMode={SelectionMode.multiple} - onRenderDetailsHeader={onRenderDetailsHeader} - onRenderRow={onRenderRow} - /> - -
- ); + return ; }; diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/generateSkillManifest.ts b/Composer/packages/client/src/pages/design/exportSkillModal/generateSkillManifest.ts index 0c793e5ba7..adb09b34e5 100644 --- a/Composer/packages/client/src/pages/design/exportSkillModal/generateSkillManifest.ts +++ b/Composer/packages/client/src/pages/design/exportSkillModal/generateSkillManifest.ts @@ -18,7 +18,7 @@ export const generateSkillManifest = ( dialogSchemas: DialogSchemaFile[], luFiles: LuFile[], selectedTriggers: ITrigger[], - selectedDialogs: DialogInfo[] + selectedDialogs: Partial[] ) => { const { activities: previousActivities, @@ -32,15 +32,17 @@ export const generateSkillManifest = ( return skillManifest; } + const resolvedDialogs = dialogs.filter(({ id }) => selectedDialogs.find(({ id: dialogId }) => id === dialogId)); + const { content } = rootDialog; const triggers = selectedTriggers.reduce((acc: ITrigger[], { id: path }) => { const trigger = get(content, path); return trigger ? [...acc, trigger] : acc; }, []); - const activities = generateActivities(dialogSchemas, triggers, selectedDialogs); + const activities = generateActivities(dialogSchemas, triggers, resolvedDialogs); const dispatchModels = generateDispatchModels(schema, dialogs, triggers, luFiles); - const definitions = getDefinitions(dialogSchemas, selectedDialogs); + const definitions = getDefinitions(dialogSchemas, resolvedDialogs); return { ...skillManifest, @@ -73,15 +75,20 @@ export const generateActivities = ( }; export const generateActivity = (dialogSchemas: DialogSchemaFile[], dialog: DialogInfo): Activities => { - const { id: name } = dialog; + const { + id: name, + content: { $designer }, + } = dialog; const { content = {} } = dialogSchemas.find(({ id }) => id === name) || {}; const { properties, $result: resultValue, type } = content; + const { description } = $designer || {}; return { [name]: { type: ActivityTypes.Event, name, + ...(description ? { description } : {}), ...(Object.keys(properties || {}).length ? { value: { properties, type } } : {}), ...(Object.keys(resultValue?.properties || {}).length ? { resultValue } : {}), }, @@ -132,11 +139,11 @@ export const generateDispatchModels = ( }, ], }; - }, {} as any); + }, {}); return { dispatchModels: { - ...(Object.keys(languages || {}).length ? { languages } : {}), + ...(Object.keys(languages).length ? { languages } : {}), ...(intents.length ? { intents } : {}), }, }; diff --git a/Composer/packages/extensions/adaptive-form/src/components/FormRow.tsx b/Composer/packages/extensions/adaptive-form/src/components/FormRow.tsx index 3feede14f2..864639f723 100644 --- a/Composer/packages/extensions/adaptive-form/src/components/FormRow.tsx +++ b/Composer/packages/extensions/adaptive-form/src/components/FormRow.tsx @@ -6,7 +6,7 @@ import { FieldProps, UIOptions } from '@bfc/extension'; import { css, jsx } from '@emotion/core'; import React from 'react'; -import { resolvePropSchema } from '../utils'; +import { isPropertyHidden, resolvePropSchema } from '../utils'; import { SchemaField } from './SchemaField'; @@ -46,6 +46,7 @@ export const getRowProps = (rowProps: FormRowProps, field: string) => { return { id: `${id}.${field}`, schema: fieldSchema ?? {}, + hidden: isPropertyHidden(uiOptions, value, field), label: (label === false ? false : undefined) as false | undefined, name: field, rawErrors: rawErrors?.[field], diff --git a/Composer/packages/extensions/adaptive-form/src/components/SchemaField.tsx b/Composer/packages/extensions/adaptive-form/src/components/SchemaField.tsx index 3a728236de..48274ad898 100644 --- a/Composer/packages/extensions/adaptive-form/src/components/SchemaField.tsx +++ b/Composer/packages/extensions/adaptive-form/src/components/SchemaField.tsx @@ -29,6 +29,7 @@ const SchemaField: React.FC = (props) => { value, rawErrors, hideError, + hidden, onChange, ...rest } = props; @@ -40,17 +41,24 @@ const SchemaField: React.FC = (props) => { ...baseUIOptions, }; + const handleChange = (newValue: any) => { + const serializedValue = + typeof uiOptions?.serializer?.set === 'function' ? uiOptions.serializer.set(newValue) : newValue; + + onChange(serializedValue); + }; + useEffect(() => { if (typeof value === 'undefined') { - if (schema?.const) { - onChange(schema.const); - } else if (schema?.default) { - onChange(schema.default); + if (schema.const) { + handleChange(schema.const); + } else if (schema.default) { + handleChange(schema.default); } } }, []); - if (name.startsWith('$')) { + if (name.startsWith('$') || hidden) { return null; } @@ -58,13 +66,6 @@ const SchemaField: React.FC = (props) => { ); - const handleChange = (newValue: any) => { - const serializedValue = - typeof uiOptions?.serializer?.set === 'function' ? uiOptions.serializer.set(newValue) : newValue; - - onChange(serializedValue); - }; - const deserializedValue = typeof uiOptions?.serializer?.get === 'function' ? uiOptions.serializer.get(value) : value; const FieldWidget = resolveFieldWidget(schema, uiOptions, formUIOptions); diff --git a/Composer/packages/extensions/adaptive-form/src/components/fields/ObjectArrayField.tsx b/Composer/packages/extensions/adaptive-form/src/components/fields/ObjectArrayField.tsx index 7de2b0c7a6..63b6f37a4a 100644 --- a/Composer/packages/extensions/adaptive-form/src/components/fields/ObjectArrayField.tsx +++ b/Composer/packages/extensions/adaptive-form/src/components/fields/ObjectArrayField.tsx @@ -14,7 +14,7 @@ import { FontSizes, NeutralColors, SharedColors } from '@uifabric/fluent-theme'; import formatMessage from 'format-message'; import map from 'lodash/map'; -import { getArrayItemProps, getOrderedProperties, useArrayItems, resolveRef } from '../../utils'; +import { getArrayItemProps, getOrderedProperties, useArrayItems, resolveRef, isPropertyHidden } from '../../utils'; import { FieldLabel } from '../FieldLabel'; import { objectArrayField } from './styles'; @@ -80,21 +80,16 @@ const ObjectArrayField: React.FC> = (props) => { itemSchema && typeof itemSchema !== 'boolean' ? itemSchema : {}, uiOptions, value - ); + ).filter((property) => Array.isArray(property) || !isPropertyHidden(uiOptions, value, property)); const stackArrayItems = useMemo(() => { - const allOrderProps = orderedProperties.reduce((all, prop) => { - if (Array.isArray(prop)) { - all.push(...prop); - } else { - all.push(prop); - } - - return all; - }, [] as string[]); + const allOrderProps = orderedProperties.reduce((all: string[], prop: string | string[]) => { + return [...all, ...(Array.isArray(prop) ? prop : [prop])]; + }, []); return ( allOrderProps.length > 2 || + orderedProperties.some((property) => Array.isArray(property)) || Object.entries(properties).some(([key, propSchema]) => { const resolved = resolveRef(propSchema as JSONSchema7, props.definitions); return allOrderProps.includes(key) && resolved.$role === 'expression'; diff --git a/Composer/packages/extensions/adaptive-form/src/utils/__tests__/getOrderedProperties.test.ts b/Composer/packages/extensions/adaptive-form/src/utils/__tests__/getOrderedProperties.test.ts index fec5ba7be4..8c4d4d950f 100644 --- a/Composer/packages/extensions/adaptive-form/src/utils/__tests__/getOrderedProperties.test.ts +++ b/Composer/packages/extensions/adaptive-form/src/utils/__tests__/getOrderedProperties.test.ts @@ -20,9 +20,26 @@ const schema = { const data = 'form data'; describe('getOrderedProperties', () => { - it('excludes fields according to hidden option', () => { - expect(getOrderedProperties(schema, { hidden: ['two'] }, data)).not.toContain('two'); - expect(getOrderedProperties(schema, { hidden: () => ['two'] }, data)).not.toContain('two'); + it('includes hidden fields', () => { + expect(getOrderedProperties(schema, { hidden: ['two'] }, data)).toContain('two'); + expect(getOrderedProperties(schema, { hidden: () => ['two'] }, data)).toContain('two'); + }); + + it('includes hidden fields when all non-hidden fields are in the order', () => { + const order = ['one', 'three', 'four', 'five', 'six', 'seven']; + + expect(getOrderedProperties(schema, { order, hidden: ['two'] }, data)).toContain('two'); + expect(getOrderedProperties(schema, { order, hidden: () => ['two'] }, data)).toContain('two'); + }); + + it('includes hidden fields when all non-hidden fields are in the order', () => { + const order = ['one', 'three', ['four', 'five'], 'six', 'seven']; + const expectedResult = ['one', 'three', ['four', 'five'], 'six', 'seven', 'two']; + + // @ts-expect-error + expect(getOrderedProperties(schema, { order, hidden: ['two'] }, data)).toEqual(expectedResult); + // @ts-expect-error + expect(getOrderedProperties(schema, { order, hidden: () => ['two'] }, data)).toEqual(expectedResult); }); it('sorts according to order option', () => { diff --git a/Composer/packages/extensions/adaptive-form/src/utils/getHiddenProperties.ts b/Composer/packages/extensions/adaptive-form/src/utils/getHiddenProperties.ts new file mode 100644 index 0000000000..8136942037 --- /dev/null +++ b/Composer/packages/extensions/adaptive-form/src/utils/getHiddenProperties.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { UIOptions } from '@bfc/extension'; + +export const globalHiddenProperties = ['$kind', '$id', '$copy', '$designer', 'id', 'disabled']; + +export const getHiddenProperties = (uiOptions: UIOptions, value: any) => { + const hiddenProperties = typeof uiOptions.hidden === 'function' ? uiOptions.hidden(value) : uiOptions.hidden || []; + return new Set([...globalHiddenProperties, ...hiddenProperties]); +}; + +export const isPropertyHidden = (uiOptions: UIOptions, value: any, property: string) => + getHiddenProperties(uiOptions, value).has(property); diff --git a/Composer/packages/extensions/adaptive-form/src/utils/getOrderedProperties.ts b/Composer/packages/extensions/adaptive-form/src/utils/getOrderedProperties.ts index ac127b6f62..c4e52de4c5 100644 --- a/Composer/packages/extensions/adaptive-form/src/utils/getOrderedProperties.ts +++ b/Composer/packages/extensions/adaptive-form/src/utils/getOrderedProperties.ts @@ -4,20 +4,19 @@ import { UIOptions, JSONSchema7 } from '@bfc/extension'; import cloneDeep from 'lodash/cloneDeep'; import formatMessage from 'format-message'; -const globalHiddenProperties = ['$kind', '$id', '$copy', '$designer', 'id', 'disabled']; +import { getHiddenProperties } from './getHiddenProperties'; type OrderConfig = (string | [string, string])[]; export function getOrderedProperties( schema: JSONSchema7, - uiOptions: UIOptions, + baseUiOptions: UIOptions, // eslint-disable-next-line @typescript-eslint/no-explicit-any data: any ): OrderConfig { - const { hidden, order = ['*'] } = cloneDeep(uiOptions); - - const hiddenFieldSet = new Set(typeof hidden === 'function' ? hidden(data) : hidden || []); - globalHiddenProperties.forEach((f) => hiddenFieldSet.add(f)); + const uiOptions = cloneDeep(baseUiOptions); + const { order = ['*'] } = uiOptions; + const hiddenFieldSet = getHiddenProperties(uiOptions, data); const uiOrder = typeof order === 'function' ? order(data) : order || []; const orderedFieldSet = new Set(); @@ -59,7 +58,7 @@ export function getOrderedProperties( const restIdx = orderedFields.indexOf('*'); // only validate wildcard if not all properties are ordered already if (allProperties.some((p) => !orderedFieldSet.has(p))) { - let errorMsg = ''; + let errorMsg; if (restIdx === -1) { errorMsg = formatMessage('no wildcard'); } else if (restIdx !== orderedFields.lastIndexOf('*')) { @@ -75,15 +74,16 @@ export function getOrderedProperties( }) ); } + } - const restFields = Object.keys(schema.properties || {}).filter((p) => { - return !orderedFieldSet.has(p) && !hiddenFieldSet.has(p) && !p.startsWith('$'); - }); + const restFields = Object.keys(schema.properties || {}).filter((p) => { + return !orderedFieldSet.has(p) && !p.startsWith('$'); + }); + if (restIdx === -1) { + orderedFields.push(...restFields); + } else { orderedFields.splice(restIdx, 1, ...restFields); - } else if (restIdx > -1) { - // remove the wildcard - orderedFields.splice(restIdx, 1); } return orderedFields; diff --git a/Composer/packages/extensions/adaptive-form/src/utils/index.ts b/Composer/packages/extensions/adaptive-form/src/utils/index.ts index 099378cd18..087351f409 100644 --- a/Composer/packages/extensions/adaptive-form/src/utils/index.ts +++ b/Composer/packages/extensions/adaptive-form/src/utils/index.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. export * from './arrayUtils'; +export * from './getHiddenProperties'; export * from './getOrderedProperties'; export * from './getUIOptions'; export * from './getValueType'; diff --git a/Composer/packages/extensions/adaptive-form/src/utils/resolveRef.ts b/Composer/packages/extensions/adaptive-form/src/utils/resolveRef.ts index 6af2e7bf5a..e41973eaa7 100644 --- a/Composer/packages/extensions/adaptive-form/src/utils/resolveRef.ts +++ b/Composer/packages/extensions/adaptive-form/src/utils/resolveRef.ts @@ -8,6 +8,10 @@ export function resolveRef( definitions: { [key: string]: JSONSchema7Definition } = {} ): JSONSchema7 { if (typeof schema?.$ref === 'string') { + if (!schema?.$ref?.startsWith('#/definitions/')) { + return schema; + } + const defName = schema.$ref.replace('#/definitions/', ''); const defSchema = definitions?.[defName] as JSONSchema7; diff --git a/Composer/packages/extensions/extension/src/types/form.ts b/Composer/packages/extensions/extension/src/types/form.ts index 4374fb8e38..2de9d5a4d3 100644 --- a/Composer/packages/extensions/extension/src/types/form.ts +++ b/Composer/packages/extensions/extension/src/types/form.ts @@ -40,6 +40,7 @@ export interface FieldProps { disabled?: boolean; enumOptions?: string[]; error?: string | JSX.Element; + hidden?: boolean; hideError?: boolean; id: string; label?: string | false;