Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update license Component for Github SBOM #9755

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 81 additions & 74 deletions src/components/Licenses/SBOMViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,79 +1,96 @@
import dayjs from "dayjs";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { CopyToClipboard } from "react-copy-to-clipboard";

import Card from "@/CAREUI/display/Card";
import CareIcon from "@/CAREUI/icons/CareIcon";

import beBomData from "@/components/Licenses/be-sbom.json";
import feBomData from "@/components/Licenses/fe-sbom.json";
import licenseUrls from "@/components/Licenses/licenseUrls.json";

const getLicenseUrl = (licenseId: string | undefined): string | null => {
if (!licenseId) return null;
return licenseUrls[licenseId as keyof typeof licenseUrls] || null;
};
interface CycloneDXExternalRef {
url?: string;
type?: string;
comment?: string;
}

interface CycloneDXLicense {
license?: {
id?: string;
};
interface GitHubPackage {
name: string;
SPDXID: string;
versionInfo?: string;
downloadLocation?: string;
filesAnalyzed?: boolean;
licenseConcluded?: string;
copyrightText?: string;
externalRefs?: {
referenceCategory?: string;
referenceType?: string;
referenceLocator?: string;
}[];
}

interface CycloneDXProperties {
name?: string;
value?: string;
}

interface CycloneDXComponent {
type?: string;
name?: string;
group?: string;
version?: string;
bomRef?: string;
author?: string;
description?: string;
licenses?: CycloneDXLicense[];
externalReferences?: CycloneDXExternalRef[];
properties?: CycloneDXProperties[];
}

interface CycloneDXTool {
name?: string;
version?: string;
vendor?: string;
externalReferences?: CycloneDXExternalRef[];
}

interface CycloneDXBOM {
bomFormat?: string;
specVersion?: string;
version?: number;
serialNumber?: string;
metadata?: {
timestamp?: string;
tools?: CycloneDXTool[];
component?: CycloneDXComponent;
interface GitHubSBOM {
sbom?: {
spdxVersion?: string;
dataLicense?: string;
SPDXID?: string;
name?: string;
documentNamespace?: string;
creationInfo?: {
creators?: string[];
created?: string;
};
packages?: GitHubPackage[];
};
components?: CycloneDXComponent[];
}

const BOMDisplay: React.FC = () => {
const [copyStatus, setCopyStatus] = useState(false);
const [showExternalRefs, setShowExternalRefs] = useState<number | null>(null);
const [activeTab, setActiveTab] = useState<string>("bom");
const [feBomData, setFeBomData] = useState<GitHubSBOM | null>(null);
const [beBomData, setBeBomData] = useState<GitHubSBOM | null>(null);

useEffect(() => {
const fetchSBOMData = async (url: string): Promise<GitHubSBOM | null> => {
try {
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
});
return response.ok ? await response.json() : null;
} catch (error) {
console.error("Error fetching SBOM data:", error);
return null;
}
};

const fetchData = async () => {
const feUrl =
"https://api.github.com/repos/ohcnetwork/care_fe/dependency-graph/sbom";
const beUrl =
"https://api.github.com/repos/ohcnetwork/care/dependency-graph/sbom";

const [frontendData, backendData] = await Promise.all([
fetchSBOMData(feUrl),
fetchSBOMData(beUrl),
]);

setFeBomData(frontendData);
setBeBomData(backendData);
};

fetchData();
}, []);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use useQuery with fetch instead.

Also, instead of fetching dynamically everytime on runtime, i'd recommend adding this data-fetching logic to postinstall step or build step.

Refer npm scripts: https://docs.npmjs.com/cli/v9/using-npm/scripts#examples

During the step, fetch the data using gh's APIs and save it as a JSON (gitignore it).
Lazy import the JSON directly in this file, and render accordingly.

cc: @bodhish


const bomData = activeTab === "bom" ? feBomData : beBomData;

const handleCopy = () => {
setCopyStatus(true);
setTimeout(() => setCopyStatus(false), 2000);
};

const bomData = (activeTab === "bom" ? feBomData : beBomData) as CycloneDXBOM;
const packages = bomData?.sbom?.packages || [];

return (
<div className="p-4">
Expand All @@ -98,59 +115,47 @@ const BOMDisplay: React.FC = () => {
<Card className="rounded-lg bg-white p-4 shadow-md transition-all duration-300">
<div className="mb-4">
<h2 className="mb-2 text-xl font-semibold text-primary md:text-2xl">
{bomData.bomFormat || "N/A"} BOM (Version:{" "}
{bomData.version || "N/A"})
SPDX SBOM (Version: {bomData?.sbom?.spdxVersion || "N/A"})
</h2>
<p className="text-sm text-gray-500">
Created on:{" "}
{bomData.metadata?.timestamp
? dayjs(bomData.metadata.timestamp).format("MMMM D, YYYY")
{bomData?.sbom?.creationInfo?.created
? dayjs(bomData.sbom.creationInfo.created).format("MMMM D, YYYY")
: "N/A"}
</p>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<h3 className="col-span-full text-lg font-semibold text-primary">
Components:
Packages:
</h3>
{bomData.components?.map((component, index) => (
{packages.map((pkg, index) => (
<div
key={index}
className="block rounded-md border p-2 transition-all duration-300 hover:shadow-lg"
>
<a
href={
component.externalReferences?.[1]?.url ||
component.externalReferences?.[0]?.url ||
"#"
}
// href={pkg.externalRefs?.[0]?.referenceLocator || "#"}
target="_blank"
rel="noopener noreferrer"
className="hover:text-primary-dark block text-primary"
>
<strong className="text-lg">
{component.name || "N/A"} v{component.version || "N/A"}
{pkg.name || "N/A"} v{pkg.versionInfo || "N/A"}
</strong>
</a>
{component.licenses && component.licenses[0]?.license?.id && (
{pkg.licenseConcluded && (
<p className="text-base">
License:{" "}
<a
href={
getLicenseUrl(component.licenses[0].license.id) || "#"
}
href={getLicenseUrl(pkg.licenseConcluded) || "#"}
target="_blank"
rel="noopener noreferrer"
className="hover:text-primary-dark text-primary"
>
{component.licenses[0].license.id || "N/A"}
{pkg.licenseConcluded || "N/A"}
</a>
</p>
)}
{component.description && (
<p className="text-base">
Description: {component.description}
</p>
)}
<div>
<h4
className="block cursor-pointer font-semibold text-primary"
Expand All @@ -164,15 +169,17 @@ const BOMDisplay: React.FC = () => {
</h4>
{showExternalRefs === index && (
<ul className="list-inside list-disc pl-4 text-xs">
{component.externalReferences?.map((ref, idx) => (
{pkg.externalRefs?.map((ref, idx) => (
<li key={idx}>
<a
href={ref.url || "#"}
href={ref.referenceLocator || "#"}
className="hover:text-primary-dark block break-words text-primary"
>
{ref.url || "N/A"}
{ref.referenceLocator || "N/A"}
</a>
{ref.comment && <p>Comment: {ref.comment}</p>}
{ref.referenceCategory && (
<p>Category: {ref.referenceCategory}</p>
)}
</li>
))}
</ul>
Expand Down
Loading