Skip to content

Commit

Permalink
feat(RootOrWithAnd): add visual clue for boolean branch return value (#…
Browse files Browse the repository at this point in the history
…413)

feat(RootOrWithAnd): add visual clue for boolean branch return value
  • Loading branch information
balzdur authored Mar 28, 2024
1 parent 6cf320f commit fa66924
Show file tree
Hide file tree
Showing 17 changed files with 180 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function Default({
validationStatus,
type,
}: DefaultProps) {
const { t } = useTranslation(['scenarios']);
const { t } = useTranslation(['common', 'scenarios']);

const customLists = useCustomLists();
const dataModel = useDataModel();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type AstNode } from '@app-builder/models';
import { useAdaptEditableAstNode } from '@app-builder/services/ast-node/options';
import { formatReturnValue } from '@app-builder/services/ast-node/return-value';
import {
type EditorNodeViewModel,
getValidationStatus,
Expand Down Expand Up @@ -39,7 +40,7 @@ export function Operand({
editableAstNode={editableAstNode}
validationStatus={getValidationStatus(operandViewModel)}
type="viewer"
returnValue={operandViewModel.returnValue}
returnValue={formatReturnValue(operandViewModel.returnValue)}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,13 @@ function useMatchOptions({
}) {
const { t } = useTranslation(['common']);
const constantOptions = useMemo(() => {
return coerceToConstantEditableAstNode(searchValue, {
booleans: { true: [t('common:true')], false: [t('common:false')] },
return coerceToConstantEditableAstNode(t, searchValue, {
// Accept english and localized values for booleans
// They will be coerced to the localized value
booleans: {
true: ['true', t('common:true')],
false: ['false', t('common:false')],
},
});
}, [searchValue, t]);
const matchOptions = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ function AggregatorDescription({
}: {
editableAstNode: AggregatorEditableAstNode;
}) {
const { t } = useTranslation(['scenarios']);
const { t } = useTranslation(['common', 'scenarios']);
const { aggregator, tableName, fieldName, filters } =
editableAstNode.astNode.namedChildren;
if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function Operator<T extends OperatorFunction>({
errors?: EvaluationError[];
viewOnly?: boolean;
}) {
const { t } = useTranslation(['scenarios']);
const { t } = useTranslation(['common', 'scenarios']);

// We treat undefinedAstNodeName as "no value"
const _value = value !== undefinedAstNodeName ? value : undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const TimestampField = ({
errors: EvaluationError[];
value: EditorNodeViewModel | null;
}) => {
const { t } = useTranslation(['scenarios']);
const { t } = useTranslation(['common', 'scenarios']);
const dataModel = useDataModel();
const triggerObjectTable = useTriggerObjectTable();
const options = useTimestampFieldOptions();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export function TwoOperandsLine({
/>
<Operator
value={twoOperandsViewModel.operator.funcName}
setValue={(operator) => {
setValue={(operator: (typeof operators)[number]) => {
setOperator(twoOperandsViewModel.operator.nodeId, operator);
}}
errors={twoOperandsViewModel.operator.errors}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import {
type EvaluationError,
separateChildrenErrors,
} from '@app-builder/models/node-evaluation';
import {
adaptBooleanReturnValue,
useDisplayReturnValues,
} from '@app-builder/services/ast-node/return-value';
import {
type EditorNodeViewModel,
findArgumentIndexErrorsFromParent,
Expand All @@ -20,6 +24,8 @@ import {
} from '@app-builder/services/validation';
import clsx from 'clsx';
import { Fragment } from 'react';
import { useTranslation } from 'react-i18next';
import { Tag } from 'ui-design-system';

import { EvaluationErrors } from '../../ScenarioValidationError';
import { AstBuilderNode } from '../AstBuilderNode/AstBuilderNode';
Expand Down Expand Up @@ -48,6 +54,7 @@ export function adaptRootOrWithAndViewModel(
return null;
}
}

return {
orNodeId: viewModel.nodeId,
orErrors: viewModel.errors,
Expand Down Expand Up @@ -87,6 +94,7 @@ export function RootOrWithAnd({
rootOrWithAndViewModel: RootOrWithAndViewModel;
viewOnly?: boolean;
}) {
const { t } = useTranslation(['common']);
const getOrAndNodeEvaluationErrorMessage =
useGetOrAndNodeEvaluationErrorMessage();
const getNodeEvaluationErrorMessage = useGetNodeEvaluationErrorMessage();
Expand All @@ -102,8 +110,10 @@ export function RootOrWithAnd({
getOrAndNodeEvaluationErrorMessage,
);

const [displayReturnValues] = useDisplayReturnValues();

return (
<div className="grid grid-cols-[40px_1fr_30px] gap-2">
<div className="grid grid-cols-[40px_1fr_max-content] gap-2">
{rootOrWithAndViewModel.ands.map((andChild, childIndex) => {
const isFirstChild = childIndex === 0;
const { nodeErrors: andNodeErrors } = separateChildrenErrors(
Expand Down Expand Up @@ -145,6 +155,35 @@ export function RootOrWithAnd({
...findArgumentIndexErrorsFromParent(child),
]).map(getNodeEvaluationErrorMessage);

const childBooleanReturnValue = adaptBooleanReturnValue(
child.returnValue,
);

let rightComponent = null;
if (!viewOnly) {
rightComponent = (
<div className="flex h-10 items-center justify-center">
<RemoveButton
onClick={() => {
removeAndChild(child.nodeId);
}}
/>
</div>
);
} else if (displayReturnValues && childBooleanReturnValue) {
rightComponent = (
<div className="flex h-10 items-center justify-center">
<Tag
border="square"
className="w-full"
color={childBooleanReturnValue.value ? 'green' : 'red'}
>
{t(`common:${childBooleanReturnValue.value}`)}
</Tag>
</div>
);
}

return (
// AND operand row
<Fragment key={child.nodeId}>
Expand All @@ -160,7 +199,7 @@ export function RootOrWithAnd({
<div
className={clsx(
'flex flex-col gap-2',
viewOnly ? 'col-span-2' : 'col-span-1',
rightComponent === null && 'col-span-2',
)}
>
<AstBuilderNode
Expand All @@ -172,15 +211,7 @@ export function RootOrWithAnd({
/>
<EvaluationErrors errors={errorMessages} />
</div>
{!viewOnly ? (
<div className="flex h-10 flex-col items-center justify-center">
<RemoveButton
onClick={() => {
removeAndChild(child.nodeId);
}}
/>
</div>
) : null}
{rightComponent}
</Fragment>
);
})}
Expand Down
38 changes: 26 additions & 12 deletions packages/app-builder/src/models/editable-ast-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,27 @@ interface EditableAstNodeBase {
displayName: string;
}

type TFunctionDisplayName = TFunction<['common', 'scenarios'], undefined>;

export class ConstantEditableAstNode implements EditableAstNodeBase {
astNode: ConstantAstNode;
operandType: OperandType;
dataType: DataType;
displayName: string;

constructor(astNode: ConstantAstNode, enumOptions: EnumValue[]) {
constructor(
t: TFunctionDisplayName,
astNode: ConstantAstNode,
enumOptions: EnumValue[],
) {
this.astNode = astNode;
this.operandType = ConstantEditableAstNode.getOperandType(
astNode.constant,
enumOptions,
);
this.dataType = ConstantEditableAstNode.getDataType(astNode.constant);
this.displayName = ConstantEditableAstNode.getConstantDisplayName(
t,
astNode.constant,
);
}
Expand Down Expand Up @@ -147,26 +154,33 @@ export class ConstantEditableAstNode implements EditableAstNodeBase {

private static getConstantDisplayName(
this: void,
t: TFunctionDisplayName,
constant: ConstantType,
): string {
if (R.isNil(constant)) return '';

if (R.isArray(constant)) {
return `[${constant.map(ConstantEditableAstNode.getConstantDisplayName).join(', ')}]`;
return `[${constant.map((constant) => ConstantEditableAstNode.getConstantDisplayName(t, constant)).join(', ')}]`;
}

if (R.isString(constant)) {
//TODO(combobox): handle Timestamp here, if we do manipulate them as ISOstring
return `"${constant.toString()}"`;
}

if (R.isNumber(constant) || R.isBoolean(constant)) {
if (R.isNumber(constant)) {
return constant.toString();
}

if (R.isBoolean(constant)) {
return t(`common:${constant}`);
}

// Handle other cases when needed
return JSON.stringify(
R.mapValues(constant, ConstantEditableAstNode.getConstantDisplayName),
R.mapValues(constant, (constant) =>
ConstantEditableAstNode.getConstantDisplayName(t, constant),
),
);
}
}
Expand All @@ -182,7 +196,7 @@ export class AggregatorEditableAstNode implements EditableAstNodeBase {
triggerObjectTable: TableModel;

constructor(
t: TFunction<['scenarios'], undefined>,
t: TFunctionDisplayName,
astNode: AggregationAstNode,
dataModel: TableModel[],
customLists: CustomList[],
Expand All @@ -200,7 +214,7 @@ export class AggregatorEditableAstNode implements EditableAstNodeBase {

static getAggregatorDisplayName(
this: void,
t: TFunction<['scenarios'], undefined>,
t: TFunctionDisplayName,
astNode: AggregationAstNode,
) {
const { aggregator, label } = astNode.namedChildren;
Expand Down Expand Up @@ -312,15 +326,15 @@ export class TimeAddEditableAstNode implements EditableAstNodeBase {
displayName: string;

constructor(
t: TFunction<['scenarios'], undefined>,
t: TFunctionDisplayName,
astNode: TimeAddAstNode = NewTimeAddAstNode(),
) {
this.astNode = astNode;
this.displayName = TimeAddEditableAstNode.getTimeAddName(t, astNode);
}

private static getTimeAddName(
t: TFunction<['scenarios'], undefined>,
t: TFunctionDisplayName,
astNode: TimeAddAstNode,
) {
const sign = astNode.namedChildren['sign']?.constant ?? '';
Expand Down Expand Up @@ -405,7 +419,7 @@ export class TimeNowEditableAstNode implements EditableAstNodeBase {
displayName: string;

constructor(
t: TFunction<['scenarios'], undefined>,
t: TFunctionDisplayName,
astNode: TimeNowAstNode = NewTimeNowAstNode(),
) {
this.astNode = astNode;
Expand Down Expand Up @@ -435,7 +449,7 @@ export type EditableAstNode =
| UndefinedEditableAstNode;

export function adaptEditableAstNode(
t: TFunction<['scenarios'], undefined>,
t: TFunctionDisplayName,
node: AstNode,
{
triggerObjectTable,
Expand All @@ -450,7 +464,7 @@ export function adaptEditableAstNode(
},
): EditableAstNode | undefined {
if (isConstant(node)) {
return new ConstantEditableAstNode(node, enumOptions);
return new ConstantEditableAstNode(t, node, enumOptions);
}

if (isCustomListAccess(node)) {
Expand Down Expand Up @@ -499,7 +513,7 @@ export function adaptEditableAstNode(
* - Else, return a default string representation
*/
export function stringifyAstNode(
t: TFunction<['scenarios'], undefined>,
t: TFunctionDisplayName,
astNode: AstNode,
config: {
triggerObjectTable: TableModel;
Expand Down
2 changes: 1 addition & 1 deletion packages/app-builder/src/models/editable-operators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export function isOperatorFunction(value: string): value is OperatorFunction {
}

export function getOperatorName(
t: TFunction<['scenarios'], undefined>,
t: TFunction<['common', 'scenarios'], undefined>,
operatorName: string,
) {
if (isOperatorFunction(operatorName)) {
Expand Down
7 changes: 4 additions & 3 deletions packages/app-builder/src/services/ast-node/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export function OptionsProvider({
}

export function useTimestampFieldOptions() {
const { t } = useTranslation(['scenarios']);
const { t } = useTranslation(['common', 'scenarios']);

const databaseAccessors = useDatabaseAccessors();
const payloadAccessors = usePayloadAccessors();
Expand Down Expand Up @@ -131,7 +131,7 @@ export function useOperandOptions({
}: {
operandViewModel: OperandViewModel;
}) {
const { t } = useTranslation(['scenarios']);
const { t } = useTranslation(['common', 'scenarios']);

const databaseAccessors = useDatabaseAccessors();
const payloadAccessors = usePayloadAccessors();
Expand Down Expand Up @@ -173,6 +173,7 @@ export function useOperandOptions({
const enumOptions = enumOptionValues.map(
(enumValue) =>
new ConstantEditableAstNode(
t,
NewConstantAstNode({
constant: enumValue,
}),
Expand Down Expand Up @@ -244,7 +245,7 @@ function getEnumOptionsFromNeighbour({
}

export function useAdaptEditableAstNode() {
const { t } = useTranslation(['scenarios']);
const { t } = useTranslation(['common', 'scenarios']);

const customLists = useCustomLists();
const dataModel = useDataModel();
Expand Down
13 changes: 12 additions & 1 deletion packages/app-builder/src/services/ast-node/return-value.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createContext, useContext, useState } from 'react';
import * as R from 'remeda';
import { noop } from 'typescript-utils';

type ReturnValue =
export type ReturnValue =
| {
value: ConstantType;
isOmitted: false;
Expand Down Expand Up @@ -65,3 +65,14 @@ export function DisplayReturnValuesProvider({
export function useDisplayReturnValues() {
return useContext(DisplayReturnValues);
}

export function adaptBooleanReturnValue(returnValue?: ReturnValue) {
if (
returnValue !== undefined &&
returnValue.isOmitted === false &&
typeof returnValue.value === 'boolean'
) {
return { value: returnValue.value };
}
return undefined;
}
Loading

0 comments on commit fa66924

Please sign in to comment.