diff --git a/public/components/package/header/header.css b/public/components/package/header/header.css
index 9a6e938d..aa0284a1 100644
--- a/public/components/package/header/header.css
+++ b/public/components/package/header/header.css
@@ -66,6 +66,12 @@ div.package-header>.package-name>.info-menu>a {
/**
* DESC
*/
+
+div.package-header>.package-description{
+ display: flex;
+ justify-content: space-between;
+}
+
div.package-header>.description {
width: 100%;
color: #FFF;
@@ -74,6 +80,15 @@ div.package-header>.description {
margin-top: 7px;
}
+div.package-header>.package-description>.has-duplicate {
+ border: none;
+ background: transparent;
+ transform: translateY(-4px);
+ font-size: 18px;
+ cursor: pointer;
+ padding: 0;
+}
+
/**
* FLAGS
*/
diff --git a/public/components/package/header/header.js b/public/components/package/header/header.js
index cf099b22..9b0e963b 100644
--- a/public/components/package/header/header.js
+++ b/public/components/package/header/header.js
@@ -1,5 +1,5 @@
// Import Third-party Dependencies
-import { getFlagsEmojisInlined } from "@nodesecure/vis-network";
+import { getFlagsEmojisInlined, FLAGS_EMOJIS } from "@nodesecure/vis-network";
// Import Internal Dependencies
import * as utils from "../../../common/utils.js";
@@ -109,6 +109,11 @@ export class PackageHeader {
flagsDomElement.appendChild(flagFragment);
}
+ // Has Duplicate Button
+ if (this.#hasDuplicate()) {
+ this.#renderHasDuplicateBtn(clone);
+ }
+
return links;
}
@@ -244,6 +249,29 @@ export class PackageHeader {
return fragment;
}
+
+ #hasDuplicate() {
+ return this.package.dependencyVersion.flags.some((title) => title === "hasDuplicate") &&
+ !window.settings.config.ignore.warnings.has("hasDuplicate") &&
+ "isDuplicated" in FLAGS_EMOJIS;
+ }
+
+ #renderHasDuplicateBtn(clone) {
+ const hasDuplicateBtn = utils.createDOMElement("button",
+ { classList: ["has-duplicate"], text: FLAGS_EMOJIS.isDuplicated });
+
+ const packagesList = this.nsn.secureDataSet.findPackagesByName(this.package.dependencyVersion.name)
+ .map(({ name, version }) => `${name}@${version}`);
+
+ hasDuplicateBtn.addEventListener("click", () => {
+ const nodeIds = [...this.nsn.findNodeIds(new Set(packagesList))];
+ this.nsn.highlightMultipleNodes(nodeIds);
+ window.locker.lock();
+ });
+
+ const packageDescDiv = clone.querySelector(".package-description");
+ packageDescDiv.appendChild(hasDuplicateBtn);
+ }
}
function createFlagInfoBulle(text, description) {
diff --git a/public/components/package/package.html b/public/components/package/package.html
index f6115141..5e866e77 100644
--- a/public/components/package/package.html
+++ b/public/components/package/package.html
@@ -6,7 +6,9 @@
-
+
diff --git a/workspaces/vis-network/index.js b/workspaces/vis-network/index.js
index 78a26ce7..a2f8c3e5 100644
--- a/workspaces/vis-network/index.js
+++ b/workspaces/vis-network/index.js
@@ -1,13 +1,14 @@
// Import Internal Dependencies
import NodeSecureDataSet from "./src/dataset.js";
import NodeSecureNetwork, { NETWORK_OPTIONS } from "./src/network.js";
-import { getJSON, getFlagsEmojisInlined } from "./src/utils.js";
+import { getJSON, getFlagsEmojisInlined, FLAGS_EMOJIS } from "./src/utils.js";
export * from "./src/constants.js";
export {
getJSON,
getFlagsEmojisInlined,
+ FLAGS_EMOJIS,
NodeSecureDataSet,
NodeSecureNetwork,
NETWORK_OPTIONS
diff --git a/workspaces/vis-network/src/dataset.js b/workspaces/vis-network/src/dataset.js
index 5a84bc11..33e080a6 100644
--- a/workspaces/vis-network/src/dataset.js
+++ b/workspaces/vis-network/src/dataset.js
@@ -228,4 +228,8 @@ export default class NodeSecureDataSet extends EventTarget {
isHighlighted(contact) {
return this.#highligthedContacts.names.has(contact.name) || this.#highligthedContacts.emails.has(contact.email);
}
+
+ findPackagesByName(name) {
+ return this.packages.filter((pkg) => pkg.name === name);
+ }
}
diff --git a/workspaces/vis-network/src/utils.js b/workspaces/vis-network/src/utils.js
index df765376..b385f1a8 100644
--- a/workspaces/vis-network/src/utils.js
+++ b/workspaces/vis-network/src/utils.js
@@ -5,7 +5,7 @@ import { getManifestEmoji } from "@nodesecure/flags/web";
import * as CONSTANTS from "./constants.js";
// CONSTANTS
-const kFlagsEmojis = Object.fromEntries(getManifestEmoji());
+export const FLAGS_EMOJIS = Object.fromEntries(getManifestEmoji());
export async function getJSON(path, customHeaders = Object.create(null)) {
const headers = {
@@ -73,7 +73,7 @@ export function getFlagsEmojisInlined(
}
// FIX: when scanner resolve to flags ^3.x
- const emoji = kFlagsEmojis[title === "hasDuplicate" ? "isDuplicated" : title];
+ const emoji = FLAGS_EMOJIS[title === "hasDuplicate" ? "isDuplicated" : title];
return emoji ? [emoji] : [];
})
diff --git a/workspaces/vis-network/test/dataset-payload.json b/workspaces/vis-network/test/dataset-payload.json
index 46988ad1..52cfa29e 100644
--- a/workspaces/vis-network/test/dataset-payload.json
+++ b/workspaces/vis-network/test/dataset-payload.json
@@ -64,6 +64,29 @@
],
"name": "pkg2",
"version": "1.0.3"
+ },
+ "1.0.4": {
+ "usedBy": {
+ "pkg3": "3.4.0"
+ },
+ "flags": [],
+ "size": 100,
+ "author": {},
+ "warnings": [],
+ "composition": {
+ "extensions": [
+ ".md",
+ ".js",
+ "",
+ ".json"
+ ]
+ },
+ "licenses": [],
+ "uniqueLicenseIds": [
+ "Unlicense"
+ ],
+ "name": "pkg2",
+ "version": "1.0.4"
}
}
},
@@ -156,4 +179,4 @@
"warning_02"
],
"vulnerabilityStrategy": "npm"
-}
\ No newline at end of file
+}
diff --git a/workspaces/vis-network/test/dataset.test.js b/workspaces/vis-network/test/dataset.test.js
index c04ae50d..9e9f4fc4 100644
--- a/workspaces/vis-network/test/dataset.test.js
+++ b/workspaces/vis-network/test/dataset.test.js
@@ -97,3 +97,39 @@ test("NodeSecureDataSet.build", () => {
assert.equal(builtData.nodes.length, 2, "should have 2 nodes");
assert.equal(builtData.edges.length, 3, "should have 3 edges");
});
+
+test("NodeSecureDataSet.findPackagesByName should have no packages when no name matches", async() => {
+ const nsDataSet = new NodeSecureDataSet();
+ await nsDataSet.init(dataSetPayload);
+
+ assert.equal(nsDataSet.findPackagesByName("unknown").length, 0, "should have no packages");
+});
+
+test("NodeSecureDataSet.findPackagesByName should have packages when name matches", async() => {
+ const nsDataSet = new NodeSecureDataSet();
+ await nsDataSet.init(dataSetPayload);
+ const packages = nsDataSet.findPackagesByName("pkg2");
+
+ const expectedPackages = [
+ {
+ id: undefined,
+ name: "pkg2",
+ version: "1.0.3",
+ hasWarnings: false,
+ flags: "",
+ links: undefined,
+ isFriendly: 0
+ },
+ {
+ id: undefined,
+ name: "pkg2",
+ version: "1.0.4",
+ hasWarnings: false,
+ flags: "",
+ links: undefined,
+ isFriendly: 0
+ }
+ ];
+
+ assert.deepEqual(packages, expectedPackages, "should all versions by name");
+});