Skip to content

Commit

Permalink
fixed evaluation of dynamic filters
Browse files Browse the repository at this point in the history
datatables: added nullRepository
  • Loading branch information
ivan committed May 1, 2023
1 parent 7822105 commit d66ab27
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 29 deletions.
4 changes: 2 additions & 2 deletions shesha-reactjs/src/components/dataTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export const DataTable: FC<Partial<IIndexTableProps>> = ({
enabled: result.canAdd || result.canDelete || result.canEdit,
};
}, [props.canDeleteInline, props.canEditInline, props.canAddInline]);

const preparedColumns = useMemo(() => {
const localPreparedColumns = columns
.filter(column => {
Expand Down Expand Up @@ -185,7 +185,7 @@ export const DataTable: FC<Partial<IIndexTableProps>> = ({
};
return removeUndefinedProperties(column) as DataTableColumn;
});
//console.log('LOG: prepare columns in table', { columns, localPreparedColumns });

return localPreparedColumns;
}, [columns, crudOptions.enabled]);

Expand Down
5 changes: 4 additions & 1 deletion shesha-reactjs/src/providers/dataTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { withBackendRepository } from './repository/backendRepository';
import { withInMemoryRepository } from './repository/inMemoryRepository';
import { advancedFilter2JsonLogic, getTableDataColumns } from './utils';
import { useLocalStorage } from 'hooks';
import { withNullRepository } from './repository/nullRepository';

interface IDataTableProviderBaseProps {
/** Configurable columns. Is used in pair with entityType */
Expand Down Expand Up @@ -113,8 +114,10 @@ const getTableProviderComponent = (props: IDataTableProviderProps): FC<IDataTabl
};
// todo: check `url` and implement
// case 'Url': return null;
default: {
return withNullRepository(DataTableProviderWithRepository, { });
}
}
throw `Unsupported 'sourceType': '${sourceType}'`;
};

const getFilter = (state: IDataTableStateContext): string => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { IConfigurableColumnsProps } from "providers/datatableColumnsConfigurator/models";
import React, { ComponentType, useMemo } from "react";
import { FC } from "react";
import { DataTableColumnDto, IGetListDataPayload, ITableDataInternalResponse } from "../interfaces";
import { IHasRepository, IRepository } from "./interfaces";

export interface IWithNullRepositoryArgs {
value?: object;
}

const createRepository = (_args: IWithNullRepositoryArgs): IRepository => {
const fetch = (_payload: IGetListDataPayload): Promise<ITableDataInternalResponse> => {
return Promise.reject('NullRepository has no implementation');
};

const prepareColumns = (_configurableColumns: IConfigurableColumnsProps[]): Promise<DataTableColumnDto[]> => {
return Promise.reject('NullRepository has no implementation');
};

const performUpdate = (_rowIndex: number, _data: any): Promise<any> => {
return Promise.reject('NullRepository has no implementation');
};

const performDelete = (_rowIndex: number, _data: any): Promise<any> => {
return Promise.reject('NullRepository has no implementation');
};

const performCreate = (_rowIndex: number, _data: any): Promise<any> => {
return Promise.reject('NullRepository has no implementation');
};

const exportToExcel = (_payload: IGetListDataPayload): Promise<void> => {
return Promise.reject('NullRepository has no implementation');
};

const repository: IRepository = {
fetch,
exportToExcel,
prepareColumns,
performCreate,
performUpdate,
performDelete,
};
return repository;
};

export const useNullRepository = (args: IWithNullRepositoryArgs): IRepository => useMemo<IRepository>(() => createRepository(args), []);

export function withNullRepository<WrappedProps>(WrappedComponent: ComponentType<WrappedProps & IHasRepository>, args: IWithNullRepositoryArgs): FC<WrappedProps> {
const { value } = args;

return props => {
const repository = useNullRepository({ value });

return (<WrappedComponent {...props} repository={repository} />);
};
};

42 changes: 22 additions & 20 deletions shesha-reactjs/src/providers/dataTable/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,14 @@ import { ProperyDataType } from 'interfaces/metadata';

