Skip to content

Commit

Permalink
feat(Template): Parameters Panel (#4815)
Browse files Browse the repository at this point in the history
* display parameters correctly with the designer UI for parameters

* made only value params field editable

* added validation logic

* enforced required with error handling

* seperated templateParameters field with description

* cleaned files not used & naming

* moved validate parameter value logic to validation to avoid duplication

* added tests for validateParameterValueWithSwaggerType

* added templateParametersField test

* shifted from templateParameterField to workflowParameterField adding extra attributes

* modified workflowparameterfield test to reflect template description

* added some test case for display parameters

* added coverage for validation error

* Fixed duplicate type rendering issue for tests
  • Loading branch information
Elaina-Lee committed May 10, 2024
1 parent 6b21a68 commit 1911f55
Show file tree
Hide file tree
Showing 15 changed files with 605 additions and 72 deletions.
2 changes: 2 additions & 0 deletions Localize/lang/strings.json
Expand Up @@ -772,6 +772,7 @@
"USVffu": "Content not shown due to security configuration.",
"UT2ozj": "(UTC-07:00) Mountain Time (US & Canada)",
"UVAfYj": "Required. The collection to take the last object from.",
"UXDOiw": "Description",
"UYRIS/": "{fileName} (file name)",
"UZiXVh": "Output",
"Ufv5m9": "Schema node ''{nodeName}'' has an non-terminating connection chain",
Expand Down Expand Up @@ -1692,6 +1693,7 @@
"_USVffu.comment": "Message text to inform the customer that the data is secure",
"_UT2ozj.comment": "Time zone value ",
"_UVAfYj.comment": "The required collection parameter for applying the 'last' function.",
"_UXDOiw.comment": "Parameter Field Description Title",
"_UYRIS/.comment": "Title for file name parameter",
"_UZiXVh.comment": "The title of the output field in the static result http action",
"_Ufv5m9.comment": "Body text for an unconnected required schema card",
Expand Down
Expand Up @@ -7,6 +7,7 @@ import * as ReactShallowRenderer from 'react-test-renderer/shallow';
import { describe, vi, beforeEach, afterEach, it, expect } from 'vitest';
describe('ui/workflowparameters/workflowparameterField', () => {
let minimal: WorkflowparameterFieldProps;
let minimalWithDes: WorkflowparameterFieldProps;
let renderer: ReactShallowRenderer.ShallowRenderer;

beforeEach(() => {
Expand All @@ -16,6 +17,13 @@ describe('ui/workflowparameters/workflowparameterField', () => {
definition: { id: 'id', value: 'blue', name: 'test', type: 'String' },
setName: vi.fn(),
};
minimalWithDes = {
...minimal,
definition: {
...minimal.definition,
description: 'test des',
},
};
renderer = ReactShallowRenderer.createRenderer();
initializeIcons();
});
Expand All @@ -34,8 +42,9 @@ describe('ui/workflowparameters/workflowparameterField', () => {
const intl = useIntl();
renderer.render(<WorkflowparameterField {...minimal} />);
const parameterFields = renderer.getRenderOutput();
expect(parameterFields.props.children).toHaveLength(3);
expect(parameterFields.props.children).toHaveLength(4);

expect(parameterFields.props.children?.[2]).toBe(undefined); //undefined for empty description
const [name, type, defaultValue]: any[] = React.Children.toArray(parameterFields.props.children);
const textFieldClassName = 'msla-workflow-parameter-field';

Expand Down Expand Up @@ -69,9 +78,91 @@ describe('ui/workflowparameters/workflowparameterField', () => {
expect(type.props.className).toBe(textFieldClassName);
expect(type.props.children).toHaveLength(2);

const [label2, dropdown]: any[] = React.Children.toArray(type.props.children);
expect(label2.props.children).toBe(typeTitle);
expect(label2.props.htmlFor).toBe('id-type');
const [label3, dropdown]: any[] = React.Children.toArray(type.props.children);
expect(label3.props.children).toBe(typeTitle);
expect(label3.props.htmlFor).toBe('id-type');

expect(dropdown.props.id).toBe('id-type');
expect(dropdown.props.ariaLabel).toBe(typeTitle);
expect(dropdown.props.options).toHaveLength(6);
expect(dropdown.props.selectedKey).toBe('String');

const defaultValueTitle = intl.formatMessage({
defaultMessage: 'Value',
id: 'mOxbN1',
description: 'Parameter Field Default Value Title',
});
const defaultValueDescription = intl.formatMessage({
defaultMessage: 'Enter value for parameter.',
id: '7jAQar',
description: 'Parameter Field Default Value Placeholder Text',
});
expect(defaultValue.props.className).toBe(textFieldClassName);
expect(name.props.children).toHaveLength(2);

const [label4, textField3]: any[] = React.Children.toArray(defaultValue.props.children);
expect(label4.props.children).toBe(defaultValueTitle);
expect(label4.props.htmlFor).toBe('id-value');

expect(textField3.props.id).toBe('id-value');
expect(textField3.props.ariaLabel).toBe(defaultValueTitle);
expect(textField3.props.placeholder).toBe(defaultValueDescription);
expect(textField3.props.value).toBe(minimal.definition.value);
});

it('should render all fields when passed a parameter definition with a description.', () => {
const intl = useIntl();
renderer.render(<WorkflowparameterField {...minimalWithDes} />);
const parameterFields = renderer.getRenderOutput();
expect(parameterFields.props.children).toHaveLength(4);

const [name, type, description, defaultValue]: any[] = React.Children.toArray(parameterFields.props.children);
const textFieldClassName = 'msla-workflow-parameter-field';

const nameTitle = intl.formatMessage({
defaultMessage: 'Name',
id: 'm8Q61y',
description: 'Parameter Field Name Title',
});
const nameDescription = intl.formatMessage({
defaultMessage: 'Enter parameter name.',
id: 'GreYWQ',
description: 'Parameter Field Name Description',
});
expect(name.props.className).toBe(textFieldClassName);
expect(name.props.children).toHaveLength(2);

const [label, textField1]: any[] = React.Children.toArray(name.props.children);
expect(label.props.children).toBe(nameTitle);
expect(label.props.htmlFor).toBe('id-name');

expect(textField1.props.id).toBe('id-name');
expect(textField1.props.ariaLabel).toBe(nameTitle);
expect(textField1.props.placeholder).toBe(nameDescription);
expect(textField1.props.value).toBe(minimalWithDes.name);

const typeTitle = intl.formatMessage({
defaultMessage: 'Type',
id: 'tNoZx2',
description: 'Parameter Field Type Title',
});
expect(type.props.className).toBe(textFieldClassName);
expect(type.props.children).toHaveLength(2);

const descriptionTitle = intl.formatMessage({
defaultMessage: 'Description',
id: 'UXDOiw',
description: 'Parameter Field Description Title',
});
const [label2, textField2]: any[] = React.Children.toArray(description.props.children);
expect(description.props.className).toBe(textFieldClassName);
expect(label2.props.children).toBe(descriptionTitle);
expect(label2.props.htmlFor).toBe('id-description');
expect(textField2.props.children).toBe(minimalWithDes.definition.description);

const [label3, dropdown]: any[] = React.Children.toArray(type.props.children);
expect(label3.props.children).toBe(typeTitle);
expect(label3.props.htmlFor).toBe('id-type');

expect(dropdown.props.id).toBe('id-type');
expect(dropdown.props.ariaLabel).toBe(typeTitle);
Expand All @@ -91,14 +182,14 @@ describe('ui/workflowparameters/workflowparameterField', () => {
expect(defaultValue.props.className).toBe(textFieldClassName);
expect(name.props.children).toHaveLength(2);

const [label3, textField2]: any[] = React.Children.toArray(defaultValue.props.children);
expect(label3.props.children).toBe(defaultValueTitle);
expect(label3.props.htmlFor).toBe('id-value');
const [label4, textField3]: any[] = React.Children.toArray(defaultValue.props.children);
expect(label4.props.children).toBe(defaultValueTitle);
expect(label4.props.htmlFor).toBe('id-value');

expect(textField2.props.id).toBe('id-value');
expect(textField2.props.ariaLabel).toBe(defaultValueTitle);
expect(textField2.props.placeholder).toBe(defaultValueDescription);
expect(textField2.props.value).toBe(minimal.definition.value);
expect(textField3.props.id).toBe('id-value');
expect(textField3.props.ariaLabel).toBe(defaultValueTitle);
expect(textField3.props.placeholder).toBe(defaultValueDescription);
expect(textField3.props.value).toBe(minimal.definition.value);
});

// TODO: 12798972 render correct type value when case does not match serialized type
Expand Down
1 change: 1 addition & 0 deletions libs/designer-ui/src/lib/workflowparameters/index.ts
@@ -1,3 +1,4 @@
export * from './workflowparameter';
export * from './workflowparameters';
export * from './workflowparametersFooter';
export * from './workflowparametersField';
Expand Up @@ -32,6 +32,8 @@ export interface WorkflowParameterDefinition {
name?: string;
type: string;
defaultValue?: string;
required?: boolean;
description?: string;
}

export interface WorkflowParameterProps {
Expand Down
Expand Up @@ -10,7 +10,7 @@ import type {
ITextStyles,
} from '@fluentui/react';
import { Dropdown, FontWeights, getTheme, Label, Text, TextField } from '@fluentui/react';
import { equals } from '@microsoft/logic-apps-shared';
import { equals, getRecordEntry } from '@microsoft/logic-apps-shared';
import { useState } from 'react';
import { useIntl } from 'react-intl';

Expand Down Expand Up @@ -58,6 +58,8 @@ const textStyles: Partial<ITextStyles> = {
};

const NAME_KEY = 'name';
const TYPE_KEY = 'type';
const DESCRIPTION_KEY = 'description';
const VALUE_KEY = 'value';
const DEFAULT_VALUE_KEY = 'defaultValue';

Expand All @@ -66,6 +68,7 @@ export interface ParameterFieldDetails {
value: string;
defaultValue?: string;
type: string;
description?: string;
}

export interface WorkflowparameterFieldProps {
Expand All @@ -76,7 +79,8 @@ export interface WorkflowparameterFieldProps {
onChange?: WorkflowParameterUpdateHandler;
useLegacy?: boolean;
isReadOnly?: boolean;
isEditable?: boolean;
isEditable?: boolean | Record<string, boolean>;
required?: boolean | Record<string, boolean>;
}

export const WorkflowparameterField = ({
Expand All @@ -86,6 +90,7 @@ export const WorkflowparameterField = ({
setName,
onChange,
isEditable,
required = true,
isReadOnly,
useLegacy,
}: WorkflowparameterFieldProps): JSX.Element => {
Expand All @@ -98,6 +103,7 @@ export const WorkflowparameterField = ({
const parameterDetails: ParameterFieldDetails = {
name: `${definition.id}-${NAME_KEY}`,
value: `${definition.id}-${VALUE_KEY}`,
description: `${definition.id}-${DESCRIPTION_KEY}`,
defaultValue: `${definition.id}-default-${VALUE_KEY}`,
type: `${definition.id}-type`,
};
Expand Down Expand Up @@ -189,6 +195,11 @@ export const WorkflowparameterField = ({
id: 'tNoZx2',
description: 'Parameter Field Type Title',
});
const descriptionTitle = intl.formatMessage({
defaultMessage: 'Description',
id: 'UXDOiw',
description: 'Parameter Field Description Title',
});
const valueTitle = intl.formatMessage({
defaultMessage: 'Value',
id: 'ClZW2r',
Expand Down Expand Up @@ -293,10 +304,10 @@ export const WorkflowparameterField = ({
return (
<>
<div className="msla-workflow-parameter-field">
<Label styles={labelStyles} required={true} htmlFor={parameterDetails.name}>
<Label styles={labelStyles} required={getFieldBooleanValue(required, NAME_KEY)} htmlFor={parameterDetails.name}>
{nameTitle}
</Label>
{isEditable ? (
{getFieldBooleanValue(isEditable, NAME_KEY) ? (
<TextField
data-testid={parameterDetails.name}
styles={textFieldStyles}
Expand All @@ -313,10 +324,10 @@ export const WorkflowparameterField = ({
)}
</div>
<div className="msla-workflow-parameter-field">
<Label styles={labelStyles} required={true} htmlFor={parameterDetails.type}>
<Label styles={labelStyles} required={getFieldBooleanValue(required, TYPE_KEY)} htmlFor={parameterDetails.type}>
{typeTitle}
</Label>
{isEditable ? (
{getFieldBooleanValue(isEditable, TYPE_KEY) ? (
<Dropdown
data-testid={parameterDetails.type}
id={parameterDetails.type}
Expand All @@ -331,10 +342,22 @@ export const WorkflowparameterField = ({
<Text className="msla-workflow-parameter-read-only">{type}</Text>
)}
</div>
{definition?.description && (
<div className="msla-workflow-parameter-field">
<Label styles={labelStyles} required={false} htmlFor={parameterDetails.description}>
{descriptionTitle}
</Label>
<Text className="msla-workflow-parameter-read-only">{definition.description}</Text>
</div>
)}
{useLegacy ? (
<>
<div className="msla-workflow-parameter-field">
<Label styles={labelStyles} required={true} htmlFor={parameterDetails.defaultValue}>
<Label
styles={labelStyles}
required={getFieldBooleanValue(required, DEFAULT_VALUE_KEY)}
htmlFor={parameterDetails.defaultValue}
>
{defaultValueTitle}
</Label>
{isEditable ? (
Expand All @@ -359,7 +382,7 @@ export const WorkflowparameterField = ({
<Label styles={labelStyles} htmlFor={parameterDetails.value}>
{actualValueTitle}
</Label>
{isEditable ? (
{getFieldBooleanValue(isEditable, VALUE_KEY) ? (
<TextField
data-testid={parameterDetails.value}
id={parameterDetails.value}
Expand All @@ -376,10 +399,10 @@ export const WorkflowparameterField = ({
</>
) : (
<div className="msla-workflow-parameter-field">
<Label styles={labelStyles} required={true} htmlFor={parameterDetails.value}>
<Label styles={labelStyles} required={getFieldBooleanValue(required, VALUE_KEY)} htmlFor={parameterDetails.value}>
{valueTitle}
</Label>
{isEditable ? (
{getFieldBooleanValue(isEditable, VALUE_KEY) ? (
<TextField
data-testid={parameterDetails.value}
id={parameterDetails.value}
Expand Down Expand Up @@ -407,3 +430,7 @@ function isSecureParameter(type?: string): boolean {
function stringifyValue(value: any): string {
return typeof value !== 'string' ? JSON.stringify(value) : value;
}

function getFieldBooleanValue(value: boolean | Record<string, boolean> | undefined, fieldKey: string): boolean {
return typeof value === 'boolean' ? value : getRecordEntry(value, fieldKey) ?? false;
}

0 comments on commit 1911f55

Please sign in to comment.