From 7765c0f930a674bac320cc49bbae948c9afff75a Mon Sep 17 00:00:00 2001 From: gerrycampion <85252124+gerrycampion@users.noreply.github.com> Date: Thu, 28 Mar 2024 11:01:48 -0400 Subject: [PATCH] Ability to export rules as YAML (#214) * Export rules yaml working * Use a generic ControlButton * Fixed linting errors --- src/components/Controls/Controls.tsx | 180 ++++++------------ src/components/Controls/ExportArtifacts.tsx | 60 ++++++ .../{ExportRules.tsx => ExportRulesCSV.tsx} | 12 +- src/components/Controls/ExportRulesYAML.tsx | 93 +++++++++ 4 files changed, 222 insertions(+), 123 deletions(-) create mode 100644 src/components/Controls/ExportArtifacts.tsx rename src/components/Controls/{ExportRules.tsx => ExportRulesCSV.tsx} (91%) create mode 100644 src/components/Controls/ExportRulesYAML.tsx diff --git a/src/components/Controls/Controls.tsx b/src/components/Controls/Controls.tsx index 263ada9..b4e6ca5 100644 --- a/src/components/Controls/Controls.tsx +++ b/src/components/Controls/Controls.tsx @@ -1,7 +1,7 @@ -import { useContext, useState } from "react"; +import { useContext, useState, MouseEvent } from "react"; import AppContext from "../AppContext"; import PromptDialog from "../PromptDialog/PromptDialog"; -import { IconButton, Toolbar, Tooltip } from "@mui/material"; +import { Menu, Toolbar } from "@mui/material"; import AddIcon from "@mui/icons-material/Add"; import SaveIcon from "@mui/icons-material/Save"; import RestoreIcon from "@mui/icons-material/Restore"; @@ -10,9 +10,10 @@ import PublishIcon from "@mui/icons-material/Publish"; import FileDownloadIcon from "@mui/icons-material/FileDownload"; import QuickSearchToolbar from "../QuickSearchToolbar/QuickSearchToolbar"; import jsYaml from "js-yaml"; -import JSZip from "jszip"; -import { saveAs } from "file-saver"; -import ExportRules from "./ExportRules"; +import ExportRulesCSV from "./ExportRulesCSV"; +import ExportArtifacts from "./ExportArtifacts"; +import ExportRulesYAML from "./ExportRulesYAML"; +import ControlButton from "./ControlButton"; export default function Controls() { const [discardDialog, setDiscardDialog] = useState(false); @@ -31,12 +32,6 @@ export default function Controls() { isRuleDirty, setAlertState, isRuleModifiable, - syntaxCheck, - schemaCheck, - jsonCheck, - loadDefineXMLCheck, - loadDatasetsCheck, - testCheck, } = useContext(AppContext); const newRule = () => { @@ -97,39 +92,15 @@ export default function Controls() { } }; - const exportArtifacts = async () => { - const zip = new JSZip(); - zip.file("Rule.yml", modifiedRule); - zip.file( - "Rule_spaces.json", - JSON.stringify(syntaxCheck.details[0].details, null, 4) - ); - zip.file( - "Schema_Validation.json", - JSON.stringify(schemaCheck.details[0].details, null, 4) - ); - zip.file( - "Rule_underscores.json", - JSON.stringify(jsonCheck.details[0].details, null, 4) - ); - zip.file("Define.xml", loadDefineXMLCheck.details[1]?.details ?? ""); - zip.file("Datasets.xlsx", loadDatasetsCheck.details[0]?.details ?? ""); - zip.file( - "Datasets.json", - JSON.stringify(loadDatasetsCheck.details[1]?.details ?? "", null, 4) - ); - zip.file( - "Request.json", - JSON.stringify(testCheck.details[1]?.details ?? "", null, 4) - ); - zip.file( - "Results.json", - JSON.stringify(testCheck.details[3]?.details ?? "", null, 4) - ); - - zip.generateAsync({ type: "blob" }).then((content) => { - saveAs(content, "Rule.zip"); - }); + const [exportAnchorEl, setExportAnchorEl] = useState( + null + ); + const open = Boolean(exportAnchorEl); + const handleExport = (event: MouseEvent) => { + setExportAnchorEl(event.currentTarget); + }; + const handleExportClose = () => { + setExportAnchorEl(null); }; return ( @@ -141,79 +112,54 @@ export default function Controls() { bgcolor: "#DDEEFF", }} > - - - - - - - - - - - - - - - - - - - setDiscardDialog(true)} - color="primary" - > - - - - - - - - setDeleteDialog(true)} - color="primary" - > - - - - - - - - - - - - - - - - - - - - - - + + + + + + + setDiscardDialog(true)} + > + + + setDeleteDialog(true)} + > + + + + + + + + + + + + + diff --git a/src/components/Controls/ExportArtifacts.tsx b/src/components/Controls/ExportArtifacts.tsx new file mode 100644 index 0000000..af57b1a --- /dev/null +++ b/src/components/Controls/ExportArtifacts.tsx @@ -0,0 +1,60 @@ +import { useContext,} from "react"; +import AppContext from "../AppContext"; +import JSZip from "jszip"; +import { saveAs } from "file-saver"; +import { MenuItem } from "@mui/material"; + +export default function ExportArtifacts({ + onClose + }) { + const { + modifiedRule, + syntaxCheck, + schemaCheck, + jsonCheck, + loadDefineXMLCheck, + loadDatasetsCheck, + testCheck, + } = useContext(AppContext); + + const exportArtifacts = async () => { + const zip = new JSZip(); + zip.file("Rule.yml", modifiedRule); + zip.file( + "Rule_spaces.json", + JSON.stringify(syntaxCheck.details[0].details, null, 4) + ); + zip.file( + "Schema_Validation.json", + JSON.stringify(schemaCheck.details[0].details, null, 4) + ); + zip.file( + "Rule_underscores.json", + JSON.stringify(jsonCheck.details[0].details, null, 4) + ); + zip.file("Define.xml", loadDefineXMLCheck.details[1]?.details ?? ""); + zip.file("Datasets.xlsx", loadDatasetsCheck.details[0]?.details ?? ""); + zip.file( + "Datasets.json", + JSON.stringify(loadDatasetsCheck.details[1]?.details ?? "", null, 4) + ); + zip.file( + "Request.json", + JSON.stringify(testCheck.details[1]?.details ?? "", null, 4) + ); + zip.file( + "Results.json", + JSON.stringify(testCheck.details[3]?.details ?? "", null, 4) + ); + + zip.generateAsync({ type: "blob" }).then((content) => { + saveAs(content, "Rule.zip"); + }); + + onClose(); + }; + return ( + Export debugging artifacts + ); + + } diff --git a/src/components/Controls/ExportRules.tsx b/src/components/Controls/ExportRulesCSV.tsx similarity index 91% rename from src/components/Controls/ExportRules.tsx rename to src/components/Controls/ExportRulesCSV.tsx index c9eb874..1c9a85b 100644 --- a/src/components/Controls/ExportRules.tsx +++ b/src/components/Controls/ExportRulesCSV.tsx @@ -1,10 +1,9 @@ -import ControlButton from "./ControlButton"; -import ChecklistIcon from "@mui/icons-material/Checklist"; import AppContext from "../AppContext"; import { useContext } from "react"; import { IFilter } from "../../types/IFilter"; import { saveAs } from "file-saver"; import { ColumnOption, stringify } from "csv-stringify/browser/esm/sync"; +import { MenuItem } from "@mui/material"; const columns: ColumnOption[] = [ { @@ -50,7 +49,9 @@ const cast = { Array.isArray(value) ? value.join(", ") : JSON.stringify(value), }; -export default function ExportRules() { +export default function ExportRulesCSV({ + onClose +}) { const { dataService, orderBy, order, searchText, setAlertState } = useContext( AppContext ); @@ -112,11 +113,10 @@ export default function ExportRules() { message: `Saved ${rules.length} Rules`, severity: "success", }); + onClose(); }; return ( - - - + Export rules CSV (as filtered) ); } diff --git a/src/components/Controls/ExportRulesYAML.tsx b/src/components/Controls/ExportRulesYAML.tsx new file mode 100644 index 0000000..6ef1fe9 --- /dev/null +++ b/src/components/Controls/ExportRulesYAML.tsx @@ -0,0 +1,93 @@ +import AppContext from "../AppContext"; +import { useContext } from "react"; +import { IFilter } from "../../types/IFilter"; +import { saveAs } from "file-saver"; +import { ColumnOption } from "csv-stringify/browser/esm/sync"; +import { MenuItem } from "@mui/material"; +import JSZip from "jszip"; + +const columns: ColumnOption[] = [ + { + header: "id", + key: "id", + }, + { + header: "ruleId", + key: "json.Authorities.Standards.References.Rule Identifier.Id", + }, + { + header: "content", + key: "content", + }, +]; + +const cast = (value) => + Array.isArray(value) ? value.join(",") : JSON.stringify(value); + +export default function ExportRulesYAML({ onClose }) { + const { dataService, orderBy, order, searchText, setAlertState } = useContext( + AppContext + ); + + const exportRules = async () => { + const params = { + orderBy: orderBy, + order: order, + select: columns.map((column) => column.key), + filters: Object.entries(searchText) + .filter( + ([_, filterValue]: [string, string]) => + !(filterValue == null || filterValue === "") + ) + .map( + ([filterName, filterValue]: [string, string]): IFilter => ({ + name: filterName, + operator: "contains", + value: filterValue, + }) + ), + }; + const rules = []; + setAlertState({ + message: `Downloading Rules ${rules.length}/?...`, + severity: "info", + }); + var currentRules = await dataService.get_rules_filter_sort(params); + rules.push(...currentRules.rules); + while (currentRules.next) { + setAlertState({ + message: `Downloading Rules ${rules.length}/?...`, + severity: "info", + }); + currentRules = await dataService.get_rules_filter_sort(currentRules.next); + rules.push(...currentRules.rules); + } + setAlertState({ + message: `Saving ${rules.length} Rules...`, + severity: "info", + }); + + const zip = new JSZip(); + for (const rule of rules) { + zip.file( + `${cast( + rule["json.Authorities.Standards.References.Rule Identifier.Id"] + )}.${rule["id"]}.yml`, + rule["content"] + ); + } + zip.generateAsync({ type: "blob" }).then((content) => { + saveAs(content, "Rules.zip"); + }); + + setAlertState({ + message: `Saved ${rules.length} Rules`, + severity: "success", + }); + onClose(); + }; + + return ( + Export rules YAML (as filtered) + ); +}