// Filters should read properties as camelCase ?:(
export const evaluateDynamicFilters = (filters: IStoredFilter[], mappings: IMatchData[]): IStoredFilter[] => {
console.log('LOG: evaluateDynamicFilters', { filters, mappings });

if (filters?.length === 0 || !mappings?.length) return filters;

return filters.map<IStoredFilter>(filter => {
/*
const expressionString = JSON.stringify(filter?.expression);
// todo: review. We MUST NOT process JsonLogic as string
// this code will be replaced with the code below
if (expressionString?.includes('{{')) {
const { result, success, unevaluatedExpressions } = evaluateComplexStringWithResult(expressionString, mappings);
return {
...filter,
expression: JSON.parse(result),
allFieldsEvaluatedSuccessfully: success,
unevaluatedExpressions,
};
}
*/
const convertedFilters = filters.map<IStoredFilter>(filter => {

// correct way of processing JsonLogic rules
if (typeof filter.expression === 'object') {
const evaluator = (operator: string, args: object[], argIndex: number): IArgumentEvaluationResult => {
console.log('LOG: evaluator', { operator, args, argIndex });

const argValue = args[argIndex];
// special handling for specifications
Expand All @@ -54,15 +37,34 @@ export const evaluateDynamicFilters = (filters: IStoredFilter[], mappings: IMatc

return { handled: false };
};
const convertedExpression = convertJsonLogicNode(filter.expression, { argumentEvaluator: evaluator, mappings });

const evaluationData = {
hasDynamicExpression: false,
allFieldsEvaluatedSuccessfully: true,
unevaluatedExpressions: [],
};

const convertedExpression = convertJsonLogicNode(filter.expression, {
argumentEvaluator: evaluator,
mappings,
onEvaluated: args => {
evaluationData.hasDynamicExpression = true;
evaluationData.allFieldsEvaluatedSuccessfully = evaluationData.allFieldsEvaluatedSuccessfully && args.success;
if (args.unevaluatedExpressions && args.unevaluatedExpressions.length)
evaluationData.unevaluatedExpressions.push(...args.unevaluatedExpressions);
}
});
return {
...filter,
...evaluationData,
expression: convertedExpression,
};
}

return filter;
});

return convertedFilters;
};

export const hasDynamicFilter = (filters: IStoredFilter[]) => {
Expand Down
2 changes: 1 addition & 1 deletion shesha-reactjs/src/providers/form/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ export const evaluateComplexString = (expression: string, mappings: IMatchData[]
return result;
};

interface IEvaluateComplexStringResult {
export interface IEvaluateComplexStringResult {
result: string;
unevaluatedExpressions?: string[];
success?: boolean;
Expand Down
21 changes: 16 additions & 5 deletions shesha-reactjs/src/utils/jsonLogic.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { evaluateComplexStringWithResult, IMatchData } from "./publicUtils";
import { evaluateComplexStringWithResult, IEvaluateComplexStringResult, IMatchData } from "./publicUtils";

type NodeCallback = (operator: string, args: object[]) => void;
const processRecursive = (jsonLogic: object, callback: NodeCallback) => {
Expand Down Expand Up @@ -42,22 +42,35 @@ export interface IArgumentEvaluationResult {
}
export type JsonLogicContainerProcessingCallback = (operator: string, args: object[], argIndex: number) => IArgumentEvaluationResult;


export interface OnEvaluatedArguments extends IEvaluateComplexStringResult {
expression: string;
}
export interface IJsonLogicConversionOptions {
argumentEvaluator: JsonLogicContainerProcessingCallback;
mappings: IMatchData[];
onEvaluated?: (args: OnEvaluatedArguments) => void;
}

export const convertJsonLogicNode = (jsonLogic: object, options: IJsonLogicConversionOptions): object => {
if (!jsonLogic)
return null;

const { argumentEvaluator, mappings } = options;
const { argumentEvaluator, mappings, onEvaluated: onEvaluation } = options;

const parseNestedNode = (node: object, nestedOptions: IJsonLogicConversionOptions): object | string => {
// special handling for evaluation nodes
const evaluationNodeParsing = tryParseAsEvaluationOperation(node);
if (evaluationNodeParsing.isEvaluationNode) {
const { result/*, success, unevaluatedExpressions*/ } = evaluateComplexStringWithResult(evaluationNodeParsing.evaluationArguments.expression, mappings);
const { result, success, unevaluatedExpressions } = evaluateComplexStringWithResult(evaluationNodeParsing.evaluationArguments.expression, mappings);

if (onEvaluation)
onEvaluation({
expression: evaluationNodeParsing.evaluationArguments.expression,
result,
success,
unevaluatedExpressions
});

return result;
} else
Expand All @@ -71,8 +84,6 @@ export const convertJsonLogicNode = (jsonLogic: object, options: IJsonLogicConve

const args = jsonLogic[operatorName];

console.log('LOG: convertJsonLogicNode', { operatorName, args, jsonLogic });

let convertedArgs = null;

if (Array.isArray(args)) {
Expand Down

0 comments on commit d66ab27

Please sign in to comment.