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 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,6 @@ src/pluginMap.ts
/apps_backup/*

# Federation Temp files
/.__mf__temp
/.__mf__temp
public/licenses/beBomData.json
public/licenses/feBomData.json
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"supported-browsers": "node ./scripts/generate-supported-browsers.mjs",
"build": "npm run setup && npm run build:meta && npm run supported-browsers && npm run build:react",
"setup": "tsx scripts/setup-care-apps.ts",
"postinstall": "tsx scripts/install-platform-deps.ts",
"postinstall": "tsx scripts/install-platform-deps.ts && tsx scripts/fetchSbomData.ts",

"test": "snyk test",
"cypress:open": "cross-env NODE_ENV=development cypress open",
"cypress:run": "cross-env NODE_ENV=development cypress run",
Expand Down
Empty file added public/licenses/.gitkeep
Copy link
Member

Choose a reason for hiding this comment

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

instead of this, we can do in the fetch sbom data script.

await mkdir(path, { recursive: true })

Empty file.
10 changes: 9 additions & 1 deletion public/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,8 @@
"capture_cover_photo": "Capture Cover Photo",
"card": "Card",
"care": "CARE",
"care_backend": "Care Backend",
"care_frontend": "Care Frontend",
"category": "Category",
"caution": "Caution",
"central_nursing_station": "Central Nursing Station",
Expand Down Expand Up @@ -646,7 +648,9 @@
"continue": "Continue",
"continue_watching": "Continue watching",
"contribute_github": "Contribute on Github",
"copied_to_clipboard": "Copied to clipboard",
"copied_to_clipboard": "Copied to clipboard!",
"copilot_thinking": "Copilot is thinking...",
"copy_bom_json": "Copy BOM JSON",
"copy_phone_number": "Copy Phone Number",
"copying_is_not_allowed": "Copying is not allowed",
"could_not_load_page": "We are facing some difficulties showing the Page you were looking for. Our Engineers have been notified and we'll make sure that this is resolved on the fly!",
Expand Down Expand Up @@ -1174,6 +1178,7 @@
"latitude_invalid": "Latitude must be between -90 and 90",
"left": "Left",
"length": "Length ({{unit}})",
"license": "License",
"link_abha_number": "Link ABHA Number",
"link_abha_profile": "Link ABHA Profile",
"link_camera_and_bed": "Link bed to Camera",
Expand Down Expand Up @@ -1276,6 +1281,7 @@
"my_doctors": "My Doctors",
"my_profile": "My Profile",
"my_schedules": "My Schedules",
Copy link
Member

Choose a reason for hiding this comment

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

why was this translation deleted?

"n_a": "N/A",
"name": "Name",
"name_of_hospital": "Name of Hospital",
"name_of_shifting_approving_facility": "Name of shifting approving facility",
Expand Down Expand Up @@ -1410,6 +1416,7 @@
"otp_verification_success": "OTP has been verified successfully.",
"out_of_range_error": "Value must be between {{ start }} and {{ end }}.",
"oxygen_information": "Oxygen Information",
"packages": "Packages",
"page_load_error": "Couldn't Load the Page",
"page_not_found": "Page Not Found",
"pain": "Pain",
Expand Down Expand Up @@ -1847,6 +1854,7 @@
"something_wrong": "Something went wrong! Try again later!",
"sort_by": "Sort By",
"source": "Source",
"spdx_sbom_version": "SPDX SBOM Version",
"spokes": "Spoke Facilities",
"srf_id": "SRF ID",
"staff": "Staff",
Expand Down
77 changes: 77 additions & 0 deletions scripts/fetchSbomData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import fs from "fs";
import fetch from "node-fetch";

const FE_SBOM_URL =
"https://api.github.com/repos/ohcnetwork/care_fe/dependency-graph/sbom";
const BE_SBOM_URL =
"https://api.github.com/repos/ohcnetwork/care/dependency-graph/sbom";

interface GitHubSbomApiResponse {
sbom: {
spdxVersion: string;
dataLicense: string;
SPDXID: string;
name: string;
documentNamespace: string;
creationInfo: {
creators: string[];
created: string;
};
packages: {
name: string;
SPDXID: string;
versionInfo: string;
downloadLocation: string;
filesAnalyzed: boolean;
licenseConcluded?: string;
copyrightText?: string;
externalRefs: {
referenceCategory: string;
referenceType: string;
referenceLocator: string;
}[];
licenseDeclared?: string;
}[];
relationships: {
spdxElementId: string;
relatedSpdxElement: string;
relationshipType: string;
}[];
};
}

const fetchSBOMData = async (url: string): Promise<GitHubSbomApiResponse> => {
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
});

if (!response.ok) {
throw new Error(
`Error fetching SBOM data from ${url}: ${response.statusText}`,
);
}

return (await response.json()) as GitHubSbomApiResponse;
};

const fetchData = async (): Promise<void> => {
const [frontendData, backendData] = await Promise.all([
fetchSBOMData(FE_SBOM_URL),
fetchSBOMData(BE_SBOM_URL),
]);

fs.writeFileSync(
"./public/licenses/feBomData.json",
JSON.stringify(frontendData, null, 2),
);

fs.writeFileSync(
"./public/licenses/beBomData.json",
JSON.stringify(backendData, null, 2),
);
};

fetchData();
146 changes: 62 additions & 84 deletions src/components/Licenses/SBOMViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,79 +1,65 @@
import { useQuery } from "@tanstack/react-query";
import dayjs from "dayjs";
import React, { useState } from "react";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { useTranslation } from "react-i18next";

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 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;
};
components?: CycloneDXComponent[];
}
const fetchJsonData = async (url: string) => {
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch data");
return response.json();
};

const BOMDisplay: React.FC = () => {
const { t } = useTranslation();
const [copyStatus, setCopyStatus] = useState(false);
const [showExternalRefs, setShowExternalRefs] = useState<number | null>(null);
const [activeTab, setActiveTab] = useState<string>("bom");

const {
data: feBomData,
isLoading: feBomLoading,
error: feBomError,
} = useQuery({
queryKey: ["feBomData"],
queryFn: () => fetchJsonData("/licenses/feBomData.json"),
});

const {
data: beBomData,
isLoading: beBomLoading,
error: beBomError,
} = useQuery({
queryKey: ["beBomData"],
queryFn: () => fetchJsonData("/licenses/beBomData.json"),
});

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

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

const bomData = (activeTab === "bom" ? feBomData : beBomData) as CycloneDXBOM;
if (feBomLoading || beBomLoading) {
return <div>{t("loading")}</div>;
}

if (feBomError || beBomError) {
return <div>{t("error_404")}</div>;
}

const packages = bomData?.sbom?.packages || [];

return (
<div className="p-4">
Expand All @@ -84,73 +70,63 @@ const BOMDisplay: React.FC = () => {
}`}
onClick={() => setActiveTab("bom")}
>
Care Frontend
{t("care_frontend")}
</button>
<button
className={`text-md w-full rounded-md px-4 py-2 transition-all duration-300 md:w-auto ${
activeTab === "beBom" ? "bg-primary text-white" : "bg-gray-200"
}`}
onClick={() => setActiveTab("beBom")}
>
Care Backend
{t("care_backend")}
</button>
</div>
<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"})
{t("spdx_sbom_version") + ": " + bomData?.sbom?.spdxVersion ||
t("n_a")}
</h2>
<p className="text-sm text-gray-500">
Created on:{" "}
{bomData.metadata?.timestamp
? dayjs(bomData.metadata.timestamp).format("MMMM D, YYYY")
: "N/A"}
{t("created_on")}{" "}
{bomData?.sbom?.creationInfo?.created
? dayjs(bomData.sbom.creationInfo.created).format("MMMM D, YYYY")
: t("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:
{t("packages")}
{":"}
</h3>
{bomData.components?.map((component, index) => (
{packages.map((pkg: any, index: number) => (
Copy link
Member

Choose a reason for hiding this comment

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

now that we have strong types defined, let's not use any.

Suggested change
{packages.map((pkg: any, index: number) => (
{packages.map((package, 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 ||
"#"
}
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 || t("n_a")} v${pkg.versionInfo || t("n_a")}`}
</strong>
</a>
{component.licenses && component.licenses[0]?.license?.id && (
{pkg.licenseConcluded && (
<p className="text-base">
License:{" "}
{t("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 || t("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 +140,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: any, idx: any) => (
<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 || t("n_a")}
</a>
{ref.comment && <p>Comment: {ref.comment}</p>}
{ref.referenceCategory && (
<p>{t("category") + ": " + ref.referenceCategory}</p>
)}
</li>
))}
</ul>
Expand All @@ -187,12 +165,12 @@ const BOMDisplay: React.FC = () => {
onCopy={handleCopy}
>
<button className="text-md hover:bg-primary-dark w-full rounded-md bg-primary px-4 py-2 text-white transition-all duration-300 focus:outline-none md:w-auto">
Copy BOM JSON
{t("copy_bom_json")}
</button>
</CopyToClipboard>
{copyStatus && (
<span className="mt-2 block text-sm text-gray-600">
Copied to clipboard!
{t("copied_to_clipboard")}
</span>
)}
</div>
Expand Down
Loading