Skip to content

Commit

Permalink
Merge branch 'main' into tachou/orchColdBootOptimization
Browse files Browse the repository at this point in the history
  • Loading branch information
boydc2014 authored Mar 30, 2021
2 parents 23395b1 + cf42c73 commit 78c2946
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 33 deletions.
3 changes: 2 additions & 1 deletion Composer/packages/ui-plugins/select-dialog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"react-dom": "16.13.1"
},
"dependencies": {
"@emotion/core": "^10.0.27"
"@emotion/core": "^10.0.27",
"ajv": "7.2.3"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,22 @@
import React from 'react';
import styled from '@emotion/styled';
import { FieldProps, JSONSchema7, useShellApi } from '@bfc/extension-client';
import { FieldLabel, JsonField, SchemaField, IntellisenseTextField, WithTypeIcons } from '@bfc/adaptive-form';
import { FieldLabel, IntellisenseTextField, OpenObjectField, WithTypeIcons, SchemaField } from '@bfc/adaptive-form';
import Stack from 'office-ui-fabric-react/lib/components/Stack/Stack';
import { FluentTheme, NeutralColors } from '@uifabric/fluent-theme';
import formatMessage from 'format-message';
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import Ajv, { AnySchemaObject } from 'ajv';

const loadSchema = async (uri: string) => {
const res = await fetch(uri);
return res.body as AnySchemaObject;
};

const ajv = new Ajv({
loadSchema,
strict: false,
});

const IntellisenseTextFieldWithIcon = WithTypeIcons(IntellisenseTextField);

Expand Down Expand Up @@ -42,16 +53,22 @@ const styles = {

const dropdownCalloutProps = { styles: { root: { minWidth: 140 } } };

const getInitialSelectedKey = (value?: string | Record<string, unknown>, schema?: JSONSchema7): string => {
if (typeof value !== 'string' && schema) {
const getSelectedKey = (
value?: string | Record<string, unknown>,
schema?: JSONSchema7,
validSchema = false
): string => {
if (typeof value !== 'string' && schema && validSchema) {
return 'form';
} else if (typeof value !== 'string' && !schema) {
return 'code';
} else if (typeof value !== 'string' && (!schema || !validSchema)) {
return 'object';
} else {
return 'expression';
}
};

type JSONValidationStatus = 'valid' | 'inValid' | 'validating';

const DialogOptionsField: React.FC<FieldProps> = ({
description,
uiOptions,
Expand All @@ -68,11 +85,39 @@ const DialogOptionsField: React.FC<FieldProps> = ({
[dialog, dialogSchemas]
);

const [selectedKey, setSelectedKey] = React.useState<string>(getInitialSelectedKey(options, schema));
const [selectedKey, setSelectedKey] = React.useState<string>();
const [validationStatus, setValidationStatus] = React.useState<JSONValidationStatus>('validating');

React.useLayoutEffect(() => {
setSelectedKey(getInitialSelectedKey(options, schema));
}, [dialog]);
const mountRef = React.useRef(false);

React.useEffect(() => {
mountRef.current = true;
if (schema && Object.keys(schema.properties || {}).length) {
(async () => {
setValidationStatus('validating');
try {
const validate = await ajv.compileAsync(schema, true);
const valid = validate(schema);

if (mountRef.current) {
setValidationStatus(valid ? 'valid' : 'inValid');
setSelectedKey(getSelectedKey(options, schema, true));
}
} catch (error) {
if (mountRef.current) {
setValidationStatus('inValid');
setSelectedKey(getSelectedKey(options, schema, false));
}
}
})();
} else {
setSelectedKey(getSelectedKey(options, schema, false));
}

return () => {
mountRef.current = false;
};
}, [schema]);

const change = React.useCallback(
(newOptions?: string | Record<string, any>) => {
Expand All @@ -84,38 +129,43 @@ const DialogOptionsField: React.FC<FieldProps> = ({
const onDropdownChange = React.useCallback(
(_: React.FormEvent<HTMLDivElement>, option?: IDropdownOption) => {
if (option) {
setSelectedKey(option.key as string);
if (option.key === 'expression') {
// When the user switched between data types - either a string (expression) or an object (form or object) - we need to set
// options to undefined so we don't incorrectly pass a string to an object editor or pass an object to a string editor.

// If selectedKey is currently set to expression and the user is switching to form or object, set the value to undefined.
// If the user is switching to expression meaning the selectedKey is currently set to form or form, set the value to undefined.
if (option.key === 'expression' || selectedKey === 'expression') {
change();
}
setSelectedKey(option.key as string);
}
},
[change]
[change, selectedKey]
);

const typeOptions = React.useMemo<IDropdownOption[]>(() => {
return [
{
key: 'form',
text: formatMessage('form'),
disabled: !schema || !Object.keys(schema).length,
disabled: !schema || validationStatus !== 'valid',
},
{
key: 'code',
text: formatMessage('code editor'),
key: 'object',
text: formatMessage('object'),
},
{
key: 'expression',
text: 'expression',
},
];
}, [schema]);
}, [schema, validationStatus]);

let Field = IntellisenseTextFieldWithIcon;
if (selectedKey === 'form') {
Field = SchemaField;
} else if (selectedKey === 'code') {
Field = JsonField;
} else if (selectedKey === 'object') {
Field = OpenObjectField;
}

return (
Expand Down Expand Up @@ -144,9 +194,9 @@ const DialogOptionsField: React.FC<FieldProps> = ({
id={`${id}.options`}
label={false}
name={'options'}
schema={schema || {}}
schema={(selectedKey === 'form' ? schema : { type: 'object', additionalProperties: true }) || {}}
uiOptions={{}}
value={options || selectedKey === 'expression' ? '' : {}}
value={options}
onChange={change}
/>
</React.Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,28 @@ import { EditorExtension } from '@bfc/extension-client';
import { DialogOptionsField } from '../DialogOptionsField';

jest.mock('@bfc/adaptive-form', () => {
const AdaptiveForm = jest.requireActual('@bfc/adaptive-form');
const MockAdaptiveForm = jest.requireActual('@bfc/adaptive-form');

return {
...AdaptiveForm,
JsonField: () => <div>Json Field</div>,
...MockAdaptiveForm,
OpenObjectField: () => <div>Object Field</div>,
SchemaField: () => <div>Options Form</div>,
IntellisenseTextField: () => <div>Intellisense Text Field</div>,
};
});

jest.mock('office-ui-fabric-react/lib/Dropdown', () => {
const Dropdown = jest.requireActual('office-ui-fabric-react/lib/Dropdown');
const MockDropdown = jest.requireActual('office-ui-fabric-react/lib/Dropdown');

return {
...Dropdown,
...MockDropdown,
Dropdown: ({ onChange }) => (
<button
onClick={(e) => {
onChange(e, { key: 'code' });
onChange(e, { key: 'object' });
}}
>
Switch to Json Field
Switch to Object Field
</button>
),
};
Expand Down Expand Up @@ -88,7 +88,7 @@ describe('DialogOptionsField', () => {
});
it('should render the JsonField if the dialog schema is undefined and options is not a string', async () => {
const { findByText } = renderDialogOptionsField({ value: { dialog: 'dialog2', options: {} } });
await findByText('Json Field');
await findByText('Object Field');
});
it('should render the IntellisenseTextField if options is a string', async () => {
const { findByText } = renderDialogOptionsField({ value: { dialog: 'dialog2', options: '=user.data' } });
Expand All @@ -102,10 +102,10 @@ describe('DialogOptionsField', () => {
// Should initially render Options Form
await findByText('Options Form');

// Switch to Json field
const button = await findByText('Switch to Json Field');
// Switch to Object field
const button = await findByText('Switch to Object Field');
fireEvent.click(button);

await findByText('Json Field');
await findByText('Object Field');
});
});
17 changes: 16 additions & 1 deletion Composer/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6027,6 +6027,16 @@ ajv-keywords@^3.4.1:
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==

[email protected]:
version "7.2.3"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.2.3.tgz#ca78d1cf458d7d36d1c3fa0794dd143406db5772"
integrity sha512-idv5WZvKVXDqKralOImQgPM9v6WOdLNa0IY3B3doOjw/YxRGT8I+allIJ6kd7Uaj+SF1xZUSU+nPM5aDNBVtnw==
dependencies:
fast-deep-equal "^3.1.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
uri-js "^4.2.2"

ajv@^4.7.0:
version "4.11.8"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
Expand Down Expand Up @@ -14995,6 +15005,11 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==

json-schema-traverse@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==

[email protected]:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
Expand Down Expand Up @@ -19933,7 +19948,7 @@ require-directory@^2.1.1:
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=

require-from-string@^2.0.1:
require-from-string@^2.0.1, require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
Expand Down

0 comments on commit 78c2946

Please sign in to comment.