diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index ce61752b..e4cac8fb 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -60,7 +60,16 @@ const Home = () => { } function historyItemParser(historyItem: HistoryItem) { - if (historyItem.json_path) { + // During development, re-parse JSON file (Possible schema changes) + // Toggle this if you don't have schema changes & wish to save time in imports + const REIMPORT_JSON_IN_DEV = true; + + if ( + REIMPORT_JSON_IN_DEV && + process.env.NODE_ENV === "development" && + historyItem.json_path + ) { + console.log("Re-import JSON file (Dev mode)"); if (!electronFs.existsSync(historyItem.json_path)) { return reportInvalidEntry(historyItem, "JSON"); } diff --git a/src/pages/Licenses/Licenses.css b/src/pages/Licenses/Licenses.css index 7b09f2d5..b2b34a8f 100644 --- a/src/pages/Licenses/Licenses.css +++ b/src/pages/Licenses/Licenses.css @@ -28,9 +28,13 @@ padding-right: 12px; } -.licenses-navigator-container .search-box { - width: 98%; - margin-bottom: 12px; +.licenses-navigator-container .filter-group { + margin-bottom: 10px; + padding-right: 5px; +} + +.licenses-navigator-container .filter-group>div { + margin-bottom: 4px; } .licenses-navigator-container .search-box input { @@ -89,7 +93,8 @@ .license-item>div { padding: 2px; - padding-right: 4px; + padding-top: 4px; + padding-bottom: 0; display: table-cell; vertical-align: top; } @@ -100,11 +105,22 @@ word-break: break-word; } + +.license-item>.vet-toggle, .license-item>.license-count { + width: 38px; +} + +.license-item>.vet-toggle { padding-top: 2px; - padding-right: 7px; + padding-right: 10px; +} + +.license-item>.license-count { + padding-top: 6px; } +.license-item>.vet-toggle>*, .license-item>.license-count>* { float: right; } \ No newline at end of file diff --git a/src/pages/Licenses/Licenses.tsx b/src/pages/Licenses/Licenses.tsx index a4009698..e6e3c0a1 100644 --- a/src/pages/Licenses/Licenses.tsx +++ b/src/pages/Licenses/Licenses.tsx @@ -7,8 +7,10 @@ import { ListGroup, ListGroupItem, } from "react-bootstrap"; +import { toast } from "react-toastify"; import { ThreeDots } from "react-loader-spinner"; import { useSearchParams } from "react-router-dom"; +import Select from "react-select"; import LicenseEntity from "../../components/LicenseEntity/LicenseEntity"; import NoDataFallback from "../../components/NoDataSection"; @@ -18,16 +20,20 @@ import { ActiveLicenseEntity, LicenseClueDetails, LicenseDetectionDetails, + VET_OPTIONS, + VetOption, } from "./licenseDefinitions"; +import { LicenseTypes } from "../../services/workbenchDB.types"; import "./Licenses.css"; const LicenseDetections = () => { const [searchParams] = useSearchParams(); - const [activeLicense, setActiveLicense] = useState( - null - ); + const [activeLicense, setActiveLicense] = + useState(null); const [searchedLicense, setSearchedLicense] = useState(""); + const [vetFilter, setVetFilter] = useState(VET_OPTIONS.ALL); + const [vettedLicenses, setVettedLicenses] = useState>(new Set()); function activateLicenseDetection(licenseDetection: LicenseDetectionDetails) { if (!licenseDetection) return; @@ -59,17 +65,15 @@ const LicenseDetections = () => { const newLicenseDetections: LicenseDetectionDetails[] = ( await db.getAllLicenseDetections() ).map((detection) => ({ + id: Number(detection.getDataValue("id")), + vetted: detection.getDataValue("vetted"), detection_count: Number(detection.getDataValue("detection_count")), identifier: detection.getDataValue("identifier") || null, - license_expression: detection - .getDataValue("license_expression") - , + license_expression: detection.getDataValue("license_expression"), detection_log: JSON.parse( detection.getDataValue("detection_log") || "[]" ), - matches: JSON.parse( - detection.getDataValue("matches") || "[]" - ), + matches: JSON.parse(detection.getDataValue("matches") || "[]"), file_regions: JSON.parse( detection.getDataValue("file_regions") || "[]" ), @@ -81,6 +85,11 @@ const LicenseDetections = () => { ).map((clue) => { return { id: Number(clue.getDataValue("id")), + // @TODO - Find better way to have unique identifier for each clue + identifier: `clue-${ + clue.getDataValue("license_expression") || "" + }-${Number(clue.getDataValue("id"))}`, + vetted: clue.getDataValue("vetted"), fileId: Number(clue.getDataValue("fileId")), filePath: clue.getDataValue("filePath") || "", fileClueIdx: Number(clue.getDataValue("fileClueIdx")), @@ -88,20 +97,27 @@ const LicenseDetections = () => { clue.getDataValue("score") !== null ? Number(clue.getDataValue("score")) : null, - license_expression: - clue.getDataValue("license_expression") || null, - rule_identifier: - clue.getDataValue("rule_identifier") || null, - matches: JSON.parse( - clue.getDataValue("matches") || "[]" - ), - file_regions: JSON.parse( - clue.getDataValue("file_regions") || "[]" - ), + license_expression: clue.getDataValue("license_expression") || null, + rule_identifier: clue.getDataValue("rule_identifier") || null, + matches: JSON.parse(clue.getDataValue("matches") || "[]"), + file_regions: JSON.parse(clue.getDataValue("file_regions") || "[]"), }; }); setLicenseClues(newLicenseClues); + const newVettedLicenses = new Set(); + newLicenseDetections.forEach((detection) => { + if (detection.vetted) { + newVettedLicenses.add(detection.identifier); + } + }); + newLicenseClues.forEach((clue) => { + if (clue.vetted) { + newVettedLicenses.add(clue.identifier); + } + }); + setVettedLicenses(newVettedLicenses); + const queriedDetectionIdentifier: string | null = searchParams.get( QUERY_KEYS.LICENSE_DETECTION ); @@ -154,6 +170,46 @@ const LicenseDetections = () => { })().then(endProcessing); }, []); + const handleItemToggle = ( + license: LicenseDetectionDetails | LicenseClueDetails, + licenseType: LicenseTypes, + newVettedStatus: boolean + ) => { + function updateVettedLicenseStatus(identifier: string, newStatus: boolean) { + setVettedLicenses((prevVettedLicenses) => { + const newVettedLicenses = new Set(prevVettedLicenses); + if (newStatus) newVettedLicenses.add(identifier); + else newVettedLicenses.delete(identifier); + return newVettedLicenses; + }); + } + + // const newVettedStatus = !vettedLicenses.has(license.identifier); + db.toggleLicenseVettedStatus( + license.id, + licenseType, + newVettedStatus + ).catch((err) => { + // Revert vetted status in UI if DB update fails + updateVettedLicenseStatus(license.identifier, !newVettedStatus); + console.log("Error updating vetted status: ", err); + toast.error("Couldn't update vetted license status!"); + }); + + updateVettedLicenseStatus(license.identifier, newVettedStatus); + }; + + const shownByVetFilter = ( + license: LicenseDetectionDetails | LicenseClueDetails + ) => { + if (vetFilter === VET_OPTIONS.ALL) return true; + if (vetFilter === VET_OPTIONS.VETTED) + return vettedLicenses.has(license.identifier); + if (vetFilter === VET_OPTIONS.UNVETTED) + return !vettedLicenses.has(license.identifier); + return true; + }; + if (!licenseDetections || !licenseClues) { return ( { } const totalLicenses = licenseDetections.length + licenseClues.length; - const DEFAULT_SECTION_HEIGHT_CAP = 0.8; + const DEFAULT_SECTION_HEIGHT_CAP = 0.75; const capSectionSize = (ratio: number) => { return ( Math.max( @@ -191,7 +247,6 @@ const LicenseDetections = () => { return (
-

License explorer

{ preferredSize="30%" className="licenses-navigator-container" > - - setSearchedLicense(e.target.value)} +
+