diff --git a/src/card/settings/CardSettingsFooter.tsx b/src/card/settings/CardSettingsFooter.tsx index 2ce9f79d8..25b33e638 100644 --- a/src/card/settings/CardSettingsFooter.tsx +++ b/src/card/settings/CardSettingsFooter.tsx @@ -139,6 +139,7 @@ const NeoCardSettingsFooter = ({ settingValue={reportSettings[actionsToCustomize]} type={type} fields={fields} + preConditionsSetting={reportSettings?.preConditions} customReportActionsModalOpen={customReportActionsModalOpen} setCustomReportActionsModalOpen={setCustomReportActionsModalOpen} onReportSettingUpdate={onReportSettingUpdate} diff --git a/src/chart/table/TableActionsHelper.ts b/src/chart/table/TableActionsHelper.ts index 1dc3a4508..fa1cc8d04 100644 --- a/src/chart/table/TableActionsHelper.ts +++ b/src/chart/table/TableActionsHelper.ts @@ -33,10 +33,58 @@ export const getCheckboxes = (actionsRules, rows, getGlobalParameter) => { return [...new Set(selection)]; }; +export const hasPreCondition = (preConditions) => { + return preConditions.length > 0; +}; + +const groupConditionsByField = (conditions) => { + return conditions.reduce((acc, condition) => { + if (!acc[condition.field]) { + acc[condition.field] = []; + } + acc[condition.field].push(condition); + return acc; + }, {}); +}; + +const evaluateGroupedConditions = (groupedConditions, row) => { + return Object.keys(groupedConditions).every((field) => { + // Logical OR between conditions for the same field + return groupedConditions[field].some((condition) => evaluateCondition(condition, row)); + }); +}; + +export const convertConditionsToExpression = (conditions, row) => { + const groupedConditions = groupConditionsByField(conditions); + return !evaluateGroupedConditions(groupedConditions, row); +}; + +const evaluateCondition = (condition, row) => { + let fieldValue = row[condition.field]; + + // Handle Neo4j integer format + if (fieldValue && typeof fieldValue === 'object' && 'low' in fieldValue && 'high' in fieldValue) { + // Assuming we only care about the 'low' value for comparisons + fieldValue = String(fieldValue.low); + } + + switch (condition.condition) { + case '=': + return fieldValue === condition.value; + case '!=': + return fieldValue !== condition.value; + case 'contains': + return typeof fieldValue === 'string' && fieldValue.includes(condition.value); + case 'not_contains': + return typeof fieldValue === 'string' && !fieldValue.includes(condition.value); + default: + return false; + } +}; + export const updateCheckBoxes = (actionsRules, rows, selection, setGlobalParameter) => { if (hasCheckboxes(actionsRules)) { const selectedRows = rows.filter((row) => selection.includes(row.id)); - console.log(selectedRows); let rules = actionsRules.filter((rule) => rule.condition && rule.condition == 'rowCheck'); rules.forEach((rule) => { const parameterValues = selectedRows.map((row) => row[rule.value]).filter((v) => v !== undefined); diff --git a/src/chart/table/TableChart.tsx b/src/chart/table/TableChart.tsx index 57e579207..e62695b8f 100644 --- a/src/chart/table/TableChart.tsx +++ b/src/chart/table/TableChart.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { DataGrid, GridColumnVisibilityModel } from '@mui/x-data-grid'; +import { DataGrid, GridColumnVisibilityModel, GridRowId } from '@mui/x-data-grid'; import { ChartProps } from '../Chart'; import { evaluateRulesOnDict, @@ -23,7 +23,13 @@ import { CloudArrowDownIconOutline, XMarkIconOutline } from '@neo4j-ndl/react/ic import { ThemeProvider, createTheme } from '@mui/material/styles'; import Button from '@mui/material/Button'; import { extensionEnabled } from '../../utils/ReportUtils'; -import { getCheckboxes, hasCheckboxes, updateCheckBoxes } from './TableActionsHelper'; +import { + convertConditionsToExpression, + getCheckboxes, + hasCheckboxes, + hasPreCondition, + updateCheckBoxes, +} from './TableActionsHelper'; const TABLE_ROW_HEIGHT = 52; const HIDDEN_COLUMN_PREFIX = '__'; @@ -78,6 +84,12 @@ export const NeoTableChart = (props: ChartProps) => { extensionEnabled(props.extensions, 'actions') && props.settings && props.settings.actionsRules ? props.settings.actionsRules : []; + + const preConditions = + extensionEnabled(props.extensions, 'actions') && props.settings && props.settings.preConditions + ? props.settings.preConditions + : []; + const compact = props.settings && props.settings.compact !== undefined ? props.settings.compact : false; const styleRules = useStyleRules( extensionEnabled(props.extensions, 'styling'), @@ -253,7 +265,9 @@ export const NeoTableChart = (props: ChartProps) => { for (const [index, rule] of styleRules.entries()) { if (rule.targetField) { if (rule.targetField === params.field) { - trueRule = `rule${evaluateSingleRuleOnDict({ [rule.field]: params.row[rule.field] }, rule, index, [e])}`; + trueRule = `rule${evaluateSingleRuleOnDict({ [rule.field]: params.row[rule.field] }, rule, index, [ + e, + ])}`; } } else { trueRule = `rule${evaluateSingleRuleOnDict({ [params.field]: params.value }, rule, index, [e])}`; @@ -266,6 +280,13 @@ export const NeoTableChart = (props: ChartProps) => { }, }; + const isRowSelectable = (params: { id: GridRowId; row: any }) => { + if (hasPreCondition(preConditions)) { + return convertConditionsToExpression(preConditions, params.row); + } + return true; + }; + return (
@@ -314,13 +335,14 @@ export const NeoTableChart = (props: ChartProps) => { 'auto'} + isRowSelectable={isRowSelectable} sx={{ ...customStyles, '&.MuiDataGrid-root .MuiDataGrid-cell': { wordBreak: 'break-word' }, }} /> ) : ( - + )}
diff --git a/src/extensions/actions/ActionsRuleCreationModal.tsx b/src/extensions/actions/ActionsRuleCreationModal.tsx index 48d6486ad..a59586334 100644 --- a/src/extensions/actions/ActionsRuleCreationModal.tsx +++ b/src/extensions/actions/ActionsRuleCreationModal.tsx @@ -10,6 +10,33 @@ import { getPageNumbersAndNamesList } from '../advancedcharts/Utils'; import { IconButton, Button, Dialog, Dropdown, TextInput } from '@neo4j-ndl/react'; import { Autocomplete, TextField } from '@mui/material'; +// Pre conditions + +const PRE_CONDITIONS_RULES = [ + { + value: '=', + label: '=', + }, + { + value: '!=', + label: '!=', + }, + { + value: 'contains', + label: 'contains', + }, + { + value: 'not_contains', + label: 'not_contains', + }, +]; + +const defaultPreCondition = { + condition: '=', + field: '', + value: '', +}; + // The set of conditional checks that are included in the rule specification. const RULE_CONDITIONS = { table: [ @@ -166,12 +193,17 @@ export const NeoCustomReportActionsModal = ({ fields, setCustomReportActionsModalOpen, onReportSettingUpdate, + preConditionsSetting, }) => { // The rule set defined in this modal is updated whenever the setting value is externally changed. const [rules, setRules] = React.useState([]); + const [preConditions, setPreConditions] = React.useState([defaultPreCondition]); useEffect(() => { if (settingValue) { setRules(settingValue); + if (preConditionsSetting) { + setPreConditions(preConditionsSetting); + } } }, [settingValue]); @@ -183,6 +215,12 @@ export const NeoCustomReportActionsModal = ({ } else { onReportSettingUpdate(settingName, rules); } + + if (preConditions.length === 0) { + onReportSettingUpdate('preConditions', undefined); + } else { + onReportSettingUpdate('preConditions', preConditions); + } setCustomReportActionsModalOpen(false); }; @@ -193,6 +231,10 @@ export const NeoCustomReportActionsModal = ({ setRules(newRules); }; + const updatePreConditionFieldById = (j, field, value) => { + setPreConditions((prevItems) => prevItems.map((item, i) => (i === j ? { ...item, [field]: value } : item))); + }; + /** * Create the list of suggestions used in the autocomplete box of the rule specification window. * This will be dynamic based on the type of report we are customizing. @@ -535,6 +577,115 @@ export const NeoCustomReportActionsModal = ({ + {rules.some((rule) => rule?.condition === 'rowCheck') && ( + + + + + + + + {preConditions.map((con, i) => { + return ( + + + + + + + ); + })} + + + + +
+
+ Report Pre Conditions +
+ {i + 1}. + IF + +
+ + e.toLowerCase().includes(con.field.toLowerCase()) + )} + value={con.field ? con.field : ''} + inputValue={con.field ? con.field : ''} + popupIcon={<>} + style={{ minWidth: 125 }} + onInputChange={(event, value) => { + updatePreConditionFieldById(i, 'field', value); + }} + onChange={(event, newValue) => { + updatePreConditionFieldById(i, 'field', newValue); + }} + renderInput={(params) => ( + + )} + /> + updatePreConditionFieldById(i, 'condition', newValue?.value), + options: PRE_CONDITIONS_RULES.map((option) => ({ + label: option.label, + value: option.value, + })), + value: { label: con.condition, value: con.condition }, + }} + style={{ minWidth: 70, display: 'inline-block' }} + fluid + /> + updatePreConditionFieldById(i, 'value', e.target.value)} + fluid + > +
+
+ { + setPreConditions((prevItems) => prevItems.filter((_, j) => j !== i)); + }} + > + + +
+
+ { + setPreConditions([...preConditions, defaultPreCondition]); + }} + > + + +
+
+ )}