Skip to content

Commit

Permalink
Merge pull request #44 from bcgov/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
timwekkenbc authored Oct 28, 2024
2 parents 1349fe6 + 9f62f86 commit 15a8136
Show file tree
Hide file tree
Showing 24 changed files with 589 additions and 333 deletions.
9 changes: 1 addition & 8 deletions .github/workflows/eslint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,4 @@ jobs:
npm install [email protected] --no-strict-ssl --no-shrinkwrap
- name: Run ESLint
run: npm run lint:pipeline
continue-on-error: true

- name: Upload ESLint report
uses: actions/upload-artifact@v4
with:
name: eslint-report
path: eslint-report.html
run: npm run lint:pipeline
137 changes: 106 additions & 31 deletions app/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import { Table, Input, Button, Flex } from "antd";
import { ColumnsType } from "antd/es/table";
import { Table, Input, Button, Flex, Tooltip } from "antd";
import { ColumnsType, TablePaginationConfig } from "antd/es/table";
import { FilterValue } from "antd/es/table/interface";
import { HomeOutlined } from "@ant-design/icons";
import { RuleInfo, RuleInfoBasic } from "../types/ruleInfo";
import { getAllRuleData, postRuleData, updateRuleData, deleteRuleData } from "../utils/api";
Expand All @@ -13,35 +14,77 @@ enum ACTION_STATUS {
DELETE = "delete",
}

const PAGE_SIZE = 15;
interface TableParams {
pagination?: TablePaginationConfig;
searchTerm?: string;
}

