Skip to content

Commit

Permalink
Rule viewer using AstNode instead of operator (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
Roukii authored Jul 31, 2023
2 parents 64c8d3c + 840ae7f commit 1e75207
Show file tree
Hide file tree
Showing 23 changed files with 529 additions and 1,227 deletions.
46 changes: 26 additions & 20 deletions packages/app-builder/src/components/Scenario/Formula/Formula.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,45 @@
import {
isConstantOperator,
isDataFieldOperator,
isMathOperator,
type AstNode,
isConstantNode,
isIdentifier,
isMathAst,
isPayload,
} from '@app-builder/models';
import { type Operator } from '@marble-api';
import { assertNever } from '@typescript-utils';
import { useEditorIdentifiers } from '@app-builder/services/editor';

import { NotImplemented } from './NotImplemented';
import { Constant, DataField, Math, Not } from './Operators';
import { Constant, Math } from './Operators';
import { Default } from './Operators/Default';
import { Identifier } from './Operators/Identifier';
import { Payload } from './Operators/Payload';

interface FormulaProps {
formula: Operator;
formula: AstNode;
isRoot?: boolean;
}

export function Formula({ formula, isRoot = false }: FormulaProps) {
if (isConstantOperator(formula)) {
return <Constant operator={formula} isRoot={isRoot} />;
const editorIdentifier = useEditorIdentifiers();
console.log('NAME : ', formula.name ?? '');
if (isConstantNode(formula)) {
return <Constant node={formula} isRoot={isRoot} />;
}
console.log('NOT CONSTANT : ', formula.name ?? '');

if (isDataFieldOperator(formula)) {
return <DataField operator={formula} isRoot={isRoot} />;
}
// if (isDataFieldOperator(formula)) {
// return <DataField operator={formula} isRoot={isRoot} />;
// }

if (isMathOperator(formula)) {
return <Math operator={formula} isRoot={isRoot} />;
if (isMathAst(formula)) {
return <Math node={formula} isRoot={isRoot} />;
}

if (formula.type === 'NOT') {
return <Not operator={formula} isRoot={isRoot} />;
if (isPayload(formula)) {
return <Payload node={formula} isRoot={isRoot} />;
}

if (formula.type === 'ROUND_FLOAT') {
return <NotImplemented value={JSON.stringify(formula, null, 2)} />;
if (isIdentifier(formula, editorIdentifier)) {
return <Identifier node={formula} isRoot={isRoot} />;
}

assertNever('unknwon Operator:', formula);
return <Default node={formula} isRoot={isRoot} />;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { type ConstantOperator } from '@app-builder/models';
import { type AstNode } from '@app-builder/models';
import { formatNumber } from '@app-builder/utils/format';
import { assertNever } from '@typescript-utils';
import { Tooltip } from '@ui-design-system';
import clsx from 'clsx';
import { useTranslation } from 'react-i18next';
Expand Down Expand Up @@ -53,43 +52,60 @@ function DefaultList({ isRoot, children, ...otherProps }: ScalarProps) {
}

export function Constant({
operator,
node,
isRoot,
}: {
operator: ConstantOperator;
node: AstNode;
isRoot?: boolean;
}) {
const { t, i18n } = useTranslation(scenarioI18n);

const { type } = operator;
switch (type) {
case 'STRING_LIST_CONSTANT': {
const formattedValue = formatArray(
operator.staticData.value,
formatString
if (node.constant === null) {
return (
<DefaultList isRoot={isRoot}>{JSON.stringify(node.constant)}</DefaultList>
);
}
switch (typeof node.constant) {
case 'object': {
if (
Array.isArray(node.constant) &&
node.constant.every((i) => typeof i === 'string')
) {
const formattedValue = formatArray(
node.constant as string[],
formatString
);
return <DefaultList isRoot={isRoot}>{formattedValue}</DefaultList>;
}
return (
<DefaultList isRoot={isRoot}>
{JSON.stringify(node.constant)}
</DefaultList>
);
return <DefaultList isRoot={isRoot}>{formattedValue}</DefaultList>;
}
case 'STRING_CONSTANT':
case 'string':
return (
<DefaultConstant isRoot={isRoot}>
{formatString(operator.staticData.value)}
{formatString(node.constant)}
</DefaultConstant>
);
case 'FLOAT_CONSTANT':
case 'number':
return (
<DefaultConstant isRoot={isRoot}>
{formatNumber(i18n.language, operator.staticData.value)}
{formatNumber(i18n.language, node.constant)}
</DefaultConstant>
);
case 'BOOL_CONSTANT':
case 'boolean':
return (
<DefaultConstant className="uppercase" isRoot={isRoot}>
{t(`scenarios:${operator.staticData.value}`)}
{t(`scenarios:${node.constant}`)}
</DefaultConstant>
);
default:
assertNever('unknwon ConstantOperator:', type);
return (
<DefaultList isRoot={isRoot}>
{JSON.stringify(node.constant)}
</DefaultList>
);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { type AstNode } from '@app-builder/models';
import clsx from 'clsx';

import { Condition } from './Condition';

function DefaultValue(node: AstNode) {
let value = (node.name ?? '') + '(';
for (const child of node.children) {
if (child.name === null) {
value += child.constant ?? '' + ', ';
} else {
value += DefaultValue(child) + ', ';
}
}
Object.keys(node.namedChildren).forEach((key) => {
const child = node.namedChildren[key];
if (child.name === null) {
const constant = (child.constant ?? '').toString();
value += key + ': ' + constant + ', ';
} else {
value += key + ': ' + DefaultValue(child) + ', ';
}
});
if (value.slice(-2) === ', ') {
value = value.substring(0, value.length - 2);
}
value += ')';
return value;
}

export function Default({ node, isRoot }: { node: AstNode; isRoot?: boolean }) {
return (
<Condition.Container isRoot={isRoot}>
<Condition.Item isRoot={isRoot}>
<span
className={clsx(
'text-grey-100 flex whitespace-pre text-center font-medium'
)}
>
{DefaultValue(node)}
</span>
</Condition.Item>
</Condition.Container>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
adaptAstNodeToViewModelFromIdentifier,
type AstNode,
} from '@app-builder/models';
import { useEditorIdentifiers } from '@app-builder/services/editor';
import { Tooltip } from '@ui-design-system';

import { Condition } from './Condition';

interface CustomListProps {
node: AstNode;
isRoot?: boolean;
}

export function Identifier({ node, isRoot }: CustomListProps) {
const editorIdentifier = useEditorIdentifiers();
const viewModel = adaptAstNodeToViewModelFromIdentifier(
node,
editorIdentifier
);
console.log(JSON.stringify(node, null, 2));
return (
<Condition.Container isRoot={isRoot}>
<Condition.Item isRoot={isRoot}>
{viewModel.tooltip ? (
<Tooltip.Default
content={
<span className="font-medium text-purple-100">
{viewModel.tooltip}
</span>
}
>
<span
// Hack to have text-ellipsis truncate beggining of the fields
dir="rtl"
className="max-w-[250px] overflow-hidden text-ellipsis font-medium text-purple-100 max-md:max-w-[150px]"
>
{viewModel.label}
</span>
</Tooltip.Default>
) : (
<span
// Hack to have text-ellipsis truncate beggining of the fields
dir="rtl"
className="max-w-[250px] overflow-hidden text-ellipsis font-medium text-purple-100 max-md:max-w-[150px]"
>
{viewModel.label}
</span>
)}
</Condition.Item>
</Condition.Container>
);
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import { type MathOperator as MathOperatorType } from '@app-builder/models';
import { assertNever } from '@typescript-utils';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { type AstNode } from '@app-builder/models';
import { useGetOperatorName } from '@app-builder/services/editor';
import React from 'react';

import { scenarioI18n } from '../../scenario-i18n';
import { Formula } from '../Formula';
import { Condition } from './Condition';

interface MathProps {
operator: MathOperatorType;
node: AstNode;
isRoot?: boolean;
}

export function Math({ operator, isRoot }: MathProps) {
export function Math({ node, isRoot }: MathProps) {
const getOperatorName = useGetOperatorName();

return (
<Condition.Container isRoot={isRoot}>
{operator.children.map((child, index) => {
{node.children?.map((child, index) => {
return (
<React.Fragment key={`${child.type}-${index}`}>
<React.Fragment key={`${child.name ?? 'constant'}-${index}`}>
{index !== 0 && (
<Condition.Item className="px-4" isRoot={isRoot}>
<MathOperator operatorType={operator.type} />
{getOperatorName(node.name ?? '')}
</Condition.Item>
)}
<Condition.Item isRoot={isRoot}>
Expand All @@ -32,57 +32,3 @@ export function Math({ operator, isRoot }: MathProps) {
</Condition.Container>
);
}

// Function instead of object mapping to handle possible translation (ex: "IS IN" operator)
export function useGetOperatorLabel() {
const { t } = useTranslation(scenarioI18n);

return useCallback(
(type: MathOperatorType['type']) => {
switch (type) {
case 'EQUAL_BOOL':
case 'EQUAL_FLOAT':
case 'EQUAL_STRING':
return '=';
// case 'NOT_EQUAL_BOOL':
// return '≠';
case 'AND':
case 'PRODUCT_FLOAT':
return '×';
case 'OR':
case 'SUM_FLOAT':
return '+';
case 'SUBTRACT_FLOAT':
return '−';
case 'DIVIDE_FLOAT':
return '÷';
case 'GREATER_FLOAT':
return '>';
case 'GREATER_OR_EQUAL_FLOAT':
return '≥';
case 'LESSER_FLOAT':
return '<';
case 'LESSER_OR_EQUAL_FLOAT':
return '≤';
case 'STRING_IS_IN_LIST':
return t('scenarios:operator.is_in');
default:
assertNever('unknwon Math operator :', type);
}
},
[t]
);
}

function MathOperator({
operatorType,
}: {
operatorType: MathOperatorType['type'];
}) {
const getOperatorLabel = useGetOperatorLabel();
return (
<span className="text-grey-100 font-semibold">
{getOperatorLabel(operatorType)}
</span>
);
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { type NotOperator } from '@marble-api';
import { type AstNode } from '@app-builder/models';

import { Formula } from '../Formula';
import { Condition } from './Condition';

interface NotProps {
operator: NotOperator;
node: AstNode;
isRoot?: boolean;
}
export function Not({ operator, isRoot }: NotProps) {
export function Not({ node, isRoot }: NotProps) {
return (
<Condition.Container isRoot={isRoot}>
<Condition.Item isRoot={isRoot}>
{!(<Formula formula={operator.children[0]} />)}
{!(<Formula formula={node.children[0]} />)}
</Condition.Item>
</Condition.Container>
);
Expand Down
Loading

0 comments on commit 1e75207

Please sign in to comment.