Skip to content

Commit

Permalink
Merge pull request #63 from bcgov/feature/range-generation
Browse files Browse the repository at this point in the history
Add isolation tester range inputs and update linked rule input handling.
  • Loading branch information
brysonjbest authored Jan 30, 2025
2 parents bd6b1ef + adaca6a commit 3b4a808
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 45 deletions.
142 changes: 108 additions & 34 deletions app/components/InputStyler/InputStyler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
NumberInput,
TextInput,
} from "./subcomponents/InputComponents";
import { Flex } from "antd";

export interface rawDataProps {
[key: string]: any;
Expand Down Expand Up @@ -78,7 +79,8 @@ export default function InputStyler(
scenarios: Scenario[] = [],
rawData: rawDataProps | null | undefined,
setRawData: any,
ruleProperties: any
ruleProperties: any,
range?: boolean
) {
const updateFieldValue = (field: string, value: any) => {
const updatedData = { ...rawData, [field]: value };
Expand All @@ -89,31 +91,59 @@ export default function InputStyler(
}
};

const handleValueChange = (value: any, field: string) => {
let queryValue: any = value;
const processValue = (value: string | number | boolean): string | number | boolean => {
if (typeof value === "string") {
if (value === "") queryValue = "";
else if (value.toLowerCase() === "true") queryValue = true;
else if (value.toLowerCase() === "false") queryValue = false;
else if (!isNaN(Number(value))) queryValue = Number(value);
const lowerValue = value.toLowerCase();
if (lowerValue === "true") return true;
if (lowerValue === "false") return false;
if (!isNaN(Number(value))) return Number(value);
}
return value;
};

const handleValueChange = (value: string | number | boolean, field: string) => {
const queryValue = processValue(value);
updateFieldValue(field, queryValue);
};

const handleClear = (field: any) => {
const inputElement = document.getElementById(field) as any;
const handleInputChange = (value: any, field: string) => {
updateFieldValue(field, value);
};

const handleRangeValueChange = (value: any, field: string, rangeType: "minValue" | "maxValue") => {
let queryValue = processValue(value);
const currentValue =
typeof rawData?.[field] === "object" ? { ...rawData[field] } : { minValue: null, maxValue: null };
currentValue[rangeType] = queryValue;
updateFieldValue(field, currentValue);
};

const handleRangeInputChange = (value: any, field: string, rangeType: "minValue" | "maxValue") => {
const currentValue = rawData?.[field] || {};
if (value === null || value === undefined) {
delete currentValue[rangeType];
updateFieldValue(field, Object.keys(currentValue).length === 0 ? undefined : currentValue);
} else {
currentValue[rangeType] = value;
updateFieldValue(field, currentValue);
}
};

const handleClear = (field: any, rangeType?: "minValue" | "maxValue") => {
const inputElement = document.getElementById(rangeType ? `${field}-${rangeType}` : field) as any;

if (inputElement) {
inputElement.value = null;
inputElement.dispatchEvent(new Event("input", { bubbles: true }));
}

handleValueChange(null, field);
};

const handleInputChange = (value: any, field: string) => {
updateFieldValue(field, value);
if (range && rangeType) {
const currentValue = { ...rawData?.[field] };
delete currentValue[rangeType];
updateFieldValue(field, Object.keys(currentValue).length === 0 ? undefined : currentValue);
} else {
updateFieldValue(field, undefined);
}
};

const valuesArray = getAutoCompleteOptions(field, scenarios);
Expand Down Expand Up @@ -179,34 +209,78 @@ export default function InputStyler(
value={value}
field={field}
valuesArray={valuesArray}
handleValueChange={handleValueChange}
handleInputChange={handleInputChange}
handleValueChange={(val: any) => handleValueChange(val, field)}
handleInputChange={(val: any) => handleInputChange(val, field)}
handleClear={handleClear}
/>
);
case "number":
return (
<NumberInput
show={validationRules?.type === "number"}
value={value}
field={field}
maximum={validationRules?.range ? validationRules?.range.max : validationRules?.max}
minimum={validationRules?.range ? validationRules?.range.min : validationRules?.min}
handleValueChange={handleValueChange}
handleInputChange={handleInputChange}
/>
<Flex gap="small" align="center" justify="space-between">
<Flex gap="small" vertical>
{range && <label>Minimum</label>}
<NumberInput
show={validationRules?.type === "number"}
value={range ? value?.minValue ?? null : value}
field={field}
maximum={validationRules?.range ? validationRules?.range.max : validationRules?.max}
minimum={validationRules?.range ? validationRules?.range.min : validationRules?.min}
handleValueChange={(val: any) =>
range ? handleRangeValueChange(val, field, "minValue") : handleValueChange(val, field)
}
handleInputChange={(val: any) =>
range ? handleRangeInputChange(val, field, "minValue") : handleInputChange(val, field)
}
/>
</Flex>
{range && (
<Flex gap="small" vertical>
<label>Maximum</label>
<NumberInput
show={validationRules?.type === "number"}
value={value?.maxValue ?? null}
field={field}
maximum={validationRules?.range ? validationRules?.range.max : validationRules?.max}
minimum={validationRules?.range ? validationRules?.range.min : validationRules?.min}
handleValueChange={(val: any) => handleRangeValueChange(val, field, "maxValue")}
handleInputChange={(val: any) => handleRangeInputChange(val, field, "maxValue")}
/>
</Flex>
)}
</Flex>
);
case "date":
return (
<DateInput
show={validationRules?.type === "date"}
value={value}
field={field}
maximum={validationRules?.range ? validationRules?.range.max : validationRules?.max}
minimum={validationRules?.range ? validationRules?.range.min : validationRules?.min}
handleInputChange={handleInputChange}
handleClear={handleClear}
/>
<Flex gap="small" align="center" justify="space-between">
<Flex gap="small" vertical>
{range && <label>Minimum</label>}
<DateInput
show={validationRules?.type === "date"}
value={range ? value?.minValue ?? null : value}
field={field}
maximum={validationRules?.range ? validationRules?.range.max : validationRules?.max}
minimum={validationRules?.range ? validationRules?.range.min : validationRules?.min}
handleInputChange={(val: any) =>
range ? handleRangeInputChange(val, field, "minValue") : handleInputChange(val, field)
}
handleClear={() => handleClear(field, range ? "minValue" : undefined)}
/>
</Flex>
{range && (
<Flex gap="small" vertical>
<label>Maximum</label>
<DateInput
show={validationRules?.type === "date"}
value={value?.maxValue ?? null}
field={field}
maximum={validationRules?.range ? validationRules?.range.max : validationRules?.max}
minimum={validationRules?.range ? validationRules?.range.min : validationRules?.min}
handleInputChange={(val: any) => handleRangeInputChange(val, field, "maxValue")}
handleClear={() => handleClear(field, "maxValue")}
/>
</Flex>
)}
</Flex>
);
default:
return (
Expand Down
5 changes: 5 additions & 0 deletions app/components/RuleManager/RuleManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export default function RuleManager({
const [scenarios, setScenarios] = useState<Scenario[]>([]);
const [ruleContent, setRuleContent] = useState<DecisionGraphType>();
const [rulemap, setRulemap] = useState<RuleMap>();
const [nestedRuleMap, setNestedRuleMap] = useState<RuleMap>();
const [simulation, setSimulation] = useState<Simulation>();
const [simulationContext, setSimulationContext] = useState<Record<string, any>>();
const [resultsOfSimulation, setResultsOfSimulation] = useState<Record<string, any> | null>();
Expand Down Expand Up @@ -84,6 +85,9 @@ export default function RuleManager({
};
const updateRuleMap = async () => {
const updatedRulemap: RuleMap = await getRuleMap(jsonFile, ruleContent);
setNestedRuleMap(updatedRulemap);
// Exclude inputs from linked rules
updatedRulemap.inputs = updatedRulemap.inputs.filter((input) => !input.nested);
setRulemap(updatedRulemap);
const ruleMapInputs = createRuleMap(updatedRulemap?.inputs, simulationContext);
setSimulationContext(ruleMapInputs);
Expand Down Expand Up @@ -162,6 +166,7 @@ export default function RuleManager({
ruleContent={ruleContent}
version={version}
setHasSaved={() => setHasUnsavedChanges(false)}
ruleMap={nestedRuleMap}
/>
</Flex>
)}
Expand Down
6 changes: 4 additions & 2 deletions app/components/SavePublish/SavePublish.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ import NewReviewForm from "./NewReviewForm";
import SavePublishWarnings from "./SavePublishWarnings";
import styles from "./SavePublish.module.css";
import RuleMapHelper from "./RuleMapHelper";
import { RuleMap } from "@/app/types/rulemap";

interface SavePublishProps {
ruleInfo: RuleInfo;
ruleContent: DecisionGraphType;
setHasSaved: () => void;
version?: string | boolean;
ruleMap?: RuleMap;
}

export default function SavePublish({ ruleInfo, ruleContent, setHasSaved, version }: SavePublishProps) {
export default function SavePublish({ ruleInfo, ruleContent, setHasSaved, version, ruleMap }: SavePublishProps) {
const { _id: ruleId, filepath: filePath, reviewBranch } = ruleInfo;

const { message } = App.useApp();
Expand Down Expand Up @@ -141,7 +143,7 @@ export default function SavePublish({ ruleInfo, ruleContent, setHasSaved, versio
</Flex>
)}
</Flex>
<SavePublishWarnings filePath={filePath} ruleContent={ruleContent} isSaving={isSaving} />
<SavePublishWarnings filePath={filePath} ruleContent={ruleContent} isSaving={isSaving} ruleMap={ruleMap} />
</>
);
}
17 changes: 11 additions & 6 deletions app/components/SavePublish/SavePublishWarnings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,34 @@ import { WarningFilled } from "@ant-design/icons";
import { DecisionGraphType } from "@gorules/jdm-editor";
import { getRuleMap, generateSchemaFromRuleContent } from "@/app/utils/api";
import styles from "./SavePublish.module.css";
import { RuleMap } from "@/app/types/rulemap";

interface SavePublishProps {
filePath: string;
ruleContent: DecisionGraphType;
isSaving: boolean;
ruleMap?: RuleMap;
}

interface MisconnectedField {
field: string;
describer: string;
}

export default function SavePublishWarnings({ filePath, ruleContent, isSaving }: SavePublishProps) {
export default function SavePublishWarnings({ filePath, ruleContent, isSaving, ruleMap }: SavePublishProps) {
const { notification } = App.useApp();
const [misconnectedFields, setMisconnectedFields] = useState<MisconnectedField[]>([]);
const [misconnectedFieldsPanelOpen, setMisconnectedFieldsPanelOpen] = useState(false);

const getFieldsFromSchema = (schema: { field?: string; nested?: string | boolean }[]) =>
schema.filter((item) => !item.nested && item.field).map(({ field }) => field as string);

const getMisconnectedFields = async () => {
// TODO: Move these API calls locally to reduce strain on server (if this solution is working well)
// Get map from input/output nodes
const inputOutputSchemaMap = await getRuleMap(filePath, ruleContent);
const existingKlammInputs = inputOutputSchemaMap.inputs.map(({ field }) => field as string);
const existingKlammOutputs = inputOutputSchemaMap.resultOutputs.map(({ field }) => field as string);
// exludes the inputs and outputs from linked rules
const inputOutputSchemaMap = ruleMap ?? (await getRuleMap(filePath, ruleContent));
const existingKlammInputs = getFieldsFromSchema(inputOutputSchemaMap.inputs);
const existingKlammOutputs = getFieldsFromSchema(inputOutputSchemaMap.resultOutputs);

if (!ruleContent?.nodes || ruleContent.nodes.length < 2) return;

Expand Down Expand Up @@ -82,7 +87,7 @@ export default function SavePublishWarnings({ filePath, ruleContent, isSaving }:
useEffect(() => {
getMisconnectedFields();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ruleContent]);
}, [ruleContent, ruleMap]);

useEffect(() => {
if (isSaving) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ export default function IsolationTester({
</li>
<li>
Define any variables you would like to remain unchanged. The more that you define, the more specific the
tests will be.
tests will be. Top level variable numbers and dates are entered as ranges. If you would like only one
specific number or date, enter the same value for both the minimum and maximum.
<Collapse
items={[
{
Expand All @@ -77,6 +78,7 @@ export default function IsolationTester({
setRawData={(data) => setSimulationContext(data)}
scenarios={scenarios}
rulemap={rulemap}
range
/>
</Flex>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,17 @@ interface ScenarioFormatterProps {
setRawData?: (data: object) => void;
scenarios?: Scenario[];
rulemap: RuleMap;
range?: boolean;
}

export default function ScenarioFormatter({ title, rawData, setRawData, scenarios, rulemap }: ScenarioFormatterProps) {
export default function ScenarioFormatter({
title,
rawData,
setRawData,
scenarios,
rulemap,
range,
}: ScenarioFormatterProps) {
const [dataSource, setDataSource] = useState<object[]>([]);
const [columns, setColumns] = useState(COLUMNS);
const [showTable, setShowTable] = useState(true);
Expand Down Expand Up @@ -74,7 +82,8 @@ export default function ScenarioFormatter({ title, rawData, setRawData, scenario
scenarios,
updatedRawData,
setRawData,
rulemap?.inputs.find((item) => item.field === field)
rulemap?.inputs.find((item) => item.field === field),
range
),
key: index,
};
Expand Down
1 change: 1 addition & 0 deletions app/types/scenario.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export interface Variable {
value: any;
type?: string;
field?: string;
nested?: string | boolean;
}

0 comments on commit 3b4a808

Please sign in to comment.