Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions src/components/custom-aggrid/cell-renderers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { isBlankOrEmpty } from 'components/utils/validation-functions';
import { ICellRendererParams } from 'ag-grid-community';
import { CustomCellRendererProps } from 'ag-grid-react';
import { mergeSx, type MuiStyles } from '@gridsuite/commons-ui';
import { IntlShape } from 'react-intl';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import type


const styles = {
tableCell: (theme) => ({
Expand All @@ -35,6 +36,17 @@ const styles = {
},
} as const satisfies MuiStyles;

const FORMULA_ERROR_KEY = 'spreadsheet/formula/error';

interface BaseCellRendererProps {
value: string | undefined;
tooltip?: string;
}

interface ErrorCellRendererParams extends ICellRendererParams {
intl: IntlShape;
}

export const BooleanCellRenderer = (props: any) => {
const isChecked = props.value;
return (
Expand Down Expand Up @@ -105,15 +117,23 @@ export const NumericCellRenderer = (props: NumericCellRendererProps) => {
);
};

const BaseCellRenderer = ({ value, tooltip }: BaseCellRendererProps) => (
<Box sx={mergeSx(styles.tableCell)}>
<Tooltip disableFocusListener disableTouchListener title={tooltip || value || ''}>
<Box sx={styles.overflow}>{value}</Box>
</Tooltip>
</Box>
);

export const ErrorCellRenderer = (props: ErrorCellRendererParams) => {
const errorMessage = props.intl.formatMessage({ id: props.value?.error });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are in react context here already, you can use useIntl directly you don't need to pass it through props

const errorValue = props.intl.formatMessage({ id: FORMULA_ERROR_KEY });
return <BaseCellRenderer value={errorValue} tooltip={errorMessage} />;
};

export const DefaultCellRenderer = (props: CustomCellRendererProps) => {
const cellValue = formatCell(props);
return (
<Box sx={mergeSx(styles.tableCell)}>
<Tooltip disableFocusListener disableTouchListener title={cellValue.value?.toString()}>
<Box sx={styles.overflow}>{cellValue.value?.toString()}</Box>
</Tooltip>
</Box>
);
const cellValue = formatCell(props).value?.toString();
return <BaseCellRenderer value={cellValue} />;
};

export const NetworkModificationNameCellRenderer = (props: CustomCellRendererProps) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ColumnMenuProps } from '../../spreadsheet-view/columns/column-menu';
import { SortParams } from '../hooks/use-custom-aggrid-sort';
import { COLUMN_TYPES, CustomCellType } from '../custom-aggrid-header.type';
import type { UUID } from 'node:crypto';
import { ValidationResult } from '../../spreadsheet-view/columns/utils/formula-validator';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import type


export enum FILTER_DATA_TYPES {
TEXT = 'text',
Expand Down Expand Up @@ -67,7 +68,7 @@ export interface ColumnContext<F extends CustomAggridFilterParams = CustomAggrid
}

export interface CustomColDef<TData = any, F extends CustomAggridFilterParams = CustomAggridFilterParams>
extends ColDef<TData, boolean | string | number | CustomCellType> {
extends ColDef<TData, boolean | string | number | CustomCellType | ValidationResult> {
colId: string;
context?: ColumnContext<F>;
}
24 changes: 20 additions & 4 deletions src/components/spreadsheet-view/columns/utils/column-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,22 @@ import {
numberColumnDefinition,
textColumnDefinition,
} from '../common-column-definitions';
import { validateFormulaResult } from './formula-validator';
import { isValidationError, validateFormulaResult, ValidationResult } from './formula-validator';
import { ColumnDefinition, SpreadsheetTabDefinition } from '../../types/spreadsheet.type';
import { CustomColDef } from '../../../custom-aggrid/custom-aggrid-filters/custom-aggrid-filter.type';
import { isCalculationRow } from '../../utils/calculation-utils';
import { ErrorCellRenderer } from '../../../custom-aggrid/cell-renderers';
import { IntlShape, useIntl } from 'react-intl';
import { useMemo } from 'react';

export const useColumnDefinitions = (tableDefinition: SpreadsheetTabDefinition) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is not useful

const intl = useIntl();
return useMemo(() => addFormulaErrorsRenderer(intl, mapColumns(tableDefinition)), [intl, tableDefinition]);
};

const createValueGetter =
(colDef: ColumnDefinition) =>
(params: ValueGetterParams): boolean | string | number | undefined => {
(params: ValueGetterParams): ValidationResult | boolean | string | number | undefined => {
try {
// Skip formula processing for pinned rows and use raw value
if (isCalculationRow(params.node?.data?.rowType)) {
Expand All @@ -37,15 +45,15 @@ const createValueGetter =
const validation = validateFormulaResult(result, colDef.type);

if (!validation.isValid) {
return undefined;
return validation;
}
return result;
} catch (e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could manage any "evaluate" error from MathJS in this catch. Next step ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Precisely :) I elaborated on this in the going further section or the summary

return undefined;
}
};

export const mapColumns = (tableDefinition: SpreadsheetTabDefinition) =>
const mapColumns = (tableDefinition: SpreadsheetTabDefinition) =>
tableDefinition?.columns.map((colDef): CustomColDef => {
let baseDefinition: ColDef;

Expand Down Expand Up @@ -87,3 +95,11 @@ export const mapColumns = (tableDefinition: SpreadsheetTabDefinition) =>
enableCellChangeFlash: true,
};
});

const addFormulaErrorsRenderer = (intl: IntlShape, columns: CustomColDef[]): CustomColDef[] => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you add it in mapColumns directly ?

return columns.map((col) => ({
...col,
cellRendererSelector: (params) =>
isValidationError(params.value) ? { component: ErrorCellRenderer, params: { intl } } : undefined, //Returning undefined make it so the originally defined renderer is used
}));
};
47 changes: 30 additions & 17 deletions src/components/spreadsheet-view/columns/utils/formula-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,47 @@
import { COLUMN_TYPES } from 'components/custom-aggrid/custom-aggrid-header.type';
import { MAX_FORMULA_CHARACTERS } from '../../constants';

interface ValidationResult {
export interface ValidationResult {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe name it ValidationError directly with
{
error: string;
}

isValid: boolean;
error?: string;
}

export function isValidationResult(value: unknown): value is ValidationResult {
return (
typeof value === 'object' && value !== null && value.hasOwnProperty('isValid') && value.hasOwnProperty('error')
);
}

export function isValidationError(value: unknown): value is ValidationResult {
return isValidationResult(value) && !value.isValid;
}

export const formatValidationResult = (isValid: boolean, messageId?: string): ValidationResult => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not really helpful

return { isValid: isValid, error: messageId };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return { isValid: isValid, error: messageId };
return { isValid, error: messageId };

};

export const validateFormulaResult = (value: any, type: COLUMN_TYPES): ValidationResult => {
if (isValidationResult(value)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this method it could be, if check passes returns the orginal value, if not returns a ValidationError

return value;
}

switch (type) {
case COLUMN_TYPES.NUMBER:
return {
isValid:
(typeof value === 'number' && !isNaN(value)) ||
(typeof value !== 'boolean' && !isNaN(Number(value))),
error: 'Formula must evaluate to a number',
};
return formatValidationResult(
(typeof value === 'number' && !isNaN(value)) || (typeof value !== 'boolean' && !isNaN(Number(value))),
'spreadsheet/formula/type/number'
);
case COLUMN_TYPES.BOOLEAN:
return {
isValid: typeof value === 'boolean',
error: 'Formula must evaluate to a boolean',
};
return formatValidationResult(typeof value === 'boolean', 'spreadsheet/formula/type/boolean');
case COLUMN_TYPES.ENUM:
return {
isValid: typeof value === 'string' || typeof value === 'number',
error: 'Formula must evaluate to a string',
};
return formatValidationResult(
typeof value === 'string' || typeof value === 'number',
'spreadsheet/formula/type/enum'
);
case COLUMN_TYPES.TEXT:
return { isValid: true }; // Text accepts any type
return formatValidationResult(true); // Text accepts any type
default:
return { isValid: false, error: 'Unknown column type' };
return formatValidationResult(false, 'spreadsheet/formula/type/unknown');
}
};

Expand Down
30 changes: 11 additions & 19 deletions src/components/spreadsheet-view/columns/utils/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,23 @@

import { all, create } from 'mathjs';
import { unitToKiloUnit, unitToMicroUnit } from '@gridsuite/commons-ui';
import { formatValidationResult } from './formula-validator';

const instance = create(all);

export const limitedEvaluate = instance.evaluate;

instance.import(
{
import: function () {
throw new Error('Function import is disabled');
},
createUnit: function () {
throw new Error('Function createUnit is disabled');
},
evaluate: function () {
throw new Error('Function evaluate is disabled');
},
parse: function () {
throw new Error('Function parse is disabled');
},
simplify: function () {
throw new Error('Function simplify is disabled');
},
derivative: function () {
throw new Error('Function derivative is disabled');
},
import: () => formatValidationResult(false, 'spreadsheet/formula/import/disabled'),
createUnit: () => formatValidationResult(false, 'spreadsheet/formula/createUnit/disabled'),
evaluate: () => formatValidationResult(false, 'spreadsheet/formula/evaluate/disabled'),
parse: () => formatValidationResult(false, 'spreadsheet/formula/parse/disabled'),
simplify: () => formatValidationResult(false, 'spreadsheet/formula/simplify/disabled'),
derivative: () => formatValidationResult(false, 'spreadsheet/formula/derivative/disabled'),
compile: () => formatValidationResult(false, 'spreadsheet/formula/compile/disabled'),
help: () => formatValidationResult(false, 'spreadsheet/formula/help/disabled'),
parser: () => formatValidationResult(false, 'spreadsheet/formula/parser/disabled'),
equal: function (a: any, b: any) {
// == instead of === to be able to compare strings to numbers
return a === b;
Expand All @@ -45,7 +37,7 @@ instance.import(
} else if (Array.isArray(obj)) {
return obj.length;
}
throw new Error('length() expects an array or object');
return formatValidationResult(false, 'spreadsheet/formula/length/error');
},
unitToKiloUnit,
unitToMicroUnit,
Expand Down
4 changes: 2 additions & 2 deletions src/components/spreadsheet-view/spreadsheet/spreadsheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { CurrentTreeNode } from 'components/graph/tree-node.type';
import { AgGridReact } from 'ag-grid-react';
import { SpreadsheetContent } from './spreadsheet-content/spreadsheet-content';
import { SpreadsheetToolbar } from './spreadsheet-toolbar/spreadsheet-toolbar';
import { mapColumns } from '../columns/utils/column-mapper';
import { useColumnDefinitions } from '../columns/utils/column-mapper';
import { DiagramType } from 'components/grid-layout/cards/diagrams/diagram.type';
import { useFilteredRowCounterInfo } from './spreadsheet-toolbar/row-counter/use-filtered-row-counter';

Expand All @@ -38,8 +38,8 @@ export const Spreadsheet = memo(
active,
}: SpreadsheetProps) => {
const gridRef = useRef<AgGridReact>(null);
const columnsDefinitions = useColumnDefinitions(tableDefinition);

const columnsDefinitions = useMemo(() => mapColumns(tableDefinition), [tableDefinition]);
const rowCounterInfos = useFilteredRowCounterInfo({
gridRef,
tableDefinition,
Expand Down
16 changes: 16 additions & 0 deletions src/translations/spreadsheet-en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,22 @@ const spreadsheetEn = {
'spreadsheet/calculation/min_abbrev': 'Min',
'spreadsheet/calculation/max_abbrev': 'Max',

//Formula errors
'spreadsheet/formula/error': '#ERROR',
'spreadsheet/formula/import/disabled': 'Function import is disabled',
'spreadsheet/formula/createUnit/disabled': 'Function createUnit is disabled',
'spreadsheet/formula/evaluate/disabled': 'Function evaluate is disabled',
'spreadsheet/formula/parse/disabled': 'Function parse is disabled',
'spreadsheet/formula/simplify/disabled': 'Function simplify is disabled',
'spreadsheet/formula/derivative/disabled': 'Function derivative is disabled',
'spreadsheet/formula/compile/disabled': 'Function compile is disabled',
'spreadsheet/formula/help/disabled': 'Function help is disabled',
'spreadsheet/formula/length/error': 'Function length expects an array or object',
'spreadsheet/formula/type/number': 'Formula must evaluate to a number',
'spreadsheet/formula/type/boolean': 'Formula must evaluate to a boolean',
'spreadsheet/formula/type/enum': 'Formula must evaluate to a string or a number',
'spreadsheet/formula/type/unknown': 'Unknown column type',

// Column types
TEXT: 'Text',
NUMBER: 'Number',
Expand Down
16 changes: 16 additions & 0 deletions src/translations/spreadsheet-fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,22 @@ const spreadsheetFr = {
'spreadsheet/calculation/min_abbrev': 'Min',
'spreadsheet/calculation/max_abbrev': 'Max',

//Formula errors
'spreadsheet/formula/error': '#ERREUR',
'spreadsheet/formula/import/disabled': 'La fonction import est désactivée',
'spreadsheet/formula/createUnit/disabled': 'La fonction createUnit est désactivée',
'spreadsheet/formula/evaluate/disabled': 'La fonction evaluate est désactivée',
'spreadsheet/formula/parse/disabled': 'La fonction parse est désactivée',
'spreadsheet/formula/simplify/disabled': 'La fonction simplify est désactivée',
'spreadsheet/formula/derivative/disabled': 'La fonction derivative est désactivée',
'spreadsheet/formula/compile/disabled': 'La fonction compile est désactivée',
'spreadsheet/formula/help/disabled': 'La fonction help est désactivée',
'spreadsheet/formula/length/error': 'La fonction length attend une donnée de type tableau ou objet',
'spreadsheet/formula/type/number': 'La formule doit exprimer une donnée numérique',
'spreadsheet/formula/type/boolean': 'La formule doit exprimer une donnée booléenne',
'spreadsheet/formula/type/enum': 'La formule doit exprimer une donnée textuelle ou numérique',
'spreadsheet/formula/type/unknown': 'Type de donnée inconnu',

// Column types
TEXT: 'Texte',
NUMBER: 'Nombre',
Expand Down
Loading