From 5c2bca41db1817e273aa6e5dd78774f6abb78388 Mon Sep 17 00:00:00 2001 From: Omkar Phansopkar Date: Sun, 4 Jun 2023 21:52:40 +0530 Subject: [PATCH 01/12] Implemented matched text diff with rule text, resolves #561 Signed-off-by: Omkar Phansopkar --- package-lock.json | 159 ++++++++++++--- package.json | 1 + .../LicenseEntity/LicenseEntity.tsx | 31 +-- .../LicenseEntity/MatchedTextContext.tsx | 187 ++++++++++++++++++ .../LicenseEntity/MatchesTableCols.ts | 53 +++-- .../LicenseEntity/licenseEntity.css | 5 + .../Licenses/MatchedTextRenderer.tsx | 26 +++ src/services/importedJsonTypes.ts | 2 + src/services/models/database.ts | 86 ++++---- src/services/models/licenseRuleReference.ts | 93 +++++++++ src/services/workbenchDB.ts | 13 +- 11 files changed, 563 insertions(+), 93 deletions(-) create mode 100644 src/components/LicenseEntity/MatchedTextContext.tsx create mode 100644 src/pages/TableView/CustomCellRenderers/Licenses/MatchedTextRenderer.tsx create mode 100644 src/services/models/licenseRuleReference.ts diff --git a/package-lock.json b/package-lock.json index 37f10037..4ba1bd39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "react": "^18.2.0", "react-bootstrap": "^2.5.0", "react-chartjs-2": "^4.3.1", + "react-diff-viewer-continued": "^3.2.6", "react-dom": "^18.2.0", "react-loader-spinner": "^5.3.4", "react-pro-sidebar": "^0.7.1", @@ -3184,6 +3185,61 @@ "node": ">=10" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "dependencies": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/css": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.11.0.tgz", + "integrity": "sha512-m4g6nKzZyiKyJ3WOfdwrBdcujVcpaScIWHAnyNKPm/A/xJKwfXPfQAbEVi1kgexWTDakmg+r2aDj0KvnMTo4oQ==", + "dependencies": { + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.2", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + }, "node_modules/@emotion/is-prop-valid": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", @@ -3193,9 +3249,31 @@ } }, "node_modules/@emotion/memoize": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/serialize": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", + "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "dependencies": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/serialize/node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" }, "node_modules/@emotion/stylis": { "version": "0.8.5", @@ -3207,6 +3285,16 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, "node_modules/@eslint/eslintrc": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.0.tgz", @@ -5459,8 +5547,7 @@ "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, "node_modules/@types/plist": { "version": "3.0.2", @@ -6901,7 +6988,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -7562,7 +7648,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -8213,8 +8298,7 @@ "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/cookie": { "version": "0.5.0", @@ -8331,7 +8415,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "dev": true, "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -9429,6 +9512,14 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "dev": true }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", @@ -10592,7 +10683,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -10792,7 +10882,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "devOptional": true, "engines": { "node": ">=10" }, @@ -12017,6 +12106,11 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -13307,7 +13401,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -13467,8 +13560,7 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, "node_modules/is-bigint": { "version": "1.0.4", @@ -15414,8 +15506,7 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/listr2": { "version": "5.0.7", @@ -15884,6 +15975,11 @@ "node": ">= 4.0.0" } }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "node_modules/memoizee": { "version": "0.4.15", "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", @@ -16938,7 +17034,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -16962,7 +17057,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -17051,7 +17145,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "engines": { "node": ">=8" } @@ -19175,6 +19268,25 @@ "node": ">=6" } }, + "node_modules/react-diff-viewer-continued": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/react-diff-viewer-continued/-/react-diff-viewer-continued-3.2.6.tgz", + "integrity": "sha512-GrzyqQnjIMoej+jMjWvtVSsQqhXgzEGqpXlJ2dAGfOk7Q26qcm8Gu6xtI430PBUyZsERe8BJSQf+7VZZo8IBNQ==", + "dependencies": { + "@emotion/css": "^11.10.5", + "classnames": "^2.3.1", + "diff": "^5.1.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 8" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -20329,7 +20441,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -22055,6 +22166,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "node_modules/sudo-prompt": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", @@ -24264,7 +24380,6 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, "engines": { "node": ">= 6" } diff --git a/package.json b/package.json index 30e28e6a..dc8d5f71 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "react": "^18.2.0", "react-bootstrap": "^2.5.0", "react-chartjs-2": "^4.3.1", + "react-diff-viewer-continued": "^3.2.6", "react-dom": "^18.2.0", "react-loader-spinner": "^5.3.4", "react-pro-sidebar": "^0.7.1", diff --git a/src/components/LicenseEntity/LicenseEntity.tsx b/src/components/LicenseEntity/LicenseEntity.tsx index 2f9e047a..0606b0fc 100644 --- a/src/components/LicenseEntity/LicenseEntity.tsx +++ b/src/components/LicenseEntity/LicenseEntity.tsx @@ -12,6 +12,7 @@ import { LicenseDetectionMatchCols, LicenseClueMatchCols, } from "./MatchesTableCols"; +import { MatchedTextProvider } from "./MatchedTextContext"; import "../../styles/entityCommonStyles.css"; import "./licenseEntity.css"; @@ -81,20 +82,22 @@ const LicenseEntity = (props: LicenseDetectionEntityProps) => { ))}
- Matches - + + Matches + +
File regions void; +} + +export const defaultMatchedTextContextValue: MatchedTextContextProperties = { + showDiffWindow: false, + openDiffWindow: () => null, +}; + +const MatchedTextContext = createContext( + defaultMatchedTextContextValue +); + +const DIFF_VIEWER_STYLES: ReactDiffViewerStylesOverride = { + diffRemoved: { + backgroundColor: "#e8faff", + }, + diffAdded: { + backgroundColor: "#f8fff1", + }, + diffChanged: { + backgroundColor: "#f8fff1", + }, + variables: { + light: { + codeFoldGutterBackground: "#6F767E", + codeFoldBackground: "#E2E4E5", + }, + }, +}; + +export const MatchedTextProvider = ( + props: React.PropsWithChildren> +) => { + const { db } = useWorkbenchDB(); + const [showDiffWindow, setShowDiffWindow] = useState(false); + const [matchDetails, setMatchDetails] = useState<{ + identifier: string | null; + matched_text: string | null; + start_line: number; + score: number; + }>({ + identifier: null, + matched_text: null, + start_line: 1, + score: 0, + }); + + const [ruleDetails, setRuleDetails] = useState<{ + ruleText: string | null; + processing: boolean; + }>({ ruleText: "", processing: false }); + + useEffect(() => { + if (!matchDetails.identifier) return; + setRuleDetails((prev) => ({ ...prev, processing: true })); + + db.getLicenseRuleReference(matchDetails.identifier, ["text"]).then( + (ruleRef) => { + console.log(`For ${matchDetails.identifier}, Found ref:`, ruleRef); + + if (!ruleRef) { + return setRuleDetails({ + processing: false, + ruleText: null, + }); + } + + setRuleDetails({ + processing: false, + ruleText: ruleRef.getDataValue("text").toString({}), + }); + } + ); + }, [matchDetails]); + + function closeDiffWindow() { + setMatchDetails({ + matched_text: null, + identifier: null, + start_line: 0, + score: 0, + }); + setRuleDetails({ + ruleText: null, + processing: false, + }); + setShowDiffWindow(false); + } + + return ( + { + if (!matched_text) return; + setMatchDetails({ + identifier: rule_identifier, + matched_text: matched_text, + start_line, + score, + }); + setShowDiffWindow(true); + }, + }} + > + {props.children} + + 150 ? "xl" : "lg"} + centered + backdrop={true} + show={showDiffWindow} + onHide={closeDiffWindow} + > + + Matched text diff + + + {ruleDetails.processing ? ( +
+ Fetching rule data + +
+ ) : ( + matchDetails.matched_text && + (ruleDetails.ruleText ? ( + <> +
Score: {matchDetails.score} %
+ + + ) : ( +
+
Score: {matchDetails.score} %
+
Matched Text:
+
{matchDetails.matched_text}
+ + Couldn't find License Rule Reference for specified identifier + - {matchDetails.identifier} + +
+ )) + )} +
+
+
+ ); +}; + +export const useMatchedTextContext = () => useContext(MatchedTextContext); diff --git a/src/components/LicenseEntity/MatchesTableCols.ts b/src/components/LicenseEntity/MatchesTableCols.ts index f4da2f93..ed55f120 100644 --- a/src/components/LicenseEntity/MatchesTableCols.ts +++ b/src/components/LicenseEntity/MatchesTableCols.ts @@ -1,5 +1,9 @@ import { ColDef } from "ag-grid-community"; -import { MatchLicenseExpressionRenderer, UrlRenderer } from "../../pages/TableView/CustomCellRenderers"; +import { + MatchLicenseExpressionRenderer, + UrlRenderer, +} from "../../pages/TableView/CustomCellRenderers"; +import MatchedTextRenderer from "../../pages/TableView/CustomCellRenderers/Licenses/MatchedTextRenderer"; export const DEFAULT_MATCHES_COL_DEF: ColDef = { sortable: true, @@ -8,7 +12,7 @@ export const DEFAULT_MATCHES_COL_DEF: ColDef = { wrapText: true, autoHeight: true, wrapHeaderText: true, -} +}; const MINI_FIELD_WIDTH = 90; interface MatchColumns { @@ -17,57 +21,64 @@ interface MatchColumns { matched_length: ColDef; match_coverage: ColDef; matcher: ColDef; + matched_text: ColDef; rule_url: ColDef; license_expression_spdx: ColDef; } const MATCH_COLS: MatchColumns = { license_expression: { - headerName: 'License expression', - field: 'license_expression', + headerName: "License expression", + field: "license_expression", cellRenderer: MatchLicenseExpressionRenderer, width: 270, }, score: { - headerName: 'Score', - field: 'score', + headerName: "Score", + field: "score", width: MINI_FIELD_WIDTH, }, matched_length: { - headerName: 'Matched length', + headerName: "Matched length", wrapHeaderText: true, - field: 'matched_length', + field: "matched_length", width: 110, }, match_coverage: { - headerName: 'Match Coverage', + headerName: "Match Coverage", wrapHeaderText: true, - field: 'match_coverage', + field: "match_coverage", width: 120, }, matcher: { - headerName: 'Matcher', - field: 'matcher', - width: 120 + headerName: "Matcher", + field: "matcher", + width: 120, + }, + matched_text: { + headerName: "Matched Text", + field: "matched_text", + cellRenderer: MatchedTextRenderer, + width: 150, }, rule_url: { - headerName: 'Rule', - field: 'rule_url', + headerName: "Rule", + field: "rule_url", cellRenderer: UrlRenderer, cellRendererParams: { - customTextField: 'rule_identifier' + customTextField: "rule_identifier", }, width: 250, }, license_expression_spdx: { - headerName: 'SPDX License expression', - field: 'license_expression_spdx', + headerName: "SPDX License expression", + field: "license_expression_spdx", cellRenderer: MatchLicenseExpressionRenderer, cellRendererParams: { spdxLicense: true, }, width: 250, }, -} +}; export const LicenseDetectionMatchCols: ColDef[] = [ MATCH_COLS.license_expression, @@ -75,6 +86,7 @@ export const LicenseDetectionMatchCols: ColDef[] = [ MATCH_COLS.matched_length, MATCH_COLS.match_coverage, MATCH_COLS.matcher, + MATCH_COLS.matched_text, MATCH_COLS.rule_url, MATCH_COLS.license_expression_spdx, ]; @@ -85,5 +97,6 @@ export const LicenseClueMatchCols: ColDef[] = [ MATCH_COLS.matched_length, MATCH_COLS.match_coverage, MATCH_COLS.matcher, + MATCH_COLS.matched_text, MATCH_COLS.rule_url, -] \ No newline at end of file +]; \ No newline at end of file diff --git a/src/components/LicenseEntity/licenseEntity.css b/src/components/LicenseEntity/licenseEntity.css index 3d6b4971..1ae05e39 100644 --- a/src/components/LicenseEntity/licenseEntity.css +++ b/src/components/LicenseEntity/licenseEntity.css @@ -28,4 +28,9 @@ .matches-table, .file-regions-table { max-height: 30vh; +} + +.matched-text-diff-modal { + overflow: scroll; + max-height: calc(100vh - 120px); } \ No newline at end of file diff --git a/src/pages/TableView/CustomCellRenderers/Licenses/MatchedTextRenderer.tsx b/src/pages/TableView/CustomCellRenderers/Licenses/MatchedTextRenderer.tsx new file mode 100644 index 00000000..6cc54fd3 --- /dev/null +++ b/src/pages/TableView/CustomCellRenderers/Licenses/MatchedTextRenderer.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { useMatchedTextContext } from "../../../../components/LicenseEntity/MatchedTextContext"; + +interface MatchedTextRendererProps { + value: string; + data: any; +} + +const MatchedTextRenderer = (props: MatchedTextRendererProps) => { + const { value, data } = props; + const { openDiffWindow } = useMatchedTextContext(); + + return ( + + value && + openDiffWindow(value, data.rule_identifier, data.start_line, data.score) + } + > + {value} + + ); +}; + +export default MatchedTextRenderer; \ No newline at end of file diff --git a/src/services/importedJsonTypes.ts b/src/services/importedJsonTypes.ts index b731d068..d9c2eb85 100644 --- a/src/services/importedJsonTypes.ts +++ b/src/services/importedJsonTypes.ts @@ -45,6 +45,7 @@ export interface LicenseMatch { score: number; start_line: number; end_line: number; + matched_text: string; matched_length: number; match_coverage: number; matcher: string; @@ -71,6 +72,7 @@ export interface LicenseClue { score: number; start_line: number; end_line: number; + matched_text: string; matched_length: number; match_coverage: number; matcher: string; diff --git a/src/services/models/database.ts b/src/services/models/database.ts index a1b00bac..9a179229 100644 --- a/src/services/models/database.ts +++ b/src/services/models/database.ts @@ -14,23 +14,30 @@ # */ -import { Model, ModelStatic, Sequelize } from 'sequelize'; - -import headerModel, { HeaderAttributes } from './header'; -import fileModel, { FileAttributes } from './file'; -import licenseExpressionModel, { LicenseExpressionAttributes, OptionalLicenseExpressionAttributes } from './licenseExpression'; -import licensePolicyModel, { LicensePolicyAttributes } from './licensePolicy'; -import copyrightModel, { CopyrightAttributes } from './copyright'; -import packageDataModel, { PackageDataAttributes } from './packageData'; -import emailModel, { EmailAttributes } from './email'; -import urlModel, { UrlAttributes } from './url'; -import flatFileModel, { FlatFileAttributes } from './flatFile'; -import scanErrorModel, { ScanErrorAttributes } from './scanError'; -import packagesModel, { PackagesAttributes } from './packages'; -import dependenciesModel, { DependenciesAttributes } from './dependencies'; -import licenseDetectionModel, { LicenseDetectionAttributes } from './licenseDetections'; -import licenseClueModel, { LicenseClueAttributes } from './licenseClues'; +import { Model, ModelStatic, Sequelize } from "sequelize"; +import headerModel, { HeaderAttributes } from "./header"; +import fileModel, { FileAttributes } from "./file"; +import licenseExpressionModel, { + LicenseExpressionAttributes, + OptionalLicenseExpressionAttributes, +} from "./licenseExpression"; +import licensePolicyModel, { LicensePolicyAttributes } from "./licensePolicy"; +import copyrightModel, { CopyrightAttributes } from "./copyright"; +import packageDataModel, { PackageDataAttributes } from "./packageData"; +import emailModel, { EmailAttributes } from "./email"; +import urlModel, { UrlAttributes } from "./url"; +import flatFileModel, { FlatFileAttributes } from "./flatFile"; +import scanErrorModel, { ScanErrorAttributes } from "./scanError"; +import packagesModel, { PackagesAttributes } from "./packages"; +import dependenciesModel, { DependenciesAttributes } from "./dependencies"; +import licenseDetectionModel, { + LicenseDetectionAttributes, +} from "./licenseDetections"; +import licenseClueModel, { LicenseClueAttributes } from "./licenseClues"; +import licenseRuleReferenceModel, { + LicenseRuleReferenceAttributes, +} from "./licenseRuleReference"; // let Header; // let File; @@ -41,29 +48,39 @@ import licenseClueModel, { LicenseClueAttributes } from './licenseClues'; // let Url; // let Scan; - // type SupportedModels = >; -export interface DatabaseStructure{ +export interface DatabaseStructure { // Top level entities - Header: ModelStatic>, - Packages: ModelStatic>, - Dependencies: ModelStatic>, - LicenseDetections: ModelStatic>, - LicenseClues: ModelStatic>, + Header: ModelStatic>; + Packages: ModelStatic>; + Dependencies: ModelStatic< + Model + >; + LicenseDetections: ModelStatic< + Model + >; + LicenseRuleReferences: ModelStatic< + Model + >; + LicenseClues: ModelStatic< + Model + >; - File: ModelStatic>, - LicenseExpression: ModelStatic>, - LicensePolicy: ModelStatic>, - Copyright: ModelStatic>, - PackageData: ModelStatic>, - Email: ModelStatic>, - Url: ModelStatic>, - ScanError: ModelStatic>, + File: ModelStatic>; + LicenseExpression: ModelStatic< + Model + >; + LicensePolicy: ModelStatic>; + Copyright: ModelStatic>; + PackageData: ModelStatic>; + Email: ModelStatic>; + Url: ModelStatic>; + ScanError: ModelStatic>; - FlatFile: ModelStatic>, + FlatFile: ModelStatic>; - fileIncludes: { model: ModelStatic>, separate: boolean }[], + fileIncludes: { model: ModelStatic>; separate: boolean }[]; } export function newDatabase(sequelize: Sequelize): DatabaseStructure { @@ -74,6 +91,7 @@ export function newDatabase(sequelize: Sequelize): DatabaseStructure { Packages: packagesModel(sequelize), Dependencies: dependenciesModel(sequelize), LicenseDetections: licenseDetectionModel(sequelize), + LicenseRuleReferences: licenseRuleReferenceModel(sequelize), LicenseClues: licenseClueModel(sequelize), File: fileModel(sequelize), @@ -111,6 +129,6 @@ export function newDatabase(sequelize: Sequelize): DatabaseStructure { return { ...result, - fileIncludes + fileIncludes, }; } \ No newline at end of file diff --git a/src/services/models/licenseRuleReference.ts b/src/services/models/licenseRuleReference.ts new file mode 100644 index 00000000..dffc9291 --- /dev/null +++ b/src/services/models/licenseRuleReference.ts @@ -0,0 +1,93 @@ +/* + # + # Copyright (c) 2019 nexB Inc. and others. All rights reserved. + # https://nexb.com and https://github.com/nexB/scancode-workbench/ + # The ScanCode Workbench software is licensed under the Apache License version 2.0. + # ScanCode is a trademark of nexB Inc. + # + # You may not use this software except in compliance with the License. + # You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0 + # Unless required by applicable law or agreed to in writing, software distributed + # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + # CONDITIONS OF ANY KIND, either express or implied. See the License for the + # specific language governing permissions and limitations under the License. + # + */ + +import { + Sequelize, + DataTypes, + IntegerDataType, + StringDataType, + Model, +} from "sequelize"; +// import { JSON_Type, jsonDataType } from "./databaseUtils"; + +export interface LicenseRuleReferenceAttributes { + id: IntegerDataType; + license_expression: StringDataType; + identifier: StringDataType; + language: StringDataType; + rule_url: StringDataType; + is_license_text: boolean; + is_license_notice: boolean; + is_license_reference: boolean; + is_license_tag: boolean; + is_license_intro: boolean; + is_continuous: boolean; + is_builtin: boolean; + is_from_license: boolean; + is_synthetic: boolean; + length: number; + relevance: number; + minimum_coverage: number; + // referenced_filenames: JSON_Type; + // notes: StringDataType; + // ignorable_copyrights: JSON_Type; + // ignorable_holders: JSON_Type; + // ignorable_authors: JSON_Type; + // ignorable_urls: JSON_Type; + // ignorable_emails: JSON_Type; + text: StringDataType; +} + +export default function licenseRuleReferenceModel(sequelize: Sequelize) { + return sequelize.define>( + "license_rule_reference", + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER, + }, + license_expression: DataTypes.STRING, + identifier: DataTypes.STRING, + language: DataTypes.STRING, + rule_url: DataTypes.STRING, + is_license_text: DataTypes.BOOLEAN, + is_license_notice: DataTypes.BOOLEAN, + is_license_reference: DataTypes.BOOLEAN, + is_license_tag: DataTypes.BOOLEAN, + is_license_intro: DataTypes.BOOLEAN, + is_continuous: DataTypes.BOOLEAN, + is_builtin: DataTypes.BOOLEAN, + is_from_license: DataTypes.BOOLEAN, + is_synthetic: DataTypes.BOOLEAN, + length: DataTypes.NUMBER, + relevance: DataTypes.NUMBER, + minimum_coverage: DataTypes.NUMBER, + // referenced_filenames: jsonDataType("referenced_filenames"), + // notes: DataTypes.STRING, + // ignorable_copyrights: jsonDataType("ignorable_copyrights"), + // ignorable_holders: jsonDataType("ignorable_holders"), + // ignorable_authors: jsonDataType("ignorable_authors"), + // ignorable_urls: jsonDataType("ignorable_urls"), + // ignorable_emails: jsonDataType("ignorable_emails"), + text: DataTypes.STRING, + }, + { + timestamps: false, + } + ); +} \ No newline at end of file diff --git a/src/services/workbenchDB.ts b/src/services/workbenchDB.ts index dfdcacdc..dcc4a2cd 100644 --- a/src/services/workbenchDB.ts +++ b/src/services/workbenchDB.ts @@ -185,6 +185,12 @@ export class WorkbenchDB { return this.sync.then((db) => db.Dependencies.findAll()); } + getLicenseRuleReference(identifier: string, attributes?: string[]) { + return this.sync.then((db) => + db.LicenseRuleReferences.findOne({ where: { identifier }, attributes }) + ); + } + // Uses the files table to do a findOne query findOne(query: FindOptions) { query = $.extend(query, { @@ -343,9 +349,6 @@ export class WorkbenchDB { ); const license_rule_references: any[] = topLevelData.license_rule_references || []; - // const license_rule_references_mapping = new Map( - // license_rule_references.map(rule_ref => [rule_ref.identifier, rule_ref]) - // ); TopLevelData = { header, @@ -367,6 +370,9 @@ export class WorkbenchDB { promiseChain = promiseChain .then(() => this.db.Packages.bulkCreate(packages)) .then(() => this.db.Dependencies.bulkCreate(dependencies)) + .then(() => + this.db.LicenseRuleReferences.bulkCreate(license_rule_references) + ) .then(() => this.db.Header.create(parsedHeader)) .then((header) => (headerId = Number(header.getDataValue("id")))) .catch((err: unknown) => { @@ -719,6 +725,7 @@ export class WorkbenchDB { score: license_clue.score, start_line: license_clue.start_line, end_line: license_clue.end_line, + matched_text: license_clue.matched_text, matched_length: license_clue.matched_length, match_coverage: license_clue.match_coverage, matcher: license_clue.matcher, From b004b783a367ac6cc3b6e6eeaddd88daabfbe034 Mon Sep 17 00:00:00 2001 From: Omkar Phansopkar Date: Mon, 5 Jun 2023 00:22:03 +0530 Subject: [PATCH 02/12] Hide matched text, if --license-text is disabled in the scan Signed-off-by: Omkar Phansopkar --- src/components/FileTree/FileTree.tsx | 10 +-- .../LicenseEntity/LicenseEntity.tsx | 23 +++++- .../LicenseEntity/MatchesTableCols.ts | 28 ++++--- src/pages/Packages/Packages.tsx | 2 +- src/pages/ScanInfo/ScanInfo.tsx | 74 ++---------------- src/pages/ScanInfo/scanInfo.css | 6 +- src/utils/parsers.ts | 76 +++++++++++++++++++ 7 files changed, 131 insertions(+), 88 deletions(-) create mode 100644 src/utils/parsers.ts diff --git a/src/components/FileTree/FileTree.tsx b/src/components/FileTree/FileTree.tsx index f64372b2..d0fe5b0f 100644 --- a/src/components/FileTree/FileTree.tsx +++ b/src/components/FileTree/FileTree.tsx @@ -26,11 +26,11 @@ const FileTree = (props: React.HTMLProps) => { useEffect(() => { setExpandedKeys((keys) => { - console.log( - "Adding", - currentPath.substring(0, currentPath.lastIndexOf("/")), - [...keys, currentPath.substring(0, currentPath.lastIndexOf("/"))] - ); + // console.log( + // "Adding", + // currentPath.substring(0, currentPath.lastIndexOf("/")), + // [...keys, currentPath.substring(0, currentPath.lastIndexOf("/"))] + // ); return [...keys, currentPath.substring(0, currentPath.lastIndexOf("/"))]; }); if (currentPath.length) { diff --git a/src/components/LicenseEntity/LicenseEntity.tsx b/src/components/LicenseEntity/LicenseEntity.tsx index 0606b0fc..f2e24b0e 100644 --- a/src/components/LicenseEntity/LicenseEntity.tsx +++ b/src/components/LicenseEntity/LicenseEntity.tsx @@ -1,6 +1,6 @@ import ReactJson from "@microlink/react-json-view"; import { AgGridReact } from "ag-grid-react"; -import React from "react"; +import React, { useEffect, useState } from "react"; import { ActiveLicense } from "../../pages/Licenses/licenseDefinitions"; import { @@ -11,17 +11,35 @@ import { DEFAULT_MATCHES_COL_DEF, LicenseDetectionMatchCols, LicenseClueMatchCols, + MATCH_COLS, } from "./MatchesTableCols"; import { MatchedTextProvider } from "./MatchedTextContext"; +import { useWorkbenchDB } from "../../contexts/dbContext"; +import { parseScanInfo } from "../../utils/parsers"; +import { ColumnApi } from "ag-grid-community"; -import "../../styles/entityCommonStyles.css"; import "./licenseEntity.css"; +import "../../styles/entityCommonStyles.css"; interface LicenseDetectionEntityProps { activeLicense: ActiveLicense | null; } const LicenseEntity = (props: LicenseDetectionEntityProps) => { const { activeLicense } = props; + const { db } = useWorkbenchDB(); + const [matchesTableColumnApi, setMatchesTableColumnApi] = + useState(null); + + useEffect(() => { + if (!db || !matchesTableColumnApi) return; + db.getScanInfo().then((rawScanInfo) => { + const scanInfo = parseScanInfo(rawScanInfo); + matchesTableColumnApi.setColumnVisible( + MATCH_COLS.matched_text.colId, + Boolean(scanInfo.optionsMap.get("license-text")) + ); + }); + }, [db, matchesTableColumnApi]); const license = activeLicense?.license; const matches = activeLicense?.license?.matches; @@ -91,6 +109,7 @@ const LicenseEntity = (props: LicenseDetectionEntityProps) => { ? LicenseDetectionMatchCols : LicenseClueMatchCols } + onGridReady={(params) => setMatchesTableColumnApi(params.columnApi)} className="ag-theme-alpine ag-grid-customClass matches-table" ensureDomOrder enableCellTextSelection diff --git a/src/components/LicenseEntity/MatchesTableCols.ts b/src/components/LicenseEntity/MatchesTableCols.ts index ed55f120..97bb7248 100644 --- a/src/components/LicenseEntity/MatchesTableCols.ts +++ b/src/components/LicenseEntity/MatchesTableCols.ts @@ -25,44 +25,51 @@ interface MatchColumns { rule_url: ColDef; license_expression_spdx: ColDef; } -const MATCH_COLS: MatchColumns = { +export const MATCH_COLS: MatchColumns = { license_expression: { - headerName: "License expression", + colId: "license_expression", field: "license_expression", + headerName: "License expression", cellRenderer: MatchLicenseExpressionRenderer, width: 270, }, score: { - headerName: "Score", + colId: "score", field: "score", + headerName: "Score", width: MINI_FIELD_WIDTH, }, matched_length: { + colId: "matched_length", + field: "matched_length", headerName: "Matched length", wrapHeaderText: true, - field: "matched_length", width: 110, }, match_coverage: { + colId: "match_coverage", + field: "match_coverage", headerName: "Match Coverage", wrapHeaderText: true, - field: "match_coverage", width: 120, }, matcher: { - headerName: "Matcher", + colId: "matcher", field: "matcher", + headerName: "Matcher", width: 120, }, matched_text: { - headerName: "Matched Text", + colId: "matched_text", field: "matched_text", + headerName: "Matched Text", cellRenderer: MatchedTextRenderer, width: 150, }, rule_url: { - headerName: "Rule", + colId: "rule_url", field: "rule_url", + headerName: "Rule", cellRenderer: UrlRenderer, cellRendererParams: { customTextField: "rule_identifier", @@ -70,8 +77,9 @@ const MATCH_COLS: MatchColumns = { width: 250, }, license_expression_spdx: { - headerName: "SPDX License expression", + colId: "license_expression_spdx", field: "license_expression_spdx", + headerName: "SPDX License expression", cellRenderer: MatchLicenseExpressionRenderer, cellRendererParams: { spdxLicense: true, @@ -99,4 +107,4 @@ export const LicenseClueMatchCols: ColDef[] = [ MATCH_COLS.matcher, MATCH_COLS.matched_text, MATCH_COLS.rule_url, -]; \ No newline at end of file +]; diff --git a/src/pages/Packages/Packages.tsx b/src/pages/Packages/Packages.tsx index 7684dc2a..4c600d65 100644 --- a/src/pages/Packages/Packages.tsx +++ b/src/pages/Packages/Packages.tsx @@ -81,7 +81,7 @@ const Packages = () => { db.sync.then(async () => { const packages = await db.getAllPackages(); const deps = await db.getAllDependencies(); - console.log("Raw Packages & deps", packages, deps); + // console.log("Raw Packages & deps", packages, deps); if (!packages.length && !deps.length) { console.log("No package or deps available"); setPackageGroups([]); diff --git a/src/pages/ScanInfo/ScanInfo.tsx b/src/pages/ScanInfo/ScanInfo.tsx index 45bd573f..274985f5 100644 --- a/src/pages/ScanInfo/ScanInfo.tsx +++ b/src/pages/ScanInfo/ScanInfo.tsx @@ -3,43 +3,12 @@ import React, { useEffect, useState } from "react"; // Maintained Fork of unmaintained but popular react-json-view import ReactJson from "@microlink/react-json-view"; -import { useWorkbenchDB } from "../../contexts/dbContext"; import InfoEntry from "./InfoEntry"; +import { useWorkbenchDB } from "../../contexts/dbContext"; +import { ScanInfo, parseIfValidJson, parseScanInfo } from "../../utils/parsers"; import "./scanInfo.css"; -interface ScanInfo { - tool_name: string; - tool_version: string; - notice: string; - duration: number; - options: [string, string][]; - input: string[]; - files_count: number; - output_format_version: string; - spdx_license_list_version: string; - operating_system: string; - cpu_architecture: string; - platform: string; - platform_version: string; - python_version: string; - workbench_version: string; - workbench_notice: string; - raw_header_content: string; -} - -function parseIfValidJson(str: unknown) { - if (typeof str !== "string") return null; - try { - const parsedObj = JSON.parse(str); - // Return only if it is an object & not a primitive value - if (Object(parsedObj) === parsedObj) return parsedObj; - return null; - } catch (e) { - return null; - } -} - const ScanInfo = () => { const workbenchDB = useWorkbenchDB(); const [parsedScanInfo, setParsedScanInfo] = useState(null); @@ -52,40 +21,7 @@ const ScanInfo = () => { db.sync.then(() => { db.getScanInfo().then((rawInfo) => { console.log("Raw scan info:", rawInfo); - const newParsedScanInfo: ScanInfo = { - tool_name: rawInfo.getDataValue("tool_name").toString({}) || "", - tool_version: rawInfo.getDataValue("tool_version").toString({}) || "", - notice: rawInfo.getDataValue("notice").toString({}) || "", - duration: Number(rawInfo.getDataValue("duration")), - options: - Object.entries( - parseIfValidJson(rawInfo.getDataValue("options")?.toString({})) || - [] - ) || [], - input: - parseIfValidJson(rawInfo.getDataValue("input")?.toString({})) || [], - files_count: Number(rawInfo.getDataValue("files_count")), - output_format_version: - rawInfo.getDataValue("output_format_version")?.toString({}) || "", - spdx_license_list_version: - rawInfo.getDataValue("spdx_license_list_version")?.toString({}) || - "", - operating_system: - rawInfo.getDataValue("operating_system")?.toString({}) || "", - cpu_architecture: - rawInfo.getDataValue("cpu_architecture")?.toString({}) || "", - platform: rawInfo.getDataValue("platform")?.toString({}) || "", - platform_version: - rawInfo.getDataValue("platform_version")?.toString({}) || "", - python_version: - rawInfo.getDataValue("python_version")?.toString({}) || "", - workbench_version: - rawInfo.getDataValue("workbench_version")?.toString({}) || "", - workbench_notice: - rawInfo.getDataValue("workbench_notice")?.toString({}) || "", - raw_header_content: - rawInfo.getDataValue("header_content")?.toString({}) || "", - }; + const newParsedScanInfo = parseScanInfo(rawInfo); console.log("Parsed scan info:", newParsedScanInfo); setParsedScanInfo(newParsedScanInfo); }); @@ -120,11 +56,11 @@ const ScanInfo = () => { 0} + show={parsedScanInfo.optionsList && parsedScanInfo.optionsList.length > 0} > - {parsedScanInfo.options.map(([key, value]) => ( + {parsedScanInfo.optionsList.map(([key, value]) => ( {typeof value !== "boolean" && } diff --git a/src/pages/ScanInfo/scanInfo.css b/src/pages/ScanInfo/scanInfo.css index ffba40c4..e069d845 100644 --- a/src/pages/ScanInfo/scanInfo.css +++ b/src/pages/ScanInfo/scanInfo.css @@ -28,6 +28,10 @@ .scan-info .options-table, .scan-info .options-table td { border: none; - padding: 0; + padding: 5; padding-right: 20px; +} + +.scan-info .options-table tr:not(:last-child) { + border-bottom: solid 1px #f1f1ff; } \ No newline at end of file diff --git a/src/utils/parsers.ts b/src/utils/parsers.ts new file mode 100644 index 00000000..619d5bcf --- /dev/null +++ b/src/utils/parsers.ts @@ -0,0 +1,76 @@ +import { Model } from "sequelize"; +import { HeaderAttributes } from "../services/models/header"; + +export function parseIfValidJson(str: unknown) { + if (typeof str !== "string") return null; + try { + const parsedObj = JSON.parse(str); + // Return only if it is an object & not a primitive value + if (Object(parsedObj) === parsedObj) return parsedObj; + return null; + } catch (e) { + return null; + } +} + +export interface ScanInfo { + tool_name: string; + tool_version: string; + notice: string; + duration: number; + optionsList: [string, unknown][]; + optionsMap: Map; + input: string[]; + files_count: number; + output_format_version: string; + spdx_license_list_version: string; + operating_system: string; + cpu_architecture: string; + platform: string; + platform_version: string; + python_version: string; + workbench_version: string; + workbench_notice: string; + raw_header_content: string; +} + +export function parseScanInfo(rawInfo: Model){ + const optionsList = Object.entries( + parseIfValidJson(rawInfo.getDataValue("options")?.toString({})) || + [] + ) || []; + const optionsMap = new Map(optionsList.map(([k,v]) => [k.substring(2), v])); + + const parsedScanInfo: ScanInfo = { + tool_name: rawInfo.getDataValue("tool_name").toString({}) || "", + tool_version: rawInfo.getDataValue("tool_version").toString({}) || "", + notice: rawInfo.getDataValue("notice").toString({}) || "", + duration: Number(rawInfo.getDataValue("duration")), + optionsList, + optionsMap, + input: + parseIfValidJson(rawInfo.getDataValue("input")?.toString({})) || [], + files_count: Number(rawInfo.getDataValue("files_count")), + output_format_version: + rawInfo.getDataValue("output_format_version")?.toString({}) || "", + spdx_license_list_version: + rawInfo.getDataValue("spdx_license_list_version")?.toString({}) || + "", + operating_system: + rawInfo.getDataValue("operating_system")?.toString({}) || "", + cpu_architecture: + rawInfo.getDataValue("cpu_architecture")?.toString({}) || "", + platform: rawInfo.getDataValue("platform")?.toString({}) || "", + platform_version: + rawInfo.getDataValue("platform_version")?.toString({}) || "", + python_version: + rawInfo.getDataValue("python_version")?.toString({}) || "", + workbench_version: + rawInfo.getDataValue("workbench_version")?.toString({}) || "", + workbench_notice: + rawInfo.getDataValue("workbench_notice")?.toString({}) || "", + raw_header_content: + rawInfo.getDataValue("header_content")?.toString({}) || "", + }; + return parsedScanInfo; +} \ No newline at end of file From aececb511e9af9d1ac453fff6ed5c65b65465f97 Mon Sep 17 00:00:00 2001 From: Omkar Phansopkar Date: Tue, 6 Jun 2023 23:16:52 +0530 Subject: [PATCH 03/12] Changed to punctuation & space insensitive diff implementation Signed-off-by: Omkar Phansopkar --- package-lock.json | 8 + package.json | 2 + .../LicenseEntity/MatchedTextContext.tsx | 142 +++++++++++++++++- .../LicenseEntity/MatchesTableCols.ts | 6 +- .../LicenseEntity/licenseEntity.css | 18 +++ .../Licenses/MatchedTextRenderer.tsx | 12 +- src/utils/text.ts | 71 +++++++++ 7 files changed, 249 insertions(+), 10 deletions(-) create mode 100644 src/utils/text.ts diff --git a/package-lock.json b/package-lock.json index 4ba1bd39..16e2b6f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "aws-sdk": "^2.1358.0", "bootstrap": "^5.2.1", "c3": "^0.7.20", + "diff": "^5.1.0", "electron-squirrel-startup": "^1.0.0", "file-loader": "^6.2.0", "font-awesome": "^4.7.0", @@ -69,6 +70,7 @@ "@svgr/webpack": "^6.4.0", "@types/c3": "^0.7.8", "@types/d3": "^7.4.0", + "@types/diff": "^5.0.3", "@types/jquery": "^3.5.14", "@types/node": "^18.8.0", "@types/react": "^18.0.21", @@ -5362,6 +5364,12 @@ "@types/ms": "*" } }, + "node_modules/@types/diff": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.0.3.tgz", + "integrity": "sha512-amrLbRqTU9bXMCc6uX0sWpxsQzRIo9z6MJPkH1pkez/qOxuqSZVuryJAWoBRq94CeG8JxY+VK4Le9HtjQR5T9A==", + "dev": true + }, "node_modules/@types/eslint": { "version": "8.21.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.1.tgz", diff --git a/package.json b/package.json index dc8d5f71..2593eda7 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@svgr/webpack": "^6.4.0", "@types/c3": "^0.7.8", "@types/d3": "^7.4.0", + "@types/diff": "^5.0.3", "@types/jquery": "^3.5.14", "@types/node": "^18.8.0", "@types/react": "^18.0.21", @@ -85,6 +86,7 @@ "aws-sdk": "^2.1358.0", "bootstrap": "^5.2.1", "c3": "^0.7.20", + "diff": "^5.1.0", "electron-squirrel-startup": "^1.0.0", "file-loader": "^6.2.0", "font-awesome": "^4.7.0", diff --git a/src/components/LicenseEntity/MatchedTextContext.tsx b/src/components/LicenseEntity/MatchedTextContext.tsx index 79a077c3..7c9c5dc6 100644 --- a/src/components/LicenseEntity/MatchedTextContext.tsx +++ b/src/components/LicenseEntity/MatchedTextContext.tsx @@ -2,11 +2,13 @@ import ReactDiffViewer, { DiffMethod, ReactDiffViewerStylesOverride, } from "react-diff-viewer-continued"; -import { Alert, Modal } from "react-bootstrap"; +import { diffWords, Change } from "diff"; +import { Alert, Col, Modal, Row } from "react-bootstrap"; import { TailSpin } from "react-loader-spinner"; import React, { createContext, useContext, useEffect, useState } from "react"; import { useWorkbenchDB } from "../../contexts/dbContext"; +import { normalizeString, splitDiffIntoLines } from "../../utils/text"; interface MatchedTextContextProperties { showDiffWindow: boolean; @@ -50,6 +52,8 @@ export const MatchedTextProvider = ( ) => { const { db } = useWorkbenchDB(); const [showDiffWindow, setShowDiffWindow] = useState(false); + const [diffs, setDiffs] = useState(null); + const [diffLines, setDiffLines] = useState(null); const [matchDetails, setMatchDetails] = useState<{ identifier: string | null; matched_text: string | null; @@ -73,7 +77,7 @@ export const MatchedTextProvider = ( db.getLicenseRuleReference(matchDetails.identifier, ["text"]).then( (ruleRef) => { - console.log(`For ${matchDetails.identifier}, Found ref:`, ruleRef); + // console.log(`For ${matchDetails.identifier}, Found ref:`, ruleRef); if (!ruleRef) { return setRuleDetails({ @@ -82,6 +86,53 @@ export const MatchedTextProvider = ( }); } + const ruleText = ruleRef.getDataValue("text")?.toString({}) || ""; + const matchedText = matchDetails.matched_text; + const diffsDetected = diffWords(ruleText, matchedText, { + ignoreCase: true, + ignoreWhitespace: true, + }); + + console.log("Raw diff", diffsDetected); + const normalizedDiffs: Change[] = diffsDetected.map((diff) => { + { + const changeDetected = Boolean(diff.added || diff.removed); + const normalizedValue = normalizeString(diff.value); + + // No change / Trivial diff + if (!changeDetected || normalizedValue.length === 0) { + return { + value: diff.value, + count: diff.count, + }; + } + + return { + ...(diff.added + ? { added: true } + : diff.removed + ? { removed: true } + : {}), + value: diff.value, + count: diff.count, + trimmedValue: normalizedValue, + }; + } + }); + const normalizedDiffLines = splitDiffIntoLines(normalizedDiffs); + setDiffs(normalizedDiffs); + setDiffLines(normalizedDiffLines); + + const additions = normalizedDiffs.filter((diff) => diff.added).length; + const deletions = normalizedDiffs.filter((diff) => diff.removed).length; + console.log( + `Normalized diff with ${additions} additions & ${deletions} deletions \n`, + { + normalizedDiffs, + diffLines: normalizedDiffLines, + } + ); + setRuleDetails({ processing: false, ruleText: ruleRef.getDataValue("text").toString({}), @@ -102,6 +153,7 @@ export const MatchedTextProvider = ( processing: false, }); setShowDiffWindow(false); + setDiffs(null); } return ( @@ -128,7 +180,8 @@ export const MatchedTextProvider = ( {props.children} 150 ? "xl" : "lg"} + size="xl" + // size={ruleDetails.ruleText?.length > 150 ? "xl" : "lg"} centered backdrop={true} show={showDiffWindow} @@ -151,9 +204,90 @@ export const MatchedTextProvider = ( ) : ( matchDetails.matched_text && - (ruleDetails.ruleText ? ( + (diffs ? ( <>
Score: {matchDetails.score} %
+ +
+
+                      {diffs.map((diff, diffIdx) => {
+                        return (
+                          
+                            {diff.added ? "" : diff.value}
+                          
+                        );
+                      })}
+                    
+ + +
+                      {diffs.map((diff, diffIdx) => {
+                        return (
+                          
+                            {diff.removed ? "" : diff.value}
+                          
+                        );
+                      })}
+                    
+ + +
New lined
+ + + {diffLines.map((diffLine, idx) => ( + // @TODO - Better key for this +

+ {diffLine.map((diff, diffIdx) => { + return ( + + {diff.added ? "" : diff.value} + + ); + })} +

+ ))} + + + {diffLines.map((diffLine, idx) => ( + // @TODO - Better key for this +

+ {diffLine.map((diff, diffIdx) => { + return ( + + {diff.removed ? "" : diff.value} + + ); + })} +

+ ))} + + { const { value, data } = props; const { openDiffWindow } = useMatchedTextContext(); + const trimmedText = useMemo( + () => trimStringWithEllipsis(value || "", 30), + [value] + ); + return ( { openDiffWindow(value, data.rule_identifier, data.start_line, data.score) } > - {value} + {trimmedText} ); }; -export default MatchedTextRenderer; \ No newline at end of file +export default MatchedTextRenderer; diff --git a/src/utils/text.ts b/src/utils/text.ts new file mode 100644 index 00000000..1b9a5027 --- /dev/null +++ b/src/utils/text.ts @@ -0,0 +1,71 @@ +import { Change } from "diff"; + +export function trimStringWithEllipsis( + str: string, + maxLengthInclusive: number +) { + if (str.length > maxLengthInclusive) { + return str.trimEnd().slice(0, maxLengthInclusive - 3) + "..."; + } + return str.trimEnd(); +} + +/** + * Removes punctuation & multiple spaces from given string. + */ +export function normalizeString(str: string) { + return str + .replace(/[.,/#!$%^&*;:[{}\]=\-_`~()]/g, "") + .replace(/\s{2,}/g, " ") + .trim(); +} + +export function splitDiffIntoLines(diffs: Change[]) { + const lines: Change[][] = [[]]; + + for (const diff of diffs) { + const splitLines = diff.value.split("\n"); + + // Handle \n at the beginning of diff + let idx = 0; + while (idx < splitLines.length && splitLines[idx].length === 0) { + lines.push([]); + idx++; + } + + const subLines = splitLines.slice(idx); + + // console.log("Splitlines", splitLines, ">>", "Sublines", subLines); + + if (subLines.length === 1) { + // Append to last line, without adding new line + lines[lines.length - 1].push({ + ...diff, + value: subLines[0], + }); + continue; + } + + for (const subLine of subLines) { + // Append to last line only if it is non-empty string & + if (subLine.length > 0) { + lines[lines.length - 1].push({ + ...diff, + value: subLine, + }); + lines.push([]); + } + + // Add newline for \n at first location in subLine or subLine is non empty string + // (subLine.length === 0 && subLine == subLines[0]) || + + // Add a new line for further iterations + // if ( + // subLine.length > 0 + // ) { + // } + } + } + + return lines; +} From d8605d7db3b86dee291e00f48c6e5260c0b85871 Mon Sep 17 00:00:00 2001 From: Omkar Phansopkar Date: Thu, 8 Jun 2023 16:53:37 +0530 Subject: [PATCH 04/12] Fixed inconsistent linebreak resolution in diff Signed-off-by: Omkar Phansopkar --- .../LicenseEntity/MatchedTextContext.tsx | 219 +++++++++++------- .../LicenseEntity/licenseEntity.css | 15 +- src/constants/routes.ts | 2 +- src/utils/text.ts | 45 ++-- 4 files changed, 158 insertions(+), 123 deletions(-) diff --git a/src/components/LicenseEntity/MatchedTextContext.tsx b/src/components/LicenseEntity/MatchedTextContext.tsx index 7c9c5dc6..da844fb6 100644 --- a/src/components/LicenseEntity/MatchedTextContext.tsx +++ b/src/components/LicenseEntity/MatchedTextContext.tsx @@ -8,7 +8,12 @@ import { TailSpin } from "react-loader-spinner"; import React, { createContext, useContext, useEffect, useState } from "react"; import { useWorkbenchDB } from "../../contexts/dbContext"; -import { normalizeString, splitDiffIntoLines } from "../../utils/text"; +import { + BelongsText, + DiffInfo, + normalizeString, + splitDiffIntoLines, +} from "../../utils/text"; interface MatchedTextContextProperties { showDiffWindow: boolean; @@ -52,8 +57,12 @@ export const MatchedTextProvider = ( ) => { const { db } = useWorkbenchDB(); const [showDiffWindow, setShowDiffWindow] = useState(false); - const [diffs, setDiffs] = useState(null); - const [diffLines, setDiffLines] = useState(null); + const [originalDiffLines, setOriginalDiffLines] = useState( + null + ); + const [modifiedDiffLines, setModifiedDiffLines] = useState( + null + ); const [matchDetails, setMatchDetails] = useState<{ identifier: string | null; matched_text: string | null; @@ -94,42 +103,91 @@ export const MatchedTextProvider = ( }); console.log("Raw diff", diffsDetected); - const normalizedDiffs: Change[] = diffsDetected.map((diff) => { - { - const changeDetected = Boolean(diff.added || diff.removed); - const normalizedValue = normalizeString(diff.value); + const normalizedDiffs: DiffInfo[] = diffsDetected.map( + (diff): DiffInfo => { + { + const changeDetected = Boolean(diff.added || diff.removed); + const normalizedValue = normalizeString(diff.value); + + // if (!changeDetected && normalizedValue.length === 0) { + // console.log(`Trivial diff at ${idx}`, { + // normalizedValue, + // value: diff.value, + // diff, + // }); + // } + + // No change / Trivial diff + if (!changeDetected || normalizedValue.length === 0) { + return { + value: diff.value, + count: diff.count, + belongsTo: diff.added + ? BelongsText.MODIFIED + : diff.removed + ? BelongsText.ORIGINAL + : BelongsText.BOTH, + }; + } - // No change / Trivial diff - if (!changeDetected || normalizedValue.length === 0) { return { + ...(diff.added + ? { added: true } + : diff.removed + ? { removed: true } + : {}), + belongsTo: diff.added + ? BelongsText.MODIFIED + : diff.removed + ? BelongsText.ORIGINAL + : BelongsText.BOTH, value: diff.value, count: diff.count, + trimmedValue: normalizedValue, }; } - - return { - ...(diff.added - ? { added: true } - : diff.removed - ? { removed: true } - : {}), - value: diff.value, - count: diff.count, - trimmedValue: normalizedValue, - }; } - }); - const normalizedDiffLines = splitDiffIntoLines(normalizedDiffs); - setDiffs(normalizedDiffs); - setDiffLines(normalizedDiffLines); + ); + + + const normalizedOriginalLines = splitDiffIntoLines( + normalizedDiffs.filter( + (diff) => + diff.belongsTo === BelongsText.BOTH || + diff.belongsTo === BelongsText.ORIGINAL + ) + ) + + + const normalizedModifiedLines = splitDiffIntoLines( + normalizedDiffs.filter( + (diff) => + diff.belongsTo === BelongsText.BOTH || + diff.belongsTo === BelongsText.MODIFIED + ) + ); + + setOriginalDiffLines(normalizedOriginalLines); + setModifiedDiffLines(normalizedModifiedLines); - const additions = normalizedDiffs.filter((diff) => diff.added).length; const deletions = normalizedDiffs.filter((diff) => diff.removed).length; + const finalDeletions = normalizedDiffs.filter( + (diff) => diff.belongsTo === BelongsText.ORIGINAL + ).length; + const additions = normalizedDiffs.filter((diff) => diff.added).length; + const finalAdditions = normalizedDiffs.filter( + (diff) => diff.belongsTo === BelongsText.MODIFIED + ).length; + console.log( - `Normalized diff with ${additions} additions & ${deletions} deletions \n`, + `Pure diff belongs Orig(${finalAdditions}) & Modified(${finalDeletions}) dels \n`, + `Valid ${additions} adds & ${deletions} dels \n`, { normalizedDiffs, - diffLines: normalizedDiffLines, + normalizedOriginalLines, + normalizedModifiedLines, + origText: ruleText, + modifiedText: matchedText, } ); @@ -153,7 +211,6 @@ export const MatchedTextProvider = ( processing: false, }); setShowDiffWindow(false); - setDiffs(null); } return ( @@ -204,88 +261,68 @@ export const MatchedTextProvider = ( ) : ( matchDetails.matched_text && - (diffs ? ( + ruleDetails.ruleText && + (originalDiffLines && modifiedDiffLines ? ( <>
Score: {matchDetails.score} %
-
-                      {diffs.map((diff, diffIdx) => {
-                        return (
-                          
-                            {diff.added ? "" : diff.value}
-                          
-                        );
-                      })}
-                    
- - -
-                      {diffs.map((diff, diffIdx) => {
-                        return (
-                          
-                            {diff.removed ? "" : diff.value}
-                          
-                        );
-                      })}
-                    
- - -
New lined
- - - {diffLines.map((diffLine, idx) => ( + {originalDiffLines.map((diffLine, idx) => ( // @TODO - Better key for this -

{diffLine.map((diff, diffIdx) => { return ( - - {diff.added ? "" : diff.value} - + {(() => { + console.log(`Render text --${diff.value}--`); + return ""; + })()} + {diff.value} + ); })} -

+ ))} - {diffLines.map((diffLine, idx) => ( - // @TODO - Better key for this -

- {diffLine.map((diff, diffIdx) => { - return ( - - {diff.removed ? "" : diff.value} - - ); - })} -

- ))} +
{key}{String(value)}
+ + {modifiedDiffLines.map((diffLine, idx) => ( + // @TODO - Better key for this + + + + + ))} + +
+ #{matchDetails.start_line + idx}. + + {diffLine.map((diff, diffIdx) => { + return ( +
+                                    {diff.value}
+                                  
+ ); + })} +
) : (
+ {/* @TODO - This is not working ? */} + {/* in liferay - lgpl2.1-plus */}
Score: {matchDetails.score} %
Matched Text:
{matchDetails.matched_text}
diff --git a/src/components/LicenseEntity/licenseEntity.css b/src/components/LicenseEntity/licenseEntity.css index e694986b..f2c44828 100644 --- a/src/components/LicenseEntity/licenseEntity.css +++ b/src/components/LicenseEntity/licenseEntity.css @@ -37,18 +37,19 @@ .matched-text-diff-modal .diff-line{ display: block; margin: 0; + white-space: pre; } -.matched-text-diff-modal .diff-line pre{ - margin: 0; - display: inline-flex; -} -/* .matched-text-diff-modal .snippet { +/* .matched-text-diff-modal .line-content { } */ -.matched-text-diff-modal .added-snippet { +.matched-text-diff-modal .diff-line .snippet{ + margin: 0; + display: inline-block; +} +.matched-text-diff-modal .diff-line .added-snippet { background-color: rgb(195, 255, 188); } -.matched-text-diff-modal .removed-snippet { +.matched-text-diff-modal .diff-line .removed-snippet { background-color: rgb(254, 203, 189); } \ No newline at end of file diff --git a/src/constants/routes.ts b/src/constants/routes.ts index 6490211e..4560dcfc 100644 --- a/src/constants/routes.ts +++ b/src/constants/routes.ts @@ -31,4 +31,4 @@ export const IMPORT_FALLBACK_ROUTES = [ ROUTES.SCAN_INFO, ]; -export const DEFAULT_ROUTE_ON_IMPORT = ROUTES.TABLE_VIEW; \ No newline at end of file +export const DEFAULT_ROUTE_ON_IMPORT = ROUTES.LICENSES; \ No newline at end of file diff --git a/src/utils/text.ts b/src/utils/text.ts index 1b9a5027..d79b43e5 100644 --- a/src/utils/text.ts +++ b/src/utils/text.ts @@ -20,8 +20,17 @@ export function normalizeString(str: string) { .trim(); } -export function splitDiffIntoLines(diffs: Change[]) { - const lines: Change[][] = [[]]; +export enum BelongsText { + ORIGINAL = "original", + MODIFIED = "modified", + BOTH = "both", +} +export interface DiffInfo extends Change { + belongsTo: BelongsText; + trimmedValue?: string; +} +export function splitDiffIntoLines(diffs: DiffInfo[]) { + const lines: DiffInfo[][] = [[]]; for (const diff of diffs) { const splitLines = diff.value.split("\n"); @@ -35,37 +44,23 @@ export function splitDiffIntoLines(diffs: Change[]) { const subLines = splitLines.slice(idx); - // console.log("Splitlines", splitLines, ">>", "Sublines", subLines); - - if (subLines.length === 1) { - // Append to last line, without adding new line - lines[lines.length - 1].push({ - ...diff, - value: subLines[0], - }); - continue; - } - for (const subLine of subLines) { - // Append to last line only if it is non-empty string & + // Append to last line only if it is non-empty string if (subLine.length > 0) { lines[lines.length - 1].push({ ...diff, value: subLine, }); - lines.push([]); - } - // Add newline for \n at first location in subLine or subLine is non empty string - // (subLine.length === 0 && subLine == subLines[0]) || - - // Add a new line for further iterations - // if ( - // subLine.length > 0 - // ) { - // } + // Create newline for intermittent newlines + // (ignore last subLine, it is continued in next line) + if (subLine != subLines[subLines.length - 1]) { + lines.push([]); + } + } } } - return lines; + // Filter out empty lines before returning; + return lines.filter((diffLine) => diffLine.length > 0); } From d2713ec87185daae8fbe8b55d94624d046063a0b Mon Sep 17 00:00:00 2001 From: Omkar Phansopkar Date: Thu, 8 Jun 2023 18:46:56 +0530 Subject: [PATCH 05/12] Fixed overflowing lines & completed UI for diff text Signed-off-by: Omkar Phansopkar --- .../LicenseEntity/MatchedTextContext.tsx | 116 ++++++++++-------- .../LicenseEntity/licenseEntity.css | 56 +++++++-- src/pages/LicenseInfoDash/LicenseInfoDash.tsx | 1 + 3 files changed, 109 insertions(+), 64 deletions(-) diff --git a/src/components/LicenseEntity/MatchedTextContext.tsx b/src/components/LicenseEntity/MatchedTextContext.tsx index da844fb6..851bc336 100644 --- a/src/components/LicenseEntity/MatchedTextContext.tsx +++ b/src/components/LicenseEntity/MatchedTextContext.tsx @@ -57,9 +57,7 @@ export const MatchedTextProvider = ( ) => { const { db } = useWorkbenchDB(); const [showDiffWindow, setShowDiffWindow] = useState(false); - const [originalDiffLines, setOriginalDiffLines] = useState( - null - ); + const [ruleDiffLines, setRuleDiffLines] = useState(null); const [modifiedDiffLines, setModifiedDiffLines] = useState( null ); @@ -149,17 +147,15 @@ export const MatchedTextProvider = ( } ); - - const normalizedOriginalLines = splitDiffIntoLines( + const normalizedRuleTextLines = splitDiffIntoLines( normalizedDiffs.filter( (diff) => diff.belongsTo === BelongsText.BOTH || diff.belongsTo === BelongsText.ORIGINAL ) - ) - + ); - const normalizedModifiedLines = splitDiffIntoLines( + const normalizedModifiedTextLines = splitDiffIntoLines( normalizedDiffs.filter( (diff) => diff.belongsTo === BelongsText.BOTH || @@ -167,8 +163,8 @@ export const MatchedTextProvider = ( ) ); - setOriginalDiffLines(normalizedOriginalLines); - setModifiedDiffLines(normalizedModifiedLines); + setRuleDiffLines(normalizedRuleTextLines); + setModifiedDiffLines(normalizedModifiedTextLines); const deletions = normalizedDiffs.filter((diff) => diff.removed).length; const finalDeletions = normalizedDiffs.filter( @@ -184,8 +180,8 @@ export const MatchedTextProvider = ( `Valid ${additions} adds & ${deletions} dels \n`, { normalizedDiffs, - normalizedOriginalLines, - normalizedModifiedLines, + normalizedOriginalLines: normalizedRuleTextLines, + normalizedModifiedLines: normalizedModifiedTextLines, origText: ruleText, modifiedText: matchedText, } @@ -262,38 +258,49 @@ export const MatchedTextProvider = ( ) : ( matchDetails.matched_text && ruleDetails.ruleText && - (originalDiffLines && modifiedDiffLines ? ( + (ruleDiffLines && modifiedDiffLines ? ( <>
Score: {matchDetails.score} %
- - {originalDiffLines.map((diffLine, idx) => ( - // @TODO - Better key for this -
- {diffLine.map((diff, diffIdx) => { - return ( -
-                              {(() => {
-                                console.log(`Render text --${diff.value}--`);
-                                return "";
-                              })()}
-                              {diff.value}
-                            
- ); - })} -
- ))} + + + + + + {ruleDiffLines.map((diffLine, idx) => ( + // @TODO - Better key for this + + + + ))} + +
+ Rule Text
+
+                                {diffLine.map((diff, diffIdx) => {
+                                  return (
+                                    
+                                      {diff.value}
+                                    
+                                  );
+                                })}
+                              
+
- - + +
+ + {modifiedDiffLines.map((diffLine, idx) => ( // @TODO - Better key for this @@ -302,22 +309,23 @@ export const MatchedTextProvider = ( className="diff-line" > ))} diff --git a/src/components/LicenseEntity/licenseEntity.css b/src/components/LicenseEntity/licenseEntity.css index f2c44828..b9720048 100644 --- a/src/components/LicenseEntity/licenseEntity.css +++ b/src/components/LicenseEntity/licenseEntity.css @@ -31,25 +31,61 @@ } .matched-text-diff-modal { - overflow: scroll; + overflow: auto; max-height: calc(100vh - 120px); } -.matched-text-diff-modal .diff-line{ - display: block; + +.matched-text-diff-modal .rule-text-section { + background-color: #f4feff; +} + +.matched-text-diff-modal .matched-text-section { + background-color: #fafff5; +} + +.matched-text-diff-modal .diff-table { + table-layout: fixed; + width: 100%; +} + +.matched-text-diff-modal .diff-table th { + padding: 15px; + padding-left: 5px; + font-size: 15px; + font-weight: 500; + background-color: #fafbfc; +} + +.matched-text-diff-modal .diff-line { + display: table-row-group; margin: 0; + vertical-align: top; white-space: pre; } -/* .matched-text-diff-modal .line-content { -} */ -.matched-text-diff-modal .diff-line .snippet{ +.matched-text-diff-modal .line-number { + margin-right: 15px; + padding-left: 10px; + padding-right: 10px; + opacity: 0.9; +} + +.matched-text-diff-modal .line-content { + padding-left: 10px; + padding-right: 10px; +} + +.matched-text-diff-modal .diff-line .line-content .snippet { margin: 0; display: inline-block; + white-space: pre-wrap; + line-height: 20px; } -.matched-text-diff-modal .diff-line .added-snippet { - background-color: rgb(195, 255, 188); + +.matched-text-diff-modal .diff-line .line-content .removed-snippet { + background-color: #fdb8c0; } -.matched-text-diff-modal .diff-line .removed-snippet { - background-color: rgb(254, 203, 189); +.matched-text-diff-modal .diff-line .line-content .added-snippet { + background-color: #acf2bd; } \ No newline at end of file diff --git a/src/pages/LicenseInfoDash/LicenseInfoDash.tsx b/src/pages/LicenseInfoDash/LicenseInfoDash.tsx index 4b3e3d77..b242527a 100644 --- a/src/pages/LicenseInfoDash/LicenseInfoDash.tsx +++ b/src/pages/LicenseInfoDash/LicenseInfoDash.tsx @@ -60,6 +60,7 @@ const LicenseInfoDash = () => { ) ) .filter((detections) => detections.length); + setScanData((oldScanData) => ({ ...oldScanData, totalLicenseFiles: filesWithDetections.length, From 76e91fd0e76a0587b8a8fc42c4a84dbb833f772e Mon Sep 17 00:00:00 2001 From: Omkar Phansopkar Date: Mon, 12 Jun 2023 00:37:57 +0530 Subject: [PATCH 06/12] Fallback for Unresolved rule-reference & finalised match diff viewer Signed-off-by: Omkar Phansopkar --- .../LicenseEntity/MatchedTextContext.tsx | 193 +++++------------- .../Licenses/MatchedTextRenderer.tsx | 7 +- src/services/workbenchDB.ts | 8 +- src/utils/text.ts | 38 ++++ 4 files changed, 98 insertions(+), 148 deletions(-) diff --git a/src/components/LicenseEntity/MatchedTextContext.tsx b/src/components/LicenseEntity/MatchedTextContext.tsx index 851bc336..eb28b4ce 100644 --- a/src/components/LicenseEntity/MatchedTextContext.tsx +++ b/src/components/LicenseEntity/MatchedTextContext.tsx @@ -1,7 +1,3 @@ -import ReactDiffViewer, { - DiffMethod, - ReactDiffViewerStylesOverride, -} from "react-diff-viewer-continued"; import { diffWords, Change } from "diff"; import { Alert, Col, Modal, Row } from "react-bootstrap"; import { TailSpin } from "react-loader-spinner"; @@ -11,7 +7,7 @@ import { useWorkbenchDB } from "../../contexts/dbContext"; import { BelongsText, DiffInfo, - normalizeString, + normalizeAndCategorizeDiffs, splitDiffIntoLines, } from "../../utils/text"; @@ -21,7 +17,7 @@ interface MatchedTextContextProperties { newMatchedText: string, ruleRefIdentifier: string, start_line: number, - score: number + coverage: number ) => void; } @@ -34,24 +30,6 @@ const MatchedTextContext = createContext( defaultMatchedTextContextValue ); -const DIFF_VIEWER_STYLES: ReactDiffViewerStylesOverride = { - diffRemoved: { - backgroundColor: "#e8faff", - }, - diffAdded: { - backgroundColor: "#f8fff1", - }, - diffChanged: { - backgroundColor: "#f8fff1", - }, - variables: { - light: { - codeFoldGutterBackground: "#6F767E", - codeFoldBackground: "#E2E4E5", - }, - }, -}; - export const MatchedTextProvider = ( props: React.PropsWithChildren> ) => { @@ -65,12 +43,12 @@ export const MatchedTextProvider = ( identifier: string | null; matched_text: string | null; start_line: number; - score: number; + coverage: number; }>({ identifier: null, matched_text: null, start_line: 1, - score: 0, + coverage: 0, }); const [ruleDetails, setRuleDetails] = useState<{ @@ -84,8 +62,6 @@ export const MatchedTextProvider = ( db.getLicenseRuleReference(matchDetails.identifier, ["text"]).then( (ruleRef) => { - // console.log(`For ${matchDetails.identifier}, Found ref:`, ruleRef); - if (!ruleRef) { return setRuleDetails({ processing: false, @@ -95,57 +71,13 @@ export const MatchedTextProvider = ( const ruleText = ruleRef.getDataValue("text")?.toString({}) || ""; const matchedText = matchDetails.matched_text; - const diffsDetected = diffWords(ruleText, matchedText, { + const rawDiffs = diffWords(ruleText, matchedText, { ignoreCase: true, ignoreWhitespace: true, }); - console.log("Raw diff", diffsDetected); - const normalizedDiffs: DiffInfo[] = diffsDetected.map( - (diff): DiffInfo => { - { - const changeDetected = Boolean(diff.added || diff.removed); - const normalizedValue = normalizeString(diff.value); - - // if (!changeDetected && normalizedValue.length === 0) { - // console.log(`Trivial diff at ${idx}`, { - // normalizedValue, - // value: diff.value, - // diff, - // }); - // } - - // No change / Trivial diff - if (!changeDetected || normalizedValue.length === 0) { - return { - value: diff.value, - count: diff.count, - belongsTo: diff.added - ? BelongsText.MODIFIED - : diff.removed - ? BelongsText.ORIGINAL - : BelongsText.BOTH, - }; - } - - return { - ...(diff.added - ? { added: true } - : diff.removed - ? { removed: true } - : {}), - belongsTo: diff.added - ? BelongsText.MODIFIED - : diff.removed - ? BelongsText.ORIGINAL - : BelongsText.BOTH, - value: diff.value, - count: diff.count, - trimmedValue: normalizedValue, - }; - } - } - ); + const normalizedDiffs: DiffInfo[] = + normalizeAndCategorizeDiffs(rawDiffs); const normalizedRuleTextLines = splitDiffIntoLines( normalizedDiffs.filter( @@ -165,28 +97,6 @@ export const MatchedTextProvider = ( setRuleDiffLines(normalizedRuleTextLines); setModifiedDiffLines(normalizedModifiedTextLines); - - const deletions = normalizedDiffs.filter((diff) => diff.removed).length; - const finalDeletions = normalizedDiffs.filter( - (diff) => diff.belongsTo === BelongsText.ORIGINAL - ).length; - const additions = normalizedDiffs.filter((diff) => diff.added).length; - const finalAdditions = normalizedDiffs.filter( - (diff) => diff.belongsTo === BelongsText.MODIFIED - ).length; - - console.log( - `Pure diff belongs Orig(${finalAdditions}) & Modified(${finalDeletions}) dels \n`, - `Valid ${additions} adds & ${deletions} dels \n`, - { - normalizedDiffs, - normalizedOriginalLines: normalizedRuleTextLines, - normalizedModifiedLines: normalizedModifiedTextLines, - origText: ruleText, - modifiedText: matchedText, - } - ); - setRuleDetails({ processing: false, ruleText: ruleRef.getDataValue("text").toString({}), @@ -196,17 +106,22 @@ export const MatchedTextProvider = ( }, [matchDetails]); function closeDiffWindow() { - setMatchDetails({ - matched_text: null, - identifier: null, - start_line: 0, - score: 0, - }); - setRuleDetails({ - ruleText: null, - processing: false, - }); setShowDiffWindow(false); + // Prevents showing fallbacks inside modal, for the transition period + setTimeout(() => { + setRuleDiffLines(null); + setModifiedDiffLines(null); + setMatchDetails({ + matched_text: null, + identifier: null, + start_line: 0, + coverage: 0, + }); + setRuleDetails({ + ruleText: null, + processing: false, + }); + }, 200); } return ( @@ -217,14 +132,14 @@ export const MatchedTextProvider = ( matched_text: string, rule_identifier: string, start_line: number, - score: number + coverage: number ) => { if (!matched_text) return; setMatchDetails({ identifier: rule_identifier, matched_text: matched_text, start_line, - score, + coverage: coverage, }); setShowDiffWindow(true); }, @@ -255,18 +170,19 @@ export const MatchedTextProvider = ( wrapperClass="d-inline-block mx-4" /> - ) : ( - matchDetails.matched_text && - ruleDetails.ruleText && - (ruleDiffLines && modifiedDiffLines ? ( + ) : matchDetails.matched_text && ruleDetails.ruleText ? ( + ruleDiffLines && + modifiedDiffLines && ( <> -
Score: {matchDetails.score} %
+
Coverage: {matchDetails.coverage} %
+ Matched Text
- #{matchDetails.start_line + idx}. + {matchDetails.start_line + idx}. - {diffLine.map((diff, diffIdx) => { - return ( -
-                                    {diff.value}
-                                  
- ); - })} +
+                                {diffLine.map((diff, diffIdx) => {
+                                  return (
+                                    
+                                      {diff.value}
+                                    
+                                  );
+                                })}
+                              
- - + + + + + {ruleDiffLines.map((diffLine, idx) => ( // @TODO - Better key for this @@ -298,9 +214,11 @@ export const MatchedTextProvider = (
- Rule Text
Rule Text
- - + + + + + {modifiedDiffLines.map((diffLine, idx) => ( // @TODO - Better key for this @@ -333,31 +251,18 @@ export const MatchedTextProvider = (
- Matched Text
Matched Text
- - ) : ( -
- {/* @TODO - This is not working ? */} - {/* in liferay - lgpl2.1-plus */} -
Score: {matchDetails.score} %
-
Matched Text:
-
{matchDetails.matched_text}
- - Couldn't find License Rule Reference for specified identifier - - {matchDetails.identifier} - -
- )) + ) + ) : ( +
+
Coverage: {matchDetails.coverage} %
+
Matched Text:
+
{matchDetails.matched_text}
+ + Couldn't find License Rule Reference for specified identifier -{" "} + {matchDetails.identifier} + +
)} diff --git a/src/pages/TableView/CustomCellRenderers/Licenses/MatchedTextRenderer.tsx b/src/pages/TableView/CustomCellRenderers/Licenses/MatchedTextRenderer.tsx index 3fc658d8..b7e959c3 100644 --- a/src/pages/TableView/CustomCellRenderers/Licenses/MatchedTextRenderer.tsx +++ b/src/pages/TableView/CustomCellRenderers/Licenses/MatchedTextRenderer.tsx @@ -21,7 +21,12 @@ const MatchedTextRenderer = (props: MatchedTextRendererProps) => { key={value} onClick={() => value && - openDiffWindow(value, data.rule_identifier, data.start_line, data.score) + openDiffWindow( + value, + data.rule_identifier, + data.start_line, + data.match_coverage + ) } > {trimmedText} diff --git a/src/services/workbenchDB.ts b/src/services/workbenchDB.ts index dcc4a2cd..6d8af9ca 100644 --- a/src/services/workbenchDB.ts +++ b/src/services/workbenchDB.ts @@ -170,7 +170,9 @@ export class WorkbenchDB { } getAllLicenseDetections() { - return this.sync.then((db) => db.LicenseDetections.findAll()); + return this.sync.then((db) => + db.LicenseDetections.findAll({ order: ["license_expression"] }) + ); } getAllLicenseClues() { @@ -251,7 +253,7 @@ export class WorkbenchDB { roots.push(file); } - // @TODO - Trying to fix rc-tree showing file icon instead of directory https://github.com/nexB/scancode-workbench/issues/542 + // @TODO - Trial to fix rc-tree showing file icon instead of directory https://github.com/nexB/scancode-workbench/issues/542 // fileList.forEach(file => { // if(file.getDataValue('type').toString({}) === 'directory' && !file.children){ // file.children=[]; @@ -298,7 +300,7 @@ export class WorkbenchDB { return new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-this-alias - const primaryPromise = this; // @TODO - Remove this + const primaryPromise = this; let batchCount = 0; diff --git a/src/utils/text.ts b/src/utils/text.ts index d79b43e5..eff92f07 100644 --- a/src/utils/text.ts +++ b/src/utils/text.ts @@ -20,6 +20,44 @@ export function normalizeString(str: string) { .trim(); } +export function normalizeAndCategorizeDiffs(diffs: Change[]) { + return diffs.map((diff): DiffInfo => { + { + const changeDetected = Boolean(diff.added || diff.removed); + const normalizedValue = normalizeString(diff.value); + + // No change / Trivial diff + if (!changeDetected || normalizedValue.length === 0) { + return { + value: diff.value, + count: diff.count, + belongsTo: diff.added + ? BelongsText.MODIFIED + : diff.removed + ? BelongsText.ORIGINAL + : BelongsText.BOTH, + }; + } + + return { + ...(diff.added + ? { added: true } + : diff.removed + ? { removed: true } + : {}), + belongsTo: diff.added + ? BelongsText.MODIFIED + : diff.removed + ? BelongsText.ORIGINAL + : BelongsText.BOTH, + value: diff.value, + count: diff.count, + trimmedValue: normalizedValue, + }; + } + }); +} + export enum BelongsText { ORIGINAL = "original", MODIFIED = "modified", From c45e696d164103bda94f9b457f898cc940f36a9f Mon Sep 17 00:00:00 2001 From: Omkar Phansopkar Date: Mon, 12 Jun 2023 15:49:57 +0530 Subject: [PATCH 07/12] Rollback to default route on import Signed-off-by: Omkar Phansopkar --- src/constants/routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants/routes.ts b/src/constants/routes.ts index 4560dcfc..6490211e 100644 --- a/src/constants/routes.ts +++ b/src/constants/routes.ts @@ -31,4 +31,4 @@ export const IMPORT_FALLBACK_ROUTES = [ ROUTES.SCAN_INFO, ]; -export const DEFAULT_ROUTE_ON_IMPORT = ROUTES.LICENSES; \ No newline at end of file +export const DEFAULT_ROUTE_ON_IMPORT = ROUTES.TABLE_VIEW; \ No newline at end of file From d10366c40312b3fa22bc4df10941f753abf8cd28 Mon Sep 17 00:00:00 2001 From: Omkar Phansopkar Date: Mon, 12 Jun 2023 22:23:18 +0530 Subject: [PATCH 08/12] Removed unused deps Signed-off-by: Omkar Phansopkar --- package-lock.json | 176 ++++++--------------------- package.json | 6 +- src/components/FileTree/FileTree.tsx | 29 +---- src/pages/ChartView/ChartView.tsx | 20 ++- src/pages/Packages/Packages.tsx | 1 - 5 files changed, 52 insertions(+), 180 deletions(-) diff --git a/package-lock.json b/package-lock.json index 16e2b6f9..87a80b0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "aws-sdk": "^2.1358.0", "bootstrap": "^5.2.1", "c3": "^0.7.20", + "chart.js": "^4.3.0", "diff": "^5.1.0", "electron-squirrel-startup": "^1.0.0", "file-loader": "^6.2.0", @@ -40,8 +41,7 @@ "rc-tree": "^5.7.0", "react": "^18.2.0", "react-bootstrap": "^2.5.0", - "react-chartjs-2": "^4.3.1", - "react-diff-viewer-continued": "^3.2.6", + "react-chartjs-2": "^5.2.0", "react-dom": "^18.2.0", "react-loader-spinner": "^5.3.4", "react-pro-sidebar": "^0.7.1", @@ -51,8 +51,6 @@ "react-tooltip": "^5.10.4", "sequelize": "^6.23.2", "sequelize-cli": "^6.5.1", - "spdx-correct": "^3.1.1", - "spdx-license-ids": "^3.0.12", "sqlite3": "^5.1.6", "url": "^0.11.0", "url-loader": "^4.1.1", @@ -3187,61 +3185,6 @@ "node": ">=10" } }, - "node_modules/@emotion/babel-plugin": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", - "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/serialize": "^1.1.2", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/babel-plugin/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "dependencies": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/css": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.11.0.tgz", - "integrity": "sha512-m4g6nKzZyiKyJ3WOfdwrBdcujVcpaScIWHAnyNKPm/A/xJKwfXPfQAbEVi1kgexWTDakmg+r2aDj0KvnMTo4oQ==", - "dependencies": { - "@emotion/babel-plugin": "^11.11.0", - "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.2", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1" - } - }, - "node_modules/@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" - }, "node_modules/@emotion/is-prop-valid": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", @@ -3255,28 +3198,6 @@ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, - "node_modules/@emotion/serialize": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", - "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", - "dependencies": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", - "csstype": "^3.0.2" - } - }, - "node_modules/@emotion/serialize/node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" - }, - "node_modules/@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" - }, "node_modules/@emotion/stylis": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", @@ -3287,16 +3208,6 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, - "node_modules/@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" - }, - "node_modules/@emotion/weak-memoize": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" - }, "node_modules/@eslint/eslintrc": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.0.tgz", @@ -3941,6 +3852,11 @@ "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -5555,7 +5471,8 @@ "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true }, "node_modules/@types/plist": { "version": "3.0.2", @@ -6996,6 +6913,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -7656,6 +7574,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "engines": { "node": ">=6" } @@ -7761,10 +7680,15 @@ } }, "node_modules/chart.js": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz", - "integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==", - "peer": true + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.0.tgz", + "integrity": "sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=7" + } }, "node_modules/check-types": { "version": "11.2.2", @@ -8306,7 +8230,8 @@ "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true }, "node_modules/cookie": { "version": "0.5.0", @@ -8423,6 +8348,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -10691,6 +10617,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -10890,6 +10817,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "devOptional": true, "engines": { "node": ">=10" }, @@ -12114,11 +12042,6 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -13409,6 +13332,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -13568,7 +13492,8 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true }, "node_modules/is-bigint": { "version": "1.0.4", @@ -15514,7 +15439,8 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true }, "node_modules/listr2": { "version": "5.0.7", @@ -15983,11 +15909,6 @@ "node": ">= 4.0.0" } }, - "node_modules/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" - }, "node_modules/memoizee": { "version": "0.4.15", "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", @@ -17042,6 +16963,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -17065,6 +16987,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -17153,6 +17076,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, "engines": { "node": ">=8" } @@ -19127,11 +19051,11 @@ } }, "node_modules/react-chartjs-2": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.3.1.tgz", - "integrity": "sha512-5i3mjP6tU7QSn0jvb8I4hudTzHJqS8l00ORJnVwI2sYu0ihpj83Lv2YzfxunfxTZkscKvZu2F2w9LkwNBhj6xA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", + "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==", "peerDependencies": { - "chart.js": "^3.5.0", + "chart.js": "^4.1.1", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, @@ -19276,25 +19200,6 @@ "node": ">=6" } }, - "node_modules/react-diff-viewer-continued": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/react-diff-viewer-continued/-/react-diff-viewer-continued-3.2.6.tgz", - "integrity": "sha512-GrzyqQnjIMoej+jMjWvtVSsQqhXgzEGqpXlJ2dAGfOk7Q26qcm8Gu6xtI430PBUyZsERe8BJSQf+7VZZo8IBNQ==", - "dependencies": { - "@emotion/css": "^11.10.5", - "classnames": "^2.3.1", - "diff": "^5.1.0", - "memoize-one": "^6.0.0", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">= 8" - }, - "peerDependencies": { - "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -20449,6 +20354,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "engines": { "node": ">=4" } @@ -22174,11 +22080,6 @@ "postcss": "^8.2.15" } }, - "node_modules/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" - }, "node_modules/sudo-prompt": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", @@ -24388,6 +24289,7 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, "engines": { "node": ">= 6" } diff --git a/package.json b/package.json index 2593eda7..67ec1914 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "aws-sdk": "^2.1358.0", "bootstrap": "^5.2.1", "c3": "^0.7.20", + "chart.js": "^4.3.0", "diff": "^5.1.0", "electron-squirrel-startup": "^1.0.0", "file-loader": "^6.2.0", @@ -102,8 +103,7 @@ "rc-tree": "^5.7.0", "react": "^18.2.0", "react-bootstrap": "^2.5.0", - "react-chartjs-2": "^4.3.1", - "react-diff-viewer-continued": "^3.2.6", + "react-chartjs-2": "^5.2.0", "react-dom": "^18.2.0", "react-loader-spinner": "^5.3.4", "react-pro-sidebar": "^0.7.1", @@ -113,8 +113,6 @@ "react-tooltip": "^5.10.4", "sequelize": "^6.23.2", "sequelize-cli": "^6.5.1", - "spdx-correct": "^3.1.1", - "spdx-license-ids": "^3.0.12", "sqlite3": "^5.1.6", "url": "^0.11.0", "url-loader": "^4.1.1", diff --git a/src/components/FileTree/FileTree.tsx b/src/components/FileTree/FileTree.tsx index d0fe5b0f..5f0aefb1 100644 --- a/src/components/FileTree/FileTree.tsx +++ b/src/components/FileTree/FileTree.tsx @@ -1,13 +1,12 @@ import RcTree from "rc-tree"; import { DataNode, Key } from "rc-tree/lib/interface"; import React, { useEffect, useState } from "react"; -import { Element, scroller } from "react-scroll"; +import { Element } from "react-scroll"; import EllipticLoader from "../EllipticLoader"; import { PathType, useWorkbenchDB } from "../../contexts/dbContext"; import SwitcherIcon from "./SwitcherIcon"; -import { scrollToDomElement } from "../../utils/dom"; import "./FileTree.css"; @@ -26,29 +25,18 @@ const FileTree = (props: React.HTMLProps) => { useEffect(() => { setExpandedKeys((keys) => { - // console.log( - // "Adding", - // currentPath.substring(0, currentPath.lastIndexOf("/")), - // [...keys, currentPath.substring(0, currentPath.lastIndexOf("/"))] - // ); return [...keys, currentPath.substring(0, currentPath.lastIndexOf("/"))]; }); if (currentPath.length) { setTimeout(() => { const targetNode = document.getElementsByName(currentPath)[0]; if (targetNode) { - // scrollToDomElement(targetNode, { yOffset: -50 }); targetNode.scrollIntoView({ behavior: "smooth", block: "start", inline: "start", }); } - // scroller.scrollTo(currentPath, { - // duration: 0, - // delay: 30, - // smooth: "easeInOutQuart", - // }); }, 500); } }, [currentPath]); @@ -64,13 +52,8 @@ const FileTree = (props: React.HTMLProps) => { function wrapNode(node: DataNode) { const key = String(node.key); node.title = ( - + {String(node.title)} - {/* {String(node.title)} */} ); node.children?.forEach(wrapNode); @@ -98,12 +81,6 @@ const FileTree = (props: React.HTMLProps) => { ); } - // console.log("Filetree", { - // currentPath, - // selectedKeys: [currentPath], - // defaultExpanded: [currentPath.substring(0, currentPath.lastIndexOf("/"))], - // }); - return (
) => { const newKeys = keys.filter( (key) => !String(key).startsWith(String(node.key)) ); - console.log("On collapse keys", newKeys); setExpandedKeys(newKeys); } else { - console.log("New expanded", keys); setExpandedKeys(keys); } }} diff --git a/src/pages/ChartView/ChartView.tsx b/src/pages/ChartView/ChartView.tsx index b2adde57..5fa9d273 100644 --- a/src/pages/ChartView/ChartView.tsx +++ b/src/pages/ChartView/ChartView.tsx @@ -23,15 +23,6 @@ import { useWorkbenchDB } from "../../contexts/dbContext"; import { formatBarchartData, getAttributeValues } from "../../utils/bar"; import { FlatFileAttributes } from "../../services/models/flatFile"; -ChartJS.register( - CategoryScale, - LinearScale, - BarElement, - Title, - Tooltip, - Legend -); - import "./chartView.css"; const BAR_HEIGHT = 30; @@ -42,6 +33,15 @@ const directoryAttributes = [ "package_data_primary_language", ]; +ChartJS.register( + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip, + Legend +); + const ChartView = () => { const { importedSqliteFilePath, db, initialized, currentPath } = useWorkbenchDB(); @@ -130,14 +130,12 @@ const ChartView = () => { scales: { x: { grid: { - drawBorder: false, drawTicks: false, display: false, }, }, y: { grid: { - drawBorder: true, drawTicks: true, display: false, }, diff --git a/src/pages/Packages/Packages.tsx b/src/pages/Packages/Packages.tsx index 4c600d65..39d7e2a7 100644 --- a/src/pages/Packages/Packages.tsx +++ b/src/pages/Packages/Packages.tsx @@ -10,7 +10,6 @@ import NoDataFallback from "../../components/NoDataSection"; import DependencyEntity from "../../components/PackagesEntityDetails/DependencyEntity"; import PackageEntity from "../../components/PackagesEntityDetails/PackageEntity"; import { QUERY_KEYS } from "../../constants/params"; -// import { PackageURL } from 'packageurl-js'; import { useWorkbenchDB } from "../../contexts/dbContext"; import { DEPENDENCY_SCOPES } from "../../services/models/dependencies"; import { From 3437025c12c56581f37b51a5b32d60fcb0ff6568 Mon Sep 17 00:00:00 2001 From: Omkar Phansopkar Date: Fri, 16 Jun 2023 21:25:23 +0530 Subject: [PATCH 09/12] Handled synthetic rules & availability of rule and match texts Signed-off-by: Omkar Phansopkar --- .../LicenseEntity/LicenseEntity.tsx | 19 +++---- .../LicenseEntity/MatchedTextContext.tsx | 46 ++++++++++++++-- src/contexts/dbContext.tsx | 35 ++++++------ src/index.html | 1 - src/pages/LicenseInfoDash/LicenseInfoDash.tsx | 2 +- src/pages/ScanInfo/ScanInfo.tsx | 53 +++++++------------ src/utils/parsers.ts | 23 +++++++- 7 files changed, 110 insertions(+), 69 deletions(-) diff --git a/src/components/LicenseEntity/LicenseEntity.tsx b/src/components/LicenseEntity/LicenseEntity.tsx index f2e24b0e..b981e899 100644 --- a/src/components/LicenseEntity/LicenseEntity.tsx +++ b/src/components/LicenseEntity/LicenseEntity.tsx @@ -15,7 +15,7 @@ import { } from "./MatchesTableCols"; import { MatchedTextProvider } from "./MatchedTextContext"; import { useWorkbenchDB } from "../../contexts/dbContext"; -import { parseScanInfo } from "../../utils/parsers"; +import { ScanOptionKeys } from "../../utils/parsers"; import { ColumnApi } from "ag-grid-community"; import "./licenseEntity.css"; @@ -26,20 +26,17 @@ interface LicenseDetectionEntityProps { } const LicenseEntity = (props: LicenseDetectionEntityProps) => { const { activeLicense } = props; - const { db } = useWorkbenchDB(); + const { scanInfo } = useWorkbenchDB(); const [matchesTableColumnApi, setMatchesTableColumnApi] = useState(null); useEffect(() => { - if (!db || !matchesTableColumnApi) return; - db.getScanInfo().then((rawScanInfo) => { - const scanInfo = parseScanInfo(rawScanInfo); - matchesTableColumnApi.setColumnVisible( - MATCH_COLS.matched_text.colId, - Boolean(scanInfo.optionsMap.get("license-text")) - ); - }); - }, [db, matchesTableColumnApi]); + if (!scanInfo || !matchesTableColumnApi) return; + matchesTableColumnApi.setColumnVisible( + MATCH_COLS.matched_text.colId, + Boolean(scanInfo.optionsMap.get(ScanOptionKeys.LICENSE_TEXT)) + ); + }, [scanInfo, matchesTableColumnApi]); const license = activeLicense?.license; const matches = activeLicense?.license?.matches; diff --git a/src/components/LicenseEntity/MatchedTextContext.tsx b/src/components/LicenseEntity/MatchedTextContext.tsx index eb28b4ce..e52a1fbe 100644 --- a/src/components/LicenseEntity/MatchedTextContext.tsx +++ b/src/components/LicenseEntity/MatchedTextContext.tsx @@ -10,6 +10,13 @@ import { normalizeAndCategorizeDiffs, splitDiffIntoLines, } from "../../utils/text"; +import { ScanOptionKeys } from "../../utils/parsers"; + +const SYNTHETIC_RULE_PREFIXES = [ + "spdx-license-identifier", + "license-detection-unknown", + "package-manifest-unknown", +]; interface MatchedTextContextProperties { showDiffWindow: boolean; @@ -33,7 +40,7 @@ const MatchedTextContext = createContext( export const MatchedTextProvider = ( props: React.PropsWithChildren> ) => { - const { db } = useWorkbenchDB(); + const { db, scanInfo } = useWorkbenchDB(); const [showDiffWindow, setShowDiffWindow] = useState(false); const [ruleDiffLines, setRuleDiffLines] = useState(null); const [modifiedDiffLines, setModifiedDiffLines] = useState( @@ -124,6 +131,12 @@ export const MatchedTextProvider = ( }, 200); } + const isSyntheticRule = + matchDetails.identifier && + SYNTHETIC_RULE_PREFIXES.some((prefix) => + matchDetails.identifier.startsWith(prefix) + ); + return ( Coverage: {matchDetails.coverage} %
Matched Text:
{matchDetails.matched_text}
- - Couldn't find License Rule Reference for specified identifier -{" "} - {matchDetails.identifier} - + {isSyntheticRule ? ( + + Rule text is not available for synthetic rules
+ Current rule - {matchDetails.identifier} +
+ ) : scanInfo.optionsMap.get(ScanOptionKeys.LICENSE_REFERENCES) ? ( + + {/* + Unable to find reference, although license-references option is enabled in scan + (Edge case - If there's a bug in sct or detecting synthetic prefixes) + */} + Couldn't find License Rule Reference for specified identifier + - {matchDetails.identifier} + + ) : ( + + Please use{" "} + + --license-references + {" "} + CLI option with your scan to see the rule text, and the diff + between rule/matched text + + )}
)} diff --git a/src/contexts/dbContext.tsx b/src/contexts/dbContext.tsx index 646db164..fc718fa7 100644 --- a/src/contexts/dbContext.tsx +++ b/src/contexts/dbContext.tsx @@ -23,6 +23,7 @@ import { DEFAULT_ROUTE_ON_IMPORT, ROUTES } from "../constants/routes"; import { AddEntry, GetHistory, RemoveEntry } from "../services/historyStore"; import { WorkbenchDB } from "../services/workbenchDB"; import { isSchemaChanged } from "../utils/checks"; +import { ScanInfo, parseScanInfo } from "../utils/parsers"; const { version: workbenchVersion } = packageJson; const { ipcRenderer } = electron; @@ -32,6 +33,7 @@ interface BasicValueState { db: WorkbenchDB | null; initialized: boolean; importedSqliteFilePath: string | null; + scanInfo: ScanInfo | null; } interface WorkbenchContextProperties extends BasicValueState { currentPath: string | null; @@ -49,13 +51,13 @@ interface WorkbenchContextProperties extends BasicValueState { updateLoadingStatus: React.Dispatch>; updateCurrentPath: (newPath: string, type: PathType) => void; goToFileInTableView: (path: string) => void; - updateWorkbenchDB: (db: WorkbenchDB, sqliteFilePath: string) => void; } export const defaultWorkbenchContextValue: WorkbenchContextProperties = { db: null, initialized: false, importedSqliteFilePath: null, + scanInfo: null, loadingStatus: null, currentPath: null, currentPathType: "directory", @@ -67,7 +69,6 @@ export const defaultWorkbenchContextValue: WorkbenchContextProperties = { abortImport: () => null, updateCurrentPath: () => null, goToFileInTableView: () => null, - updateWorkbenchDB: () => null, }; const WorkbenchContext = createContext( @@ -84,6 +85,7 @@ export const WorkbenchDBProvider = ( db: null, initialized: false, importedSqliteFilePath: null, + scanInfo: null, }); const [currentPath, setCurrentPath] = useState(""); const [currentPathType, setCurrentPathType] = useState("directory"); @@ -92,13 +94,13 @@ export const WorkbenchDBProvider = ( setCurrentPath(path); setCurrentPathType(pathType); } - - function changeRouteOnImport(){ + + function changeRouteOnImport() { navigate(DEFAULT_ROUTE_ON_IMPORT); } - function goToFileInTableView(path: string){ - updateCurrentPath(path, 'file'); + function goToFileInTableView(path: string) { + updateCurrentPath(path, "file"); navigate("/" + ROUTES.TABLE_VIEW); } @@ -108,17 +110,19 @@ export const WorkbenchDBProvider = ( db: null, initialized: false, importedSqliteFilePath: null, + scanInfo: null, }); }; const abortImport = () => updateLoadingStatus(null); - const updateWorkbenchDB = (db: WorkbenchDB, sqliteFilePath: string) => { + const updateWorkbenchDB = async (db: WorkbenchDB, sqliteFilePath: string) => { updateLoadingStatus(100); setValue({ db, initialized: true, importedSqliteFilePath: sqliteFilePath, + scanInfo: parseScanInfo(await db.getScanInfo()), }); }; @@ -199,7 +203,7 @@ export const WorkbenchDBProvider = ( newWorkbenchDB.sync .then((db) => db.File.findOne({ where: { parent: "#" } })) - .then((root) => { + .then(async (root) => { if (!root) { console.error("Root path not found !!!!", root); return; @@ -212,13 +216,14 @@ export const WorkbenchDBProvider = ( opened_at: moment().format(), }); - updateWorkbenchDB(newWorkbenchDB, sqliteFilePath); + await updateWorkbenchDB(newWorkbenchDB, sqliteFilePath); - if (defaultPath) + if (defaultPath){ updateCurrentPath( defaultPath, root.getDataValue("type").toString({}) as PathType ); + } // Update window title const newlyImportedFileName = sqliteFilePath @@ -313,7 +318,7 @@ export const WorkbenchDBProvider = ( newWorkbenchDB.sync .then((db) => db.File.findOne({ where: { parent: "#" } })) - .then((root) => { + .then(async (root) => { if (!root) { console.error("Root path not found !!!!"); console.error("Root:", root); @@ -322,13 +327,14 @@ export const WorkbenchDBProvider = ( } const defaultPath = root.getDataValue("path"); - updateWorkbenchDB(newWorkbenchDB, sqliteFilePath); + await updateWorkbenchDB(newWorkbenchDB, sqliteFilePath); - if (defaultPath) + if (defaultPath){ updateCurrentPath( defaultPath, root.getDataValue("type").toString({}) as PathType ); + } // Update window title const newlyImportedFileName = jsonFilePath @@ -467,7 +473,6 @@ export const WorkbenchDBProvider = ( abortImport, updateCurrentPath, goToFileInTableView, - updateWorkbenchDB, }} > {props.children} @@ -475,4 +480,4 @@ export const WorkbenchDBProvider = ( ); }; -export const useWorkbenchDB = () => useContext(WorkbenchContext); \ No newline at end of file +export const useWorkbenchDB = () => useContext(WorkbenchContext); diff --git a/src/index.html b/src/index.html index 64a83b74..ed2dcb94 100644 --- a/src/index.html +++ b/src/index.html @@ -3,7 +3,6 @@ Scancode Workbench - diff --git a/src/pages/LicenseInfoDash/LicenseInfoDash.tsx b/src/pages/LicenseInfoDash/LicenseInfoDash.tsx index b242527a..ec3e2d73 100644 --- a/src/pages/LicenseInfoDash/LicenseInfoDash.tsx +++ b/src/pages/LicenseInfoDash/LicenseInfoDash.tsx @@ -213,7 +213,7 @@ const LicenseInfoDash = () => { diff --git a/src/pages/ScanInfo/ScanInfo.tsx b/src/pages/ScanInfo/ScanInfo.tsx index 274985f5..885a6d25 100644 --- a/src/pages/ScanInfo/ScanInfo.tsx +++ b/src/pages/ScanInfo/ScanInfo.tsx @@ -11,42 +11,27 @@ import "./scanInfo.css"; const ScanInfo = () => { const workbenchDB = useWorkbenchDB(); - const [parsedScanInfo, setParsedScanInfo] = useState(null); - - useEffect(() => { - const { db, initialized, currentPath } = workbenchDB; - - if (!initialized || !db || !currentPath) return; - - db.sync.then(() => { - db.getScanInfo().then((rawInfo) => { - console.log("Raw scan info:", rawInfo); - const newParsedScanInfo = parseScanInfo(rawInfo); - console.log("Parsed scan info:", newParsedScanInfo); - setParsedScanInfo(newParsedScanInfo); - }); - }); - }, [workbenchDB]); + const { scanInfo } = workbenchDB; return (

Scan Information


- {parsedScanInfo ? ( + {scanInfo ? ( - {parsedScanInfo.tool_name} + {scanInfo.tool_name} - {parsedScanInfo.tool_version} + {scanInfo.tool_version} 0} + show={scanInfo.input && scanInfo.input.length > 0} >
    - {(parsedScanInfo.input || []).map( + {(scanInfo.input || []).map( (value: string, idx: number) => (
  • {value}
  • ) @@ -56,11 +41,11 @@ const ScanInfo = () => { 0} + show={scanInfo.optionsList && scanInfo.optionsList.length > 0} >
- {parsedScanInfo.optionsList.map(([key, value]) => ( + {scanInfo.optionsList.map(([key, value]) => ( {typeof value !== "boolean" && } @@ -71,44 +56,44 @@ const ScanInfo = () => { - {parsedScanInfo.files_count} + {scanInfo.files_count} - {parsedScanInfo.output_format_version} + {scanInfo.output_format_version} - {parsedScanInfo.spdx_license_list_version} + {scanInfo.spdx_license_list_version} - {parsedScanInfo.operating_system} + {scanInfo.operating_system} - {parsedScanInfo.cpu_architecture} + {scanInfo.cpu_architecture} - {parsedScanInfo.platform} + {scanInfo.platform} - {parsedScanInfo.platform_version} + {scanInfo.platform_version} - {parsedScanInfo.python_version} + {scanInfo.python_version} - {parsedScanInfo.duration} seconds + {scanInfo.duration} seconds - {parsedScanInfo.notice} + {scanInfo.notice} diff --git a/src/utils/parsers.ts b/src/utils/parsers.ts index 619d5bcf..7ded4571 100644 --- a/src/utils/parsers.ts +++ b/src/utils/parsers.ts @@ -13,13 +13,32 @@ export function parseIfValidJson(str: unknown) { } } +export enum ScanOptionKeys { + CLASSIFY = "--classify", + COPYRIGHT = "--copyright", + SYSTEM_PACKAGE = "--system-package", + JSON_PP = "--json-pp", + SUMMARY = "--summary", + PROCESSES = "--processes", + INFO = "--info", + EMAIL = "--email", + URL = "--url", + PACKAGE = "--package", + LICENSE = "--license", + LICENSE_DIAGNOSTICS = "--license-diagnostics", + LICENSE_REFERENCES = "--license-references", + LICENSE_TEXT = "--license-text", + LICENSE_SCORE = "--license-score", + UNKNOWN_LICENSES = "--unknown-licenses", +} + export interface ScanInfo { tool_name: string; tool_version: string; notice: string; duration: number; optionsList: [string, unknown][]; - optionsMap: Map; + optionsMap: Map; input: string[]; files_count: number; output_format_version: string; @@ -39,7 +58,7 @@ export function parseScanInfo(rawInfo: Model parseIfValidJson(rawInfo.getDataValue("options")?.toString({})) || [] ) || []; - const optionsMap = new Map(optionsList.map(([k,v]) => [k.substring(2), v])); + const optionsMap = new Map(optionsList.map(([k,v]) => [k, v as ScanOptionKeys])); const parsedScanInfo: ScanInfo = { tool_name: rawInfo.getDataValue("tool_name").toString({}) || "", From 17672867aaffc8dbd5e42364375397740da00345 Mon Sep 17 00:00:00 2001 From: Omkar Phansopkar Date: Fri, 16 Jun 2023 23:32:08 +0530 Subject: [PATCH 10/12] Fixed empty space diffs around newlines Signed-off-by: Omkar Phansopkar --- .../LicenseEntity/MatchedTextContext.tsx | 11 +++-- src/pages/ScanInfo/ScanInfo.tsx | 20 ++++------ src/utils/text.ts | 40 ++++++++----------- 3 files changed, 28 insertions(+), 43 deletions(-) diff --git a/src/components/LicenseEntity/MatchedTextContext.tsx b/src/components/LicenseEntity/MatchedTextContext.tsx index e52a1fbe..cae43248 100644 --- a/src/components/LicenseEntity/MatchedTextContext.tsx +++ b/src/components/LicenseEntity/MatchedTextContext.tsx @@ -7,8 +7,8 @@ import { useWorkbenchDB } from "../../contexts/dbContext"; import { BelongsText, DiffInfo, - normalizeAndCategorizeDiffs, - splitDiffIntoLines, + categorizeDiffs, + normalizeAndSplitDiffIntoLines, } from "../../utils/text"; import { ScanOptionKeys } from "../../utils/parsers"; @@ -83,10 +83,9 @@ export const MatchedTextProvider = ( ignoreWhitespace: true, }); - const normalizedDiffs: DiffInfo[] = - normalizeAndCategorizeDiffs(rawDiffs); + const normalizedDiffs: DiffInfo[] = categorizeDiffs(rawDiffs); - const normalizedRuleTextLines = splitDiffIntoLines( + const normalizedRuleTextLines = normalizeAndSplitDiffIntoLines( normalizedDiffs.filter( (diff) => diff.belongsTo === BelongsText.BOTH || @@ -94,7 +93,7 @@ export const MatchedTextProvider = ( ) ); - const normalizedModifiedTextLines = splitDiffIntoLines( + const normalizedModifiedTextLines = normalizeAndSplitDiffIntoLines( normalizedDiffs.filter( (diff) => diff.belongsTo === BelongsText.BOTH || diff --git a/src/pages/ScanInfo/ScanInfo.tsx b/src/pages/ScanInfo/ScanInfo.tsx index 885a6d25..c2ff93c4 100644 --- a/src/pages/ScanInfo/ScanInfo.tsx +++ b/src/pages/ScanInfo/ScanInfo.tsx @@ -1,11 +1,11 @@ -import React, { useEffect, useState } from "react"; +import React from "react"; // Maintained Fork of unmaintained but popular react-json-view import ReactJson from "@microlink/react-json-view"; import InfoEntry from "./InfoEntry"; import { useWorkbenchDB } from "../../contexts/dbContext"; -import { ScanInfo, parseIfValidJson, parseScanInfo } from "../../utils/parsers"; +import { ScanInfo, parseIfValidJson } from "../../utils/parsers"; import "./scanInfo.css"; @@ -22,20 +22,16 @@ const ScanInfo = () => { {scanInfo.tool_name} - - {scanInfo.tool_version} - + {scanInfo.tool_version} 0} >
    - {(scanInfo.input || []).map( - (value: string, idx: number) => ( -
  • {value}
  • - ) - )} + {(scanInfo.input || []).map((value: string, idx: number) => ( +
  • {value}
  • + ))}
@@ -55,9 +51,7 @@ const ScanInfo = () => {
{key}{String(value)}
- - {scanInfo.files_count} - + {scanInfo.files_count} {scanInfo.output_format_version} diff --git a/src/utils/text.ts b/src/utils/text.ts index eff92f07..359eefb5 100644 --- a/src/utils/text.ts +++ b/src/utils/text.ts @@ -20,25 +20,9 @@ export function normalizeString(str: string) { .trim(); } -export function normalizeAndCategorizeDiffs(diffs: Change[]) { +export function categorizeDiffs(diffs: Change[]) { return diffs.map((diff): DiffInfo => { { - const changeDetected = Boolean(diff.added || diff.removed); - const normalizedValue = normalizeString(diff.value); - - // No change / Trivial diff - if (!changeDetected || normalizedValue.length === 0) { - return { - value: diff.value, - count: diff.count, - belongsTo: diff.added - ? BelongsText.MODIFIED - : diff.removed - ? BelongsText.ORIGINAL - : BelongsText.BOTH, - }; - } - return { ...(diff.added ? { added: true } @@ -52,7 +36,6 @@ export function normalizeAndCategorizeDiffs(diffs: Change[]) { : BelongsText.BOTH, value: diff.value, count: diff.count, - trimmedValue: normalizedValue, }; } }); @@ -65,9 +48,8 @@ export enum BelongsText { } export interface DiffInfo extends Change { belongsTo: BelongsText; - trimmedValue?: string; } -export function splitDiffIntoLines(diffs: DiffInfo[]) { +export function normalizeAndSplitDiffIntoLines(diffs: DiffInfo[]) { const lines: DiffInfo[][] = [[]]; for (const diff of diffs) { @@ -83,12 +65,22 @@ export function splitDiffIntoLines(diffs: DiffInfo[]) { const subLines = splitLines.slice(idx); for (const subLine of subLines) { + const isTrivialDiff = normalizeString(subLine).length === 0; + // Append to last line only if it is non-empty string if (subLine.length > 0) { - lines[lines.length - 1].push({ - ...diff, - value: subLine, - }); + if (isTrivialDiff) { + lines[lines.length - 1].push({ + value: subLine, + belongsTo: diff.belongsTo, + count: diff.count, + }); + } else { + lines[lines.length - 1].push({ + ...diff, + value: subLine, + }); + } // Create newline for intermittent newlines // (ignore last subLine, it is continued in next line) From 75e56ef00a02a0fcfcb7e344e252c07c7b1dc13a Mon Sep 17 00:00:00 2001 From: Omkar Phansopkar Date: Sun, 18 Jun 2023 00:40:56 +0530 Subject: [PATCH 11/12] Fixed incorrect diffs surrounding symbols and newlines Signed-off-by: Omkar Phansopkar --- package-lock.json | 29 ++---- package.json | 5 +- src/app.css | 36 ++++--- .../LicenseEntity/MatchedTextContext.tsx | 52 +++++----- .../LicenseEntity/licenseEntity.css | 3 +- src/pages/Licenses/Licenses.css | 17 +--- src/utils/text.ts | 99 +++++++++++++------ 7 files changed, 132 insertions(+), 109 deletions(-) diff --git a/package-lock.json b/package-lock.json index 87a80b0f..c3ad0970 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "bootstrap": "^5.2.1", "c3": "^0.7.20", "chart.js": "^4.3.0", - "diff": "^5.1.0", + "diff-match-patch": "^1.0.5", "electron-squirrel-startup": "^1.0.0", "file-loader": "^6.2.0", "font-awesome": "^4.7.0", @@ -35,7 +35,6 @@ "mock-aws-s3": "^4.0.2", "moment": "^2.29.4", "nock": "^13.3.0", - "packageurl-js": "^1.0.0", "pg": "^8.10.0", "pg-hstore": "^2.3.4", "rc-tree": "^5.7.0", @@ -68,7 +67,7 @@ "@svgr/webpack": "^6.4.0", "@types/c3": "^0.7.8", "@types/d3": "^7.4.0", - "@types/diff": "^5.0.3", + "@types/diff-match-patch": "^1.0.32", "@types/jquery": "^3.5.14", "@types/node": "^18.8.0", "@types/react": "^18.0.21", @@ -5280,10 +5279,10 @@ "@types/ms": "*" } }, - "node_modules/@types/diff": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.0.3.tgz", - "integrity": "sha512-amrLbRqTU9bXMCc6uX0sWpxsQzRIo9z6MJPkH1pkez/qOxuqSZVuryJAWoBRq94CeG8JxY+VK4Le9HtjQR5T9A==", + "node_modules/@types/diff-match-patch": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.32.tgz", + "integrity": "sha512-bPYT5ECFiblzsVzyURaNhljBH2Gh1t9LowgUwciMrNAhFewLkHT2H0Mto07Y4/3KCOGZHRQll3CTtQZ0X11D/A==", "dev": true }, "node_modules/@types/eslint": { @@ -9446,13 +9445,10 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "dev": true }, - "node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", - "engines": { - "node": ">=0.3.1" - } + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" }, "node_modules/diff-sequences": { "version": "27.5.1", @@ -16939,11 +16935,6 @@ "node": ">=6" } }, - "node_modules/packageurl-js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/packageurl-js/-/packageurl-js-1.0.1.tgz", - "integrity": "sha512-EtXC0kgLjy/C7S4SN3Kk1SDRWLzIn/LUK0gXlz3gsxDdpI0k7q8C3SASpV0pc+v0yADBTt5rWewq/flGdGxtoQ==" - }, "node_modules/packet-reader": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", diff --git a/package.json b/package.json index 67ec1914..06a582e0 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@svgr/webpack": "^6.4.0", "@types/c3": "^0.7.8", "@types/d3": "^7.4.0", - "@types/diff": "^5.0.3", + "@types/diff-match-patch": "^1.0.32", "@types/jquery": "^3.5.14", "@types/node": "^18.8.0", "@types/react": "^18.0.21", @@ -87,7 +87,7 @@ "bootstrap": "^5.2.1", "c3": "^0.7.20", "chart.js": "^4.3.0", - "diff": "^5.1.0", + "diff-match-patch": "^1.0.5", "electron-squirrel-startup": "^1.0.0", "file-loader": "^6.2.0", "font-awesome": "^4.7.0", @@ -97,7 +97,6 @@ "mock-aws-s3": "^4.0.2", "moment": "^2.29.4", "nock": "^13.3.0", - "packageurl-js": "^1.0.0", "pg": "^8.10.0", "pg-hstore": "^2.3.4", "rc-tree": "^5.7.0", diff --git a/src/app.css b/src/app.css index aad8e4fd..9c3e4cd7 100644 --- a/src/app.css +++ b/src/app.css @@ -7,18 +7,30 @@ body { width: 100%; position: absolute; } -a, a:not([href]), a:not([href]):not([class]) { + +a, +a:not([href]), +a:not([href]):not([class]) { color: #0d6efd; cursor: pointer; text-decoration: none; } -a:hover, a:not([href]):hover, a:not([href]):not([class]):hover { + +a:hover, +a:not([href]):hover, +a:not([href]):not([class]):hover { color: #0a58ca; } + +button:focus:not(:focus-visible) { + outline: none; + box-shadow: none; +} + img { - -webkit-user-select: none; - -webkit-user-drag: none; - -webkit-app-region: no-drag; + -webkit-user-select: none; + -webkit-user-drag: none; + -webkit-app-region: no-drag; user-select: none; } @@ -32,7 +44,7 @@ img { font-weight: 400; src: url('./lib/css/fonts/Raleway-Regular.ttf'); src: local('?'), - url('./lib/css/fonts/Raleway-Regular.ttf') format('truetype'); + url('./lib/css/fonts/Raleway-Regular.ttf') format('truetype'); } @font-face { @@ -41,7 +53,7 @@ img { font-weight: 800; src: url('./lib/css/fonts/Raleway-Black.ttf'); src: local('?'), - url('./lib/css/fonts/Raleway-Black.ttf') format('truetype'); + url('./lib/css/fonts/Raleway-Black.ttf') format('truetype'); } @font-face { @@ -50,7 +62,7 @@ img { font-weight: 400; src: url('./lib/css/fonts/Lato-Regular.ttf'); src: local('?'), - url('./lib/css/fonts/Lato-Regular.ttf') format('truetype'); + url('./lib/css/fonts/Lato-Regular.ttf') format('truetype'); } @@ -59,27 +71,29 @@ img { padding: 5px; padding-right: 18px; } + .card { border-radius: 2px; box-shadow: 1px 0 8px -3px #b1b1b1; background-color: #FFFFFF; margin-bottom: 30px; } + img { -webkit-user-drag: none; } -.card > .content { +.card>.content { text-align: center; padding: 15px 15px 0; } -.card > .header { +.card>.header { text-align: center; padding: 15px 15px; } -.card > .value { +.card>.value { margin: 0; margin-left: auto; margin-right: auto; diff --git a/src/components/LicenseEntity/MatchedTextContext.tsx b/src/components/LicenseEntity/MatchedTextContext.tsx index cae43248..a479874f 100644 --- a/src/components/LicenseEntity/MatchedTextContext.tsx +++ b/src/components/LicenseEntity/MatchedTextContext.tsx @@ -1,13 +1,12 @@ -import { diffWords, Change } from "diff"; import { Alert, Col, Modal, Row } from "react-bootstrap"; import { TailSpin } from "react-loader-spinner"; import React, { createContext, useContext, useEffect, useState } from "react"; import { useWorkbenchDB } from "../../contexts/dbContext"; import { - BelongsText, - DiffInfo, - categorizeDiffs, + BelongsIndicator, + DiffComponents, + diffStrings, normalizeAndSplitDiffIntoLines, } from "../../utils/text"; import { ScanOptionKeys } from "../../utils/parsers"; @@ -42,10 +41,12 @@ export const MatchedTextProvider = ( ) => { const { db, scanInfo } = useWorkbenchDB(); const [showDiffWindow, setShowDiffWindow] = useState(false); - const [ruleDiffLines, setRuleDiffLines] = useState(null); - const [modifiedDiffLines, setModifiedDiffLines] = useState( + const [ruleDiffLines, setRuleDiffLines] = useState( null ); + const [modifiedDiffLines, setModifiedDiffLines] = useState< + DiffComponents[][] | null + >(null); const [matchDetails, setMatchDetails] = useState<{ identifier: string | null; matched_text: string | null; @@ -78,26 +79,20 @@ export const MatchedTextProvider = ( const ruleText = ruleRef.getDataValue("text")?.toString({}) || ""; const matchedText = matchDetails.matched_text; - const rawDiffs = diffWords(ruleText, matchedText, { - ignoreCase: true, - ignoreWhitespace: true, - }); - - const normalizedDiffs: DiffInfo[] = categorizeDiffs(rawDiffs); + const diffs = diffStrings(ruleText, matchedText); const normalizedRuleTextLines = normalizeAndSplitDiffIntoLines( - normalizedDiffs.filter( + diffs.filter( (diff) => - diff.belongsTo === BelongsText.BOTH || - diff.belongsTo === BelongsText.ORIGINAL + diff.belongsTo === BelongsIndicator.BOTH || + diff.belongsTo === BelongsIndicator.ORIGINAL ) ); - const normalizedModifiedTextLines = normalizeAndSplitDiffIntoLines( - normalizedDiffs.filter( + diffs.filter( (diff) => - diff.belongsTo === BelongsText.BOTH || - diff.belongsTo === BelongsText.MODIFIED + diff.belongsTo === BelongsIndicator.BOTH || + diff.belongsTo === BelongsIndicator.MODIFIED ) ); @@ -188,7 +183,7 @@ export const MatchedTextProvider = ( <>
Coverage: {matchDetails.coverage} %
- + @@ -203,17 +198,18 @@ export const MatchedTextProvider = ( className="diff-line" >
-
+                              
                                 {diffLine.map((diff, diffIdx) => {
                                   return (
                                     
-                                      {diff.value}
+                                      {diff.value}
                                     
                                   );
                                 })}
@@ -224,7 +220,7 @@ export const MatchedTextProvider = (
                       
                     
- + @@ -242,13 +238,15 @@ export const MatchedTextProvider = ( {matchDetails.start_line + idx}. +
-
+                              
                                 {diffLine.map((diff, diffIdx) => {
                                   return (
                                     
                                       {diff.value}
diff --git a/src/components/LicenseEntity/licenseEntity.css b/src/components/LicenseEntity/licenseEntity.css
index 5998c587..16bbc0bf 100644
--- a/src/components/LicenseEntity/licenseEntity.css
+++ b/src/components/LicenseEntity/licenseEntity.css
@@ -55,6 +55,7 @@
   margin: 0;
   vertical-align: top;
   white-space: pre;
+  overflow-wrap: anywhere;
 }
 
 .matched-text-diff-modal .line-number {
@@ -69,7 +70,7 @@
   padding-right: 10px;
 }
 
-.matched-text-diff-modal .diff-line .line-content .snippet {
+.matched-text-diff-modal .diff-line .line-content .line-text {
   margin: 0;
   display: inline-block;
   white-space: pre-wrap;
diff --git a/src/pages/Licenses/Licenses.css b/src/pages/Licenses/Licenses.css
index 820e1d7a..47768861 100644
--- a/src/pages/Licenses/Licenses.css
+++ b/src/pages/Licenses/Licenses.css
@@ -82,19 +82,4 @@
 
 .license-item:hover {
   background-color: rgb(242, 242, 242);
-}
-
-
-/* .expand-package {
-  display: inline-block;
-  border-radius: 50%;
-}
-.expand-package .expanded-icon{
-  transform: rotate(90deg);
-}
-.expand-package svg{
-  transition: transform 300ms ease-in;
-}
-.expand-package:active, .expand-package:hover {
-  font-size: 14px;
-} */
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/utils/text.ts b/src/utils/text.ts
index 359eefb5..a64b9ba2 100644
--- a/src/utils/text.ts
+++ b/src/utils/text.ts
@@ -1,5 +1,6 @@
-import { Change } from "diff";
+import { diff_match_patch } from "diff-match-patch";
 
+// Trim string to specified length & add ellipsis if required
 export function trimStringWithEllipsis(
   str: string,
   maxLengthInclusive: number
@@ -10,47 +11,82 @@ export function trimStringWithEllipsis(
   return str.trimEnd();
 }
 
-/**
- * Removes punctuation & multiple spaces from given string.
- */
+// Removes symbols & extra spaces from given string.
 export function normalizeString(str: string) {
   return str
-    .replace(/[.,/#!$%^&*;:[{}\]=\-_`~()]/g, "")
+    .replace(/[.,/#!$%^&*;:[{}\]=\-_'"`~()]/g, "")
     .replace(/\s{2,}/g, " ")
     .trim();
 }
 
-export function categorizeDiffs(diffs: Change[]) {
-  return diffs.map((diff): DiffInfo => {
-    {
-      return {
-        ...(diff.added
-          ? { added: true }
-          : diff.removed
-          ? { removed: true }
-          : {}),
-        belongsTo: diff.added
-          ? BelongsText.MODIFIED
-          : diff.removed
-          ? BelongsText.ORIGINAL
-          : BelongsText.BOTH,
-        value: diff.value,
-        count: diff.count,
-      };
-    }
-  });
-}
-
-export enum BelongsText {
+// Note - This indicates, which section to show component and not added/removed
+// It is useful when there exists a trivial diff, which has to be shown only in specific section but doesn't qualify as a diff
+export enum BelongsIndicator {
   ORIGINAL = "original",
   MODIFIED = "modified",
   BOTH = "both",
 }
-export interface DiffInfo extends Change {
-  belongsTo: BelongsText;
+export interface DiffComponents {
+  belongsTo: BelongsIndicator;
+  value: string;
+  diffComponent?: "added" | "removed" | null;
 }
-export function normalizeAndSplitDiffIntoLines(diffs: DiffInfo[]) {
-  const lines: DiffInfo[][] = [[]];
+
+const diffMatcher = new diff_match_patch();
+
+export function diffStrings(text1: string, text2: string) {
+  const diffs = diffMatcher.diff_main(text1, text2);
+  const diffsWithIgnoredLetterCases: DiffComponents[] = [];
+
+  for (let i = 0; i < diffs.length; i++) {
+    const currentDiff = diffs[i];
+    const nextDiff = i + 1 < diffs.length ? diffs[i + 1] : null;
+
+    if (
+      currentDiff &&
+      nextDiff &&
+      currentDiff[0] === -1 &&
+      nextDiff[0] === 1 &&
+      normalizeString(currentDiff[1]).toLowerCase() ===
+        normalizeString(nextDiff[1]).toLowerCase()
+    ) {
+      // Add both to their respective diff category without identifying as diffComponent
+      diffsWithIgnoredLetterCases.push({
+        belongsTo: BelongsIndicator.ORIGINAL,
+        value: currentDiff[1],
+      });
+      diffsWithIgnoredLetterCases.push({
+        belongsTo: BelongsIndicator.MODIFIED,
+        value: nextDiff[1],
+      });
+      i++; // Skip next iteration, as its already handled
+      continue;
+    }
+
+    diffsWithIgnoredLetterCases.push({
+      ...{
+        diffComponent:
+          currentDiff[0] === 1
+            ? "added"
+            : currentDiff[0] === -1
+            ? "removed"
+            : null,
+      },
+      belongsTo:
+        currentDiff[0] === 1
+          ? BelongsIndicator.MODIFIED
+          : currentDiff[0] === -1
+          ? BelongsIndicator.ORIGINAL
+          : BelongsIndicator.BOTH,
+      value: currentDiff[1],
+    });
+  }
+
+  return diffsWithIgnoredLetterCases;
+}
+
+export function normalizeAndSplitDiffIntoLines(diffs: DiffComponents[]) {
+  const lines: DiffComponents[][] = [[]];
 
   for (const diff of diffs) {
     const splitLines = diff.value.split("\n");
@@ -73,7 +109,6 @@ export function normalizeAndSplitDiffIntoLines(diffs: DiffInfo[]) {
           lines[lines.length - 1].push({
             value: subLine,
             belongsTo: diff.belongsTo,
-            count: diff.count,
           });
         } else {
           lines[lines.length - 1].push({

From fe481428a224761e5e87efd700053d1ebd5566d7 Mon Sep 17 00:00:00 2001
From: Omkar Phansopkar 
Date: Mon, 19 Jun 2023 20:14:42 +0530
Subject: [PATCH 12/12] Fixed diff styling

Signed-off-by: Omkar Phansopkar 
---
 src/components/LicenseEntity/MatchedTextContext.tsx |  4 ++--
 src/components/LicenseEntity/licenseEntity.css      | 12 +++++++++---
 2 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/src/components/LicenseEntity/MatchedTextContext.tsx b/src/components/LicenseEntity/MatchedTextContext.tsx
index a479874f..5eec95e9 100644
--- a/src/components/LicenseEntity/MatchedTextContext.tsx
+++ b/src/components/LicenseEntity/MatchedTextContext.tsx
@@ -183,7 +183,7 @@ export const MatchedTextProvider = (
               <>
                 
Coverage: {matchDetails.coverage} %
-
@@ -220,7 +220,7 @@ export const MatchedTextProvider = (
- + diff --git a/src/components/LicenseEntity/licenseEntity.css b/src/components/LicenseEntity/licenseEntity.css index 16bbc0bf..7c44ba32 100644 --- a/src/components/LicenseEntity/licenseEntity.css +++ b/src/components/LicenseEntity/licenseEntity.css @@ -29,12 +29,18 @@ max-height: calc(100vh - 120px); } +.matched-text-diff-modal .row { + padding: 5px; +} + .matched-text-diff-modal .rule-text-section { - background-color: #f4feff; + background-color: #f6feff; + padding: 0; } .matched-text-diff-modal .matched-text-section { - background-color: #fafff5; + background-color: #f9fffc; + padding: 0; } .matched-text-diff-modal .diff-table { @@ -44,7 +50,7 @@ .matched-text-diff-modal .diff-table th { padding: 15px; - padding-left: 5px; + padding-left: 10px; font-size: 15px; font-weight: 500; background-color: #fafbfc;