export default function Admin() {
const [isLoading, setIsLoading] = useState(true);
const [initialRules, setInitialRules] = useState<RuleInfo[]>([]);
const [rules, setRules] = useState<RuleInfo[]>([]);
const [currentPage, setCurrentPage] = useState(0);
const [tableParams, setTableParams] = useState<TableParams>({
pagination: {
current: 1,
pageSize: 15,
total: 0,
},
searchTerm: "",
});

const getOrRefreshRuleList = async () => {
// Get rules that are already defined in the DB
const existingRules = await getAllRuleData();
setInitialRules(existingRules);
setRules(JSON.parse(JSON.stringify([...existingRules]))); // JSON.parse(JSON.stringify(data)) is a hacky way to deep copy the data - needed for comparison later
setIsLoading(false);
setIsLoading(true);
try {
const ruleData = await getAllRuleData({
page: tableParams.pagination?.current || 1,
pageSize: tableParams.pagination?.pageSize || 15,
searchTerm: tableParams.searchTerm || "",
});
const existingRules = ruleData?.data || [];
setInitialRules(existingRules);
setRules(JSON.parse(JSON.stringify([...existingRules])));
setTableParams({
...tableParams,
pagination: {
...tableParams.pagination,
total: ruleData?.total || 0,
},
});
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
}
};

useEffect(() => {
getOrRefreshRuleList();
}, []);
useEffect(
() => {
getOrRefreshRuleList();
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[JSON.stringify(tableParams)]
);

const updateRule = (e: React.ChangeEvent<HTMLInputElement>, index: number, property: keyof RuleInfoBasic) => {
const newRules = [...rules];
newRules[index][property] = e.target.value;
setRules(newRules);
};

const deleteRule = async (index: number) => {
const deletionIndex = (currentPage - 1) * PAGE_SIZE + index;
const newRules = [...rules.slice(0, deletionIndex), ...rules.slice(deletionIndex + 1, rules.length)];
const resetDraft = async (rule: RuleInfo) => {
setIsLoading(true);
try {
await updateRuleData(rule._id, rule);
} catch (error) {
console.error(`Error reseting draft for rule ${rule._id}: ${error}`);
} finally {
setIsLoading(false);
}
};

const deleteRule = (index: number) => {
const newRules = [...rules];
newRules.splice(index, 1);
setRules(newRules);
};

Expand All @@ -67,11 +110,6 @@ export default function Admin() {
return [...updatedEntries, ...deletedEntries];
};

const updateCurrPage = (page: number, pageSize: number) => {
// Keep track of current page so we can delete via splice properly
setCurrentPage(page);
};

// Save all rule updates to the API/DB
const saveAllRuleUpdates = async () => {
setIsLoading(true);
Expand Down Expand Up @@ -119,23 +157,56 @@ export default function Admin() {
{
dataIndex: "delete",
width: "60px",
render: (value: string, _: RuleInfo, index: number) => (
<Button danger onClick={() => deleteRule(index)}>
Delete
</Button>
),
render: (value: string, _: RuleInfo, index: number) => {
return (
<>
{_.isPublished ? (
<Tooltip title="To delete a published rule, please review the Github Rules Repo.">
<Button danger disabled={!_.ruleDraft} onClick={() => resetDraft(_)}>
Reset Draft
</Button>
</Tooltip>
) : (
<Tooltip title="Caution: Unpublished draft rules cannot be recovered once deleted.">
<Button danger onClick={() => deleteRule(index)}>
Delete Rule{" "}
</Button>
</Tooltip>
)}
</>
);
},
},
{
dataIndex: "view",
width: "60px",
render: (_: string, { _id }: RuleInfo) => (
<Link href={`/rule/${_id}`}>
<Button>View</Button>
</Link>
),
render: (_: string, { _id, isPublished }: RuleInfo) => {
const ruleLink = `/rule/${_id}`;
const draftLink = `${ruleLink}?version=draft`;
return (
<Link href={isPublished ? ruleLink : draftLink}>
<Button>View</Button>
</Link>
);
},
},
];

const handleTableChange = (pagination: TablePaginationConfig, filters: Record<string, FilterValue | null>) => {
setTableParams((prevParams) => ({
pagination,
filters,
searchTerm: prevParams.searchTerm,
}));
};
const handleSearch = (value: string) => {
setTableParams({
...tableParams,
searchTerm: value,
pagination: { ...tableParams.pagination, current: 1 },
});
};

return (
<>
<Flex justify="space-between" align="center">
Expand All @@ -149,12 +220,16 @@ export default function Admin() {
</Button>
)}
</Flex>
<Input.Search placeholder="Search rules..." onSearch={handleSearch} style={{ marginBottom: 16 }} allowClear />

{isLoading ? (
<p>Loading...</p>
) : (
<Table
columns={columns}
pagination={{ pageSize: PAGE_SIZE, onChange: updateCurrPage }}
pagination={tableParams.pagination}
onChange={handleTableChange}
loading={isLoading}
dataSource={rules.map((rule, key) => ({ key, ...rule }))}
/>
)}
Expand Down
14 changes: 9 additions & 5 deletions app/components/InputOutputTable/InputOutputTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { MinusCircleOutlined } from "@ant-design/icons";
import { RuleMap } from "@/app/types/rulemap";
import styles from "./InputOutputTable.module.css";
import { dollarFormat } from "@/app/utils/utils";
import FieldStyler from "../InputStyler/subcomponents/FieldStyler";

const COLUMNS = [
{
Expand Down Expand Up @@ -141,11 +142,14 @@ export default function InputOutputTable({
const newData = Object.entries(rawData)
.filter(([field]) => !PROPERTIES_TO_IGNORE.includes(field))
.sort(([propertyA], [propertyB]) => propertyA.localeCompare(propertyB))
.map(([field, value], index) => ({
field: propertyRuleMap?.find((item) => item.field === field)?.name || field,
value: convertAndStyleValue(value, field, editable),
key: index,
}));
.map(([field, value], index) => {
const propertyRule = propertyRuleMap?.find((item) => item.field === field);
return {
field: FieldStyler(propertyRule?.name || field, propertyRule?.description),
value: convertAndStyleValue(value, field, editable),
key: index,
};
});
setDataSource(newData);
const newColumns = COLUMNS.filter((column) => showColumn(newData, column.dataIndex));
setColumns(newColumns);
Expand Down
24 changes: 24 additions & 0 deletions app/components/InputStyler/subcomponents/FieldStyler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Tooltip, Popover } from "antd";
import { InfoCircleOutlined } from "@ant-design/icons";

export default function FieldStyler(fieldName: string, description?: string) {
const popOverInformation = (
<Popover placement="top" content={description} title={fieldName} trigger={"click"}>
<span>
{" "}
<InfoCircleOutlined />
</span>
</Popover>
);
const helpDialog = "View Description";
return (
<label>
{fieldName}
{description && (
<Tooltip title={helpDialog} placement="top">
{popOverInformation}
</Tooltip>
)}
</label>
);
}
15 changes: 14 additions & 1 deletion app/components/InputStyler/subcomponents/InputComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,20 @@ export const ReadOnlyBooleanDisplay = ({ show, value }: ReadOnlyProps) => {

export const ReadOnlyStringDisplay = ({ show, value }: ReadOnlyProps) => {
if (!show) return null;
return value.toString();
const stringList = value.split(",");
if (stringList.length > 1) {
return (
<>
{stringList.map((string: string, index: number) => (
<Tag color="blue" key={index}>
{string.trim()}
</Tag>
))}
</>
);
}

return <Tag color="blue">{value}</Tag>;
};

export const ReadOnlyNumberDisplay = ({ show, value, field }: ReadOnlyNumberDisplayProps) => {
Expand Down
8 changes: 5 additions & 3 deletions app/components/RuleManager/RuleManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { RuleMap } from "@/app/types/rulemap";
import { Scenario } from "@/app/types/scenario";
import useLeaveScreenPopup from "@/app/hooks/useLeaveScreenPopup";
import { DEFAULT_RULE_CONTENT } from "@/app/constants/defaultRuleContent";
import { RULE_VERSION } from "@/app/constants/ruleVersion";
import SavePublish from "../SavePublish";
import ScenariosManager from "../ScenariosManager";
import styles from "./RuleManager.module.css";
Expand Down Expand Up @@ -53,8 +54,8 @@ export default function RuleManager({
const [simulationContext, setSimulationContext] = useState<Record<string, any>>();
const [resultsOfSimulation, setResultsOfSimulation] = useState<Record<string, any> | null>();
const { setHasUnsavedChanges } = useLeaveScreenPopup();
const canEditGraph = editing === "draft" || editing === true;
const canEditScenarios = editing === "draft" || editing === "inreview" || editing === true;
const canEditGraph = editing === RULE_VERSION.draft || editing === true;
const canEditScenarios = editing === RULE_VERSION.draft || editing === RULE_VERSION.inReview || editing === true;

const updateRuleContent = (updatedRuleContent: DecisionGraphType) => {
if (ruleContent !== updatedRuleContent) {
Expand Down Expand Up @@ -102,6 +103,7 @@ export default function RuleManager({
if (runContext) {
console.info("Simulate:", runContext);
try {
message.destroy();
const data = await postDecision(ruleContent, runContext);
console.info("Simulation Results:", data, data?.result);
// Check if data.result is an array and throw error as object is required
Expand All @@ -113,7 +115,7 @@ export default function RuleManager({
// Set the results of the simulation
setResultsOfSimulation(data?.result);
} catch (e: any) {
message.error("Error during simulation run: " + e);
message.error("Error during simulation run: " + e, 10);
console.error("Error during simulation run:", e);
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function LinkRuleComponent({ specification, id, isSelected, name,
const getRuleOptions = async () => {
const ruleData = await getAllRuleData();
setRuleOptions(
ruleData.map(({ title, filepath }) => ({
ruleData?.data.map(({ title, filepath }) => ({
label: title || filepath,
value: filepath,
}))
Expand Down
Loading

0 comments on commit 15a8136

Please sign in to comment.