From f4a815d31d030b314a7b85cf830630b258a08341 Mon Sep 17 00:00:00 2001 From: hoangnt2 Date: Tue, 26 Sep 2023 10:44:00 +0700 Subject: [PATCH 1/3] fix(Code): Fix type for components table and delete component modal --- .../components/components/ComponentIndex.tsx | 75 ++++++++++--------- .../components/components/ComponentsTable.tsx | 62 ++++++++------- .../components/DeleteComponentDialog.tsx | 37 +++++---- src/object-types/Component.ts | 57 ++++++++++++++ src/object-types/EmbeddedAttachment.ts | 8 +- src/object-types/EmbeddedComponent.ts | 31 ++++++++ src/object-types/EmbeddedComponents.ts | 38 ++++++++++ src/object-types/EmbeddedRelease.ts | 19 +++++ src/object-types/Vendor.ts | 6 ++ 9 files changed, 255 insertions(+), 78 deletions(-) create mode 100644 src/object-types/Component.ts create mode 100644 src/object-types/EmbeddedComponent.ts create mode 100644 src/object-types/EmbeddedComponents.ts create mode 100644 src/object-types/EmbeddedRelease.ts diff --git a/src/app/[locale]/components/components/ComponentIndex.tsx b/src/app/[locale]/components/components/ComponentIndex.tsx index 9647b8a30..f3d2b7a13 100644 --- a/src/app/[locale]/components/components/ComponentIndex.tsx +++ b/src/app/[locale]/components/components/ComponentIndex.tsx @@ -37,122 +37,125 @@ const ComponentIndex = ({ session }: Props) => { const headerbuttons = { 'Add Component': { link: '/components/add', type: 'primary' }, - 'Import SBOM': { link: '#', type: 'secondary', onClick: handleClickImportSBOM } + 'Import SBOM': { link: '#', type: 'secondary', onClick: handleClickImportSBOM }, } const advancedSearch = [ { fieldName: 'Component Name', value: '', - paramName: 'name' + paramName: 'name', }, { fieldName: 'Categories', value: '', - paramName: 'categories' + paramName: 'categories', }, { fieldName: 'Component Type', value: [ { key: 'OSS', - text: 'OSS' + text: 'OSS', }, { key: 'COTS', - text: 'COTS' + text: 'COTS', }, { key: 'INTERNAL', - text: 'Internal' + text: 'Internal', }, { key: 'INNER_SOURCE', - text: 'Inner Source' + text: 'Inner Source', }, { key: 'SERVICE', - text: 'Service' + text: 'Service', }, { key: 'FREESOFTWARE', - text: 'Freeware' + text: 'Freeware', }, { key: 'CODE_SNIPPET', - text: 'Code Snippet' - } + text: 'Code Snippet', + }, ], - paramName: 'type' + paramName: 'type', }, { fieldName: 'Group', value: [ { key: 'None', - text: 'None' - } + text: 'None', + }, ], - paramName: 'group' + paramName: 'group', }, { fieldName: 'Languages', value: '', - paramName: 'languages' + paramName: 'languages', }, { fieldName: 'Software Platforms', value: '', - paramName: 'softwarePlatform' + paramName: 'softwarePlatform', }, { fieldName: 'Vendors', value: '', - paramName: 'vendors' + paramName: 'vendors', }, { fieldName: 'Operating Systems', value: '', - paramName: 'operatingSystem' + paramName: 'operatingSystem', }, { fieldName: 'Main Licenses', value: '', - paramName: 'mainLicenses' + paramName: 'mainLicenses', }, { fieldName: 'Created By (Email)', value: '', - paramName: 'createdBy' + paramName: 'createdBy', }, { fieldName: 'Created On', value: [ { key: 'EQUAL', - text: '=' + text: '=', }, { key: 'LESS_THAN_OR_EQUAL_TO', - text: '<=' + text: '<=', }, { key: 'GREATER_THAN_OR_EQUAL_TO', - text: '>=' + text: '>=', }, { key: 'BETWEEN', - text: 'Between' - } + text: 'Between', + }, ], - paramName: 'createdOn' - } + paramName: 'createdOn', + }, ] - const handleExportComponent = (withLinkedReleases: boolean) => { + const handleExportComponent = (withLinkedReleases: string) => { const currentDate = new Date().toISOString().split('T')[0] - DownloadService.download(`reports?withlinkedreleases=${withLinkedReleases}&mimetype=xlsx&mailrequest=false&module=components`, - session, `components-${currentDate}.xlsx`) + DownloadService.download( + `reports?withlinkedreleases=${withLinkedReleases}&mimetype=xlsx&mailrequest=false&module=components`, + session, + `components-${currentDate}.xlsx` + ) } return ( @@ -169,8 +172,12 @@ const ComponentIndex = ({ session }: Props) => { {t('Export Spreadsheet')} - handleExportComponent(false)}>{t('Components only')} - handleExportComponent(true)}>{t('Components with releases')} + handleExportComponent('false')}> + {t('Components only')} + + handleExportComponent('true')}> + {t('Components with releases')} + @@ -178,7 +185,7 @@ const ComponentIndex = ({ session }: Props) => {
- + diff --git a/src/app/[locale]/components/components/ComponentsTable.tsx b/src/app/[locale]/components/components/ComponentsTable.tsx index e1522dff1..3ef5ee4f6 100644 --- a/src/app/[locale]/components/components/ComponentsTable.tsx +++ b/src/app/[locale]/components/components/ComponentsTable.tsx @@ -12,9 +12,8 @@ import CommonUtils from '@/utils/common.utils' import { FaTrashAlt, FaPencilAlt } from 'react-icons/fa' import Link from 'next/link' -import { useEffect, useState } from 'react' +import React, { useEffect, useState } from 'react' import styles from '../components.module.css' -import { signOut } from 'next-auth/react' import ApiUtils from '@/utils/api/api.util' import HttpStatus from '@/object-types/enums/HttpStatus' import { useSearchParams } from 'next/navigation' @@ -25,6 +24,9 @@ import { COMMON_NAMESPACE } from '@/object-types/Constants' import { Session } from '@/object-types/Session' import { Spinner } from 'react-bootstrap' import { Table, _ } from '@/components/sw360' +import { EmbeddedComponents } from '@/object-types/EmbeddedComponents' +import { EmbeddedComponent } from '@/object-types/EmbeddedComponent' +import { signOut } from 'next-auth/react' interface Props { session?: Session @@ -36,24 +38,24 @@ const ComponentsTable = ({ session, setNumberOfComponent }: Props) => { const t = useTranslations(COMMON_NAMESPACE) const [componentData, setComponentData] = useState([]) const [loading, setLoading] = useState(true) - const [deletingComponent, setDeletingComponent] = useState('') + const [deletingComponent, setDeletingComponent] = useState('') const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) - const handleClickDelete = (componentId: any) => { + const handleClickDelete = (componentId: string) => { setDeletingComponent(componentId) setDeleteDialogOpen(true) } - const fetchData: any = useCallback( - async (queryUrl: string, signal: any) => { + const fetchData = useCallback( + async (queryUrl: string, signal: AbortSignal) => { const componentsResponse = await ApiUtils.GET(queryUrl, session.user.access_token, signal) if (componentsResponse.status == HttpStatus.OK) { - const components = await componentsResponse.json() + const components = (await componentsResponse.json()) as EmbeddedComponents return components } else if (componentsResponse.status == HttpStatus.UNAUTHORIZED) { - signOut() + return signOut() } else { - return [] + return undefined } }, [session.user.access_token] @@ -65,7 +67,7 @@ const ComponentsTable = ({ session, setNumberOfComponent }: Props) => { const queryUrl = CommonUtils.createUrlWithParams('components', searchParams) const data: Array = [] - const parseTableRowData = (item: any) => { + const parseTableRowData = (item: EmbeddedComponent) => { data.push([ !CommonUtils.isNullOrUndefined(item.defaultVendor) ? item.defaultVendor.shortName : '', [item.id, item.name], @@ -78,14 +80,18 @@ const ComponentsTable = ({ session, setNumberOfComponent }: Props) => { const controller = new AbortController() const signal = controller.signal - fetchData(queryUrl, signal).then((components: any) => { - if (!CommonUtils.isNullOrUndefined(components['_embedded']['sw360:components'])) { - components['_embedded']['sw360:components'].forEach(parseTableRowData) - setComponentData(data) - setNumberOfComponent(data.length) - setLoading(false) - } - }) + fetchData(queryUrl, signal) + .then((components: EmbeddedComponents) => { + if (!CommonUtils.isNullOrUndefined(components['_embedded']['sw360:components'])) { + components['_embedded']['sw360:components'].forEach(parseTableRowData) + setComponentData(data) + setNumberOfComponent(data.length) + setLoading(false) + } + }) + .catch(() => { + console.error('False to fetch components') + }) return () => { controller.abort() @@ -101,7 +107,7 @@ const ComponentsTable = ({ session, setNumberOfComponent }: Props) => { { id: 'name', name: t('Component Name'), - formatter: ([id, name]: any) => + formatter: ([id, name]: Array) => _( {name} @@ -112,17 +118,19 @@ const ComponentsTable = ({ session, setNumberOfComponent }: Props) => { { id: 'mainLicenses', name: t('Main Licenses'), - formatter: (licenseIds: any) => + formatter: (licenseIds: Array) => licenseIds.length > 0 && _( Object.entries(licenseIds) - .map(([, item]: any) => ( - - {' '} - {item}{' '} - - )) - .reduce((prev, curr): any => [prev, ', ', curr]) + .map( + ([, item]: Array): React.ReactNode => ( + + {' '} + {item}{' '} + + ) + ) + .reduce((prev, curr): React.ReactNode[] => [prev, ', ', curr]) ), sort: true, }, diff --git a/src/app/[locale]/components/components/DeleteComponentDialog.tsx b/src/app/[locale]/components/components/DeleteComponentDialog.tsx index a924e1b56..d74f83c14 100644 --- a/src/app/[locale]/components/components/DeleteComponentDialog.tsx +++ b/src/app/[locale]/components/components/DeleteComponentDialog.tsx @@ -24,8 +24,10 @@ import { useTranslations } from 'next-intl' import { COMMON_NAMESPACE } from '@/object-types/Constants' import { useRouter } from 'next/navigation' import ActionType from '@/object-types/enums/ActionType' +import { Session } from '@/object-types/Session' +import Component from '@/object-types/Component' -const DEFAULT_COMPONENT_INFO: any = { name: '', _embedded: { 'sw360:releases': [] } } +const DEFAULT_COMPONENT_INFO: Component = { name: '', _embedded: { 'sw360:releases': [] } } interface Props { componentId?: string @@ -34,11 +36,16 @@ interface Props { actionType?: string } +interface DeleteResponse { + resourceId: string + status: number +} + const DeleteComponentDialog = ({ componentId, show, setShow, actionType }: Props) => { - const { data: session }: any = useSession() + const { data: session } = useSession() as { data: Session } const t = useTranslations(COMMON_NAMESPACE) const router = useRouter() - const [component, setComponent] = useState(DEFAULT_COMPONENT_INFO) + const [component, setComponent] = useState(DEFAULT_COMPONENT_INFO) const [variant, setVariant] = useState('success') const [message, setMessage] = useState('') const [showMessage, setShowMessage] = useState(false) @@ -55,11 +62,11 @@ const DeleteComponentDialog = ({ componentId, show, setShow, actionType }: Props setReloadPage(true) }, []) - const deleteComponent: any = async () => { + const deleteComponent = async () => { const response = await ApiUtils.DELETE(`components/${componentId}`, session.user.access_token) try { if (response.status == HttpStatus.MULTIPLE_STATUS) { - const body = await response.json() + const body = (await response.json()) as Array const deleteStatus = body[0].status if (deleteStatus == HttpStatus.OK) { displayMessage('success', 'Delete component success!') @@ -76,7 +83,7 @@ const DeleteComponentDialog = ({ componentId, show, setShow, actionType }: Props displayMessage('danger', 'Error when processing!') } } else if (response.status == HttpStatus.UNAUTHORIZED) { - signOut() + await signOut() } else { handleError() } @@ -85,8 +92,8 @@ const DeleteComponentDialog = ({ componentId, show, setShow, actionType }: Props } } - const fetchData: any = useCallback( - async (signal: any) => { + const fetchData = useCallback( + async (signal: AbortSignal) => { if (session) { const componentsResponse = await ApiUtils.GET( `components/${componentId}`, @@ -94,11 +101,11 @@ const DeleteComponentDialog = ({ componentId, show, setShow, actionType }: Props signal ) if (componentsResponse.status == HttpStatus.OK) { - const component = await componentsResponse.json() + const component = (await componentsResponse.json()) as Component console.log(component.name) setComponent(component) } else if (componentsResponse.status == HttpStatus.UNAUTHORIZED) { - signOut() + await signOut() } else { setComponent(DEFAULT_COMPONENT_INFO) handleError() @@ -109,7 +116,9 @@ const DeleteComponentDialog = ({ componentId, show, setShow, actionType }: Props ) const handleSubmit = () => { - deleteComponent() + deleteComponent().catch((err) => { + console.log(err) + }) } const handleCloseDialog = () => { @@ -123,7 +132,9 @@ const DeleteComponentDialog = ({ componentId, show, setShow, actionType }: Props useEffect(() => { const controller = new AbortController() const signal = controller.signal - fetchData(signal) + fetchData(signal).catch((err) => { + console.error(err) + }) return () => { controller.abort() @@ -162,7 +173,7 @@ const DeleteComponentDialog = ({ componentId, show, setShow, actionType }: Props {' '} {t('Close')}{' '} - diff --git a/src/object-types/Component.ts b/src/object-types/Component.ts new file mode 100644 index 000000000..b2e92e85a --- /dev/null +++ b/src/object-types/Component.ts @@ -0,0 +1,57 @@ +// Copyright (C) TOSHIBA CORPORATION, 2023. Part of the SW360 Frontend Project. +// Copyright (C) Toshiba Software Development (Vietnam) Co., Ltd., 2023. Part of the SW360 Frontend Project. + +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ + +// SPDX-License-Identifier: EPL-2.0 +// License-Filename: LICENSE + +import Vendor from './Vendor' +import EmbeddedUser from './EmbeddedUser' +import EmbeddedRelease from './EmbeddedRelease' +import EmbeddedAttachment from './EmbeddedAttachment' + +export default interface Component { + id?: string + name?: string + description?: string + createdOn?: string + componentType?: string + subscribers?: Array + moderators?: Array + componentOwner?: string + ownerAccountingUnit?: string + ownerGroup?: string + ownerCountry?: string + visbility?: string + externalIds?: { [k: string]: string } + additionalData?: { [k: string]: string } + mainLicenseIds?: Array + defaultVendorId?: string + categories?: Array + languages?: Array + softwarePlatforms?: Array + operatingSystems?: Array + homepage?: string + mailinglist?: string + wiki?: string + blog?: string + modifiedOn?: string + setBusinessUnit?: boolean + setVisbility?: boolean + _links?: { + self: { + href: string + } + } + _embedded?: { + createdBy?: EmbeddedUser + 'sw360:moderators'?: Array + 'sw360:vendors'?: Array + 'sw360:releases'?: Array + defaultVendor?: Vendor + 'sw360:attachments'?: Array + } +} diff --git a/src/object-types/EmbeddedAttachment.ts b/src/object-types/EmbeddedAttachment.ts index 12350e7e9..d5f102b41 100644 --- a/src/object-types/EmbeddedAttachment.ts +++ b/src/object-types/EmbeddedAttachment.ts @@ -9,12 +9,12 @@ // License-Filename: LICENSE export default interface EmbeddedAttachment { - filename: string, - sha1: string, - attachmentType: string, + filename: string + sha1: string + attachmentType?: string _links: { self: { href: string } } -} \ No newline at end of file +} diff --git a/src/object-types/EmbeddedComponent.ts b/src/object-types/EmbeddedComponent.ts new file mode 100644 index 000000000..8d2bddbc3 --- /dev/null +++ b/src/object-types/EmbeddedComponent.ts @@ -0,0 +1,31 @@ +// Copyright (C) TOSHIBA CORPORATION, 2023. Part of the SW360 Frontend Project. +// Copyright (C) Toshiba Software Development (Vietnam) Co., Ltd., 2023. Part of the SW360 Frontend Project. + +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ + +// SPDX-License-Identifier: EPL-2.0 +// License-Filename: LICENSE + +export interface EmbeddedComponent { + id: string + name: string + description: string + componentType: string + visbility: string + mainLicenseIds: Array + defaultVendor: { + type: string + url: string + shortName: string + fullName: string + } + setBusinessUnit: boolean + setVisbility: boolean + _links: { + self: { + href: string + } + } +} diff --git a/src/object-types/EmbeddedComponents.ts b/src/object-types/EmbeddedComponents.ts new file mode 100644 index 000000000..cd257349d --- /dev/null +++ b/src/object-types/EmbeddedComponents.ts @@ -0,0 +1,38 @@ +// Copyright (C) TOSHIBA CORPORATION, 2023. Part of the SW360 Frontend Project. +// Copyright (C) Toshiba Software Development (Vietnam) Co., Ltd., 2023. Part of the SW360 Frontend Project. + +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ + +// SPDX-License-Identifier: EPL-2.0 +// License-Filename: LICENSE + +import { EmbeddedComponent } from './EmbeddedComponent' + +export interface EmbeddedComponents { + _embedded: { + 'sw360:components': Array + } + _links?: { + first?: { + href: string + } + last?: { + href: string + } + curies?: [ + { + href: string + name: string + templated: boolean + } + ] + } + page?: { + size?: number + totalElements?: number + totalPages?: number + number?: number + } +} diff --git a/src/object-types/EmbeddedRelease.ts b/src/object-types/EmbeddedRelease.ts new file mode 100644 index 000000000..3100298de --- /dev/null +++ b/src/object-types/EmbeddedRelease.ts @@ -0,0 +1,19 @@ +// Copyright (C) TOSHIBA CORPORATION, 2023. Part of the SW360 Frontend Project. +// Copyright (C) Toshiba Software Development (Vietnam) Co., Ltd., 2023. Part of the SW360 Frontend Project. + +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ + +// SPDX-License-Identifier: EPL-2.0 +// License-Filename: LICENSE + +export default interface EmbeddedRelease { + name: string + version: string + _links: { + self: { + href: string + } + } +} diff --git a/src/object-types/Vendor.ts b/src/object-types/Vendor.ts index 84c88f0f0..22648189a 100644 --- a/src/object-types/Vendor.ts +++ b/src/object-types/Vendor.ts @@ -11,4 +11,10 @@ export default interface Vendor { id?: string fullName?: string + shortName?: string + _links?: { + self: { + href: string + } + } } From bfab794ba2d618c53fedc112ce653d89d5702226 Mon Sep 17 00:00:00 2001 From: hoangnt2 Date: Tue, 26 Sep 2023 14:26:42 +0700 Subject: [PATCH 2/3] fix(Code): Fix type checking for Import SBOM and add missing message --- messages/en.json | 3 + messages/ja.json | 3 + messages/vi.json | 3 + messages/zh.json | 3 + .../components/components/ImportSBOMModal.tsx | 198 ++++++++++-------- 5 files changed, 125 insertions(+), 85 deletions(-) diff --git a/messages/en.json b/messages/en.json index 15e4420e4..584ac5681 100644 --- a/messages/en.json +++ b/messages/en.json @@ -395,6 +395,8 @@ "Number of Security Vulnerabilities": "Number of Security Vulnerabilities", "number_of_attachments_not_match_condition": "There has to be exactly one source attachment, but there are {count} at this release. Please come back once you corrected that.", "New Project": "New Project", + "New Components": "New Components", + "New Release": "New Release", "Name of the component": "Name of the component", "NAME_COMPONENT": "Name of the component", "Obligations": "Obligations", @@ -570,6 +572,7 @@ "to edit the project relation": "to edit the project relation", "Test Manager": "Test Manager", "The component cannot be deleted, since it contains releases Please delete the releases first": "The component {name} cannot be deleted, since it contains {releaseCount} releases. Please delete the releases first.", + "The new Component and new Release will be created, do you want to import?": "The new Component and new Release will be created, do you want to import?", "This release contains": "This release {name} contains: ", "This currently only supports SPDX RDF/XML files with a uniq described top level node": "This currently only supports SPDX RDF/XML files with a uniq described top level node", "The release has been successfully linked to project": "The release {releaseName} has been successfully linked to project {projectName}", diff --git a/messages/ja.json b/messages/ja.json index dc928f00d..5bc4edf58 100644 --- a/messages/ja.json +++ b/messages/ja.json @@ -390,6 +390,8 @@ "Number of Security Vulnerabilities": "セキュリティ脆弱性の数", "number_of_attachments_not_match_condition": "There has to be exactly one source attachment, but there are {count} at this release. Please come back once you corrected that.", "New Project": "NOT TRANSLATED", + "New Components": "New Components", + "New Release": "New Release", "Obligations": "Obligations", "OPEN": "開く", "Open": "NOT TRANSLATED", @@ -563,6 +565,7 @@ "Tag": "NOT TRANSLATED", "Test and Diagnostics": "NOT TRANSLATED", "The component cannot be deleted, since it contains releases Please delete the releases first": "コンポーネント {name}{releaseCount} を持つため消去できない.最初にリリースを消去してください", + "The new Component and new Release will be created, do you want to import?": "The new Component and new Release will be created, do you want to import?", "This release contains": "このリリース {name} には次の内容が含まれています:", "This currently only supports SPDX RDF/XML files with a uniq described top level node": "This currently only supports SPDX RDF/XML files with a uniq described top level node", "The release has been successfully linked to project": "The release {releaseName} has been successfully linked to project {projectName}", diff --git a/messages/vi.json b/messages/vi.json index bc2d43d87..576017211 100644 --- a/messages/vi.json +++ b/messages/vi.json @@ -390,6 +390,8 @@ "number_of_attachments_not_match_condition": "There has to be exactly one source attachment, but there are {count} at this release. Please come back once you corrected that.", "None": "NOT TRANSLATED", "New Project": "NOT TRANSLATED", + "New Components": "New Components", + "New Release": "New Release", "Obligations": "Obligations", "OPEN": "Mở", "Open": "NOT TRANSLATED", @@ -561,6 +563,7 @@ "Tag": "NOT TRANSLATED", "Test and Diagnostics": "NOT TRANSLATED", "The component cannot be deleted, since it contains releases Please delete the releases first": "Thành phần {name} không thể bị xóa, vì nó chứa {releaseCount} bản phát hành. Vui lòng xóa các bản phát hành trước.", + "The new Component and new Release will be created, do you want to import?": "The new Component and new Release will be created, do you want to import?", "This release contains": "Bản phát hành {name} này chứa: ", "This currently only supports SPDX RDF/XML files with a uniq described top level node": "This currently only supports SPDX RDF/XML files with a uniq described top level node", "The release has been successfully linked to project": "The release {releaseName} has been successfully linked to project {projectName}", diff --git a/messages/zh.json b/messages/zh.json index 1a71495c5..4ebff473b 100644 --- a/messages/zh.json +++ b/messages/zh.json @@ -384,6 +384,8 @@ "No attachments yet": "还没有附件。", "None": "NOT TRANSLATED", "New Project": "NOT TRANSLATED", + "New Components": "New Components", + "New Release": "New Release", "Obligations": "Obligations", "OPEN": "打开", "Open": "NOT TRANSLATED", @@ -556,6 +558,7 @@ "Technical writer": "NOT TRANSLATED", "Tag": "NOT TRANSLATED", "The projects have been successfully linked to project": "The projects have been successfully linked to project", + "The new Component and new Release will be created, do you want to import?": "The new Component and new Release will be created, do you want to import?", "Test and Diagnostics": "NOT TRANSLATED", "to edit the project relation": "to edit the project relation", "The component cannot be deleted, since it contains releases Please delete the releases first": "组件 {name} 无法被删除,因为它包含发行版本 {releaseCount}。请先删除发行版本。", diff --git a/src/app/[locale]/components/components/ImportSBOMModal.tsx b/src/app/[locale]/components/components/ImportSBOMModal.tsx index b69973482..f02155aad 100644 --- a/src/app/[locale]/components/components/ImportSBOMModal.tsx +++ b/src/app/[locale]/components/components/ImportSBOMModal.tsx @@ -9,15 +9,16 @@ // License-Filename: LICENSE 'use client' -import { COMMON_NAMESPACE } from "@/object-types/Constants" -import { useTranslations } from "next-intl" -import React, { useRef, useState } from "react" -import { Modal, Button, Alert } from "react-bootstrap" +import { COMMON_NAMESPACE } from '@/object-types/Constants' +import { useTranslations } from 'next-intl' +import React, { useRef, useState } from 'react' +import { Modal, Button, Alert } from 'react-bootstrap' import styles from '../components.module.css' -import ApiUtils from "@/utils/api/api.util" -import { Session } from "@/object-types/Session" -import HttpStatus from "@/object-types/enums/HttpStatus" +import ApiUtils from '@/utils/api/api.util' +import { Session } from '@/object-types/Session' +import HttpStatus from '@/object-types/enums/HttpStatus' import { useRouter } from 'next/navigation' +import Component from '@/object-types/Component' interface Props { show: boolean @@ -30,16 +31,22 @@ enum ImportSBOMState { PREPARE_IMPORT, IMPORTING, IMPORTED, - IMPORT_ERROR + IMPORT_ERROR, +} + +interface PrepareImportData { + message?: string + componentsName?: string + releasesName?: string } const ImportSBOMModal = ({ show, setShow, session }: Props) => { const t = useTranslations(COMMON_NAMESPACE) const [importState, setImportState] = useState(ImportSBOMState.INIT_STATE) - const [prepateImportData, setPrepareImportData] = useState(undefined) + const [prepateImportData, setPrepareImportData] = useState(undefined) const [notAllowedMessageDisplayed, setNotAllowedMessageDisplayed] = useState(false) - const selectedFile = useRef(undefined) - const inputRef = useRef(undefined) + const selectedFile = useRef(undefined) + const inputRef = useRef(undefined) const router = useRouter() const handleFileChange = (e: React.ChangeEvent) => { @@ -53,7 +60,7 @@ const ImportSBOMModal = ({ show, setShow, session }: Props) => { return } setImportState(ImportSBOMState.IMPORTING) - prepareImport() + prepareImport().catch((err) => console.error(err)) } const prepareImport = async () => { @@ -61,9 +68,13 @@ const ImportSBOMModal = ({ show, setShow, session }: Props) => { const formData = new FormData() formData.append('file', selectedFile.current, selectedFile.current.name) - const response = await ApiUtils.POST('components/prepareImport/SBOM?type=SPDX', formData, session.user.access_token) + const response = await ApiUtils.POST( + 'components/prepareImport/SBOM?type=SPDX', + formData, + session.user.access_token + ) if (response.status === HttpStatus.OK) { - const responseData = await response.json() + const responseData = (await response.json()) as PrepareImportData setPrepareImportData(responseData) setImportState(ImportSBOMState.PREPARE_IMPORT) } else if (response.status === HttpStatus.INTERNAL_SERVER_ERROR) { @@ -75,10 +86,18 @@ const ImportSBOMModal = ({ show, setShow, session }: Props) => { const formData = new FormData() formData.append('file', selectedFile.current, selectedFile.current.name) - const response = await ApiUtils.POST('components/import/SBOM?type=SPDX', formData, session.user.access_token) - if (response.status === HttpStatus.OK) { - const responseData = await response.json() - router.push(`/components/detail/${responseData.id}`) + try { + const response = await ApiUtils.POST( + 'components/import/SBOM?type=SPDX', + formData, + session.user.access_token + ) + if (response.status === HttpStatus.OK) { + const responseData = (await response.json()) as Component + router.push(`/components/detail/${responseData.id}`) + } + } catch (err) { + console.error(err) } } @@ -99,7 +118,7 @@ const ImportSBOMModal = ({ show, setShow, session }: Props) => { return } setImportState(ImportSBOMState.IMPORTING) - prepareImport() + prepareImport().catch((err) => console.log(err)) } } @@ -112,85 +131,101 @@ const ImportSBOMModal = ({ show, setShow, session }: Props) => { return ( closeModal()} backdrop='static' centered size='lg'> - {t('Upload SBOM')} + + {t('Upload SBOM')} + - setNotAllowedMessageDisplayed(false)} dismissible show={notAllowedMessageDisplayed}> - {(selectedFile.current) && `${selectedFile.current.name} has type not allowed, please upload files of type rdf,spdx.`} + setNotAllowedMessageDisplayed(false)} + dismissible + show={notAllowedMessageDisplayed} + > + {selectedFile.current && + `${selectedFile.current.name} has type not allowed, please upload files of type rdf,spdx.`} - {(importState === ImportSBOMState.INIT_STATE || importState === ImportSBOMState.IMPORT_ERROR) && + {(importState === ImportSBOMState.INIT_STATE || importState === ImportSBOMState.IMPORT_ERROR) && ( <> -
-

{t('Upload BOM document as')}

- {t('This currently only supports SPDX RDF/XML files with a uniq described top level node')}. -
- {t('If the wrong SPDX is entered, the information will not be registered correctly')}. -
-
-
-
+

+ {t('Upload BOM document as')} +

+ {t('This currently only supports SPDX RDF/XML files with a uniq described top level node')}. +
+ {t('If the wrong SPDX is entered, the information will not be registered correctly')}. +
+
+
+
- {t('Drop a File Here')} -
- {t('Or')} -
- - + {t('Drop a File Here')} +
+ {t('Or')} +
+ + +
-
- } - {(importState === ImportSBOMState.IMPORTING) && -

{t('Importing')}...

- } - {(importState === ImportSBOMState.PREPARE_IMPORT) - && + )} + {importState === ImportSBOMState.IMPORTING &&

{t('Importing')}...

} + {importState === ImportSBOMState.PREPARE_IMPORT && ( <> - { - (prepateImportData.message) - ? -

{prepateImportData.message}

- : + {prepateImportData.message ? ( +

{prepateImportData.message}

+ ) : ( +
+

+ + {t('The new Component and new Release will be created, do you want to import?')} + +

-

{t('The new Component and new Release will be created, do you want to import?')}

-
- {t('New Components')}: - {prepateImportData.componentsName} -
-
- {t('New Release')}s: - {prepateImportData.releasesName} -
+ {t('New Components')}: + {prepateImportData.componentsName}
- } +
+ {t('New Release')}s: + {prepateImportData.releasesName} +
+
+ )} - - } + )} - {(importState === ImportSBOMState.IMPORT_ERROR) - && + {importState === ImportSBOMState.IMPORT_ERROR && ( <>

{'Failed :('}

- {JSON.stringify({ "readyState": 4, "responseText": "", "status": 500, "statusText": "error" })} + {JSON.stringify({ readyState: 4, responseText: '', status: 500, statusText: 'error' })}
- - } + )}
+ {importState === ImportSBOMState.PREPARE_IMPORT && !prepateImportData.message && ( + + )} - { - (importState === ImportSBOMState.PREPARE_IMPORT && !prepateImportData.message) - && - - } ) } -export default ImportSBOMModal \ No newline at end of file +export default ImportSBOMModal From 3d2e0206070e9c8b154a7758072a97eef8a20e31 Mon Sep 17 00:00:00 2001 From: hoangnt2 Date: Tue, 26 Sep 2023 15:01:09 +0700 Subject: [PATCH 3/3] fix(Code): Fix type checking and add missing key for Advance Search --- src/components/sw360/AdvancedSearch/AdvancedSearch.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/sw360/AdvancedSearch/AdvancedSearch.tsx b/src/components/sw360/AdvancedSearch/AdvancedSearch.tsx index 5297e6f24..25cc2af84 100644 --- a/src/components/sw360/AdvancedSearch/AdvancedSearch.tsx +++ b/src/components/sw360/AdvancedSearch/AdvancedSearch.tsx @@ -44,10 +44,10 @@ function AdvancedSearch({ title = 'Advanced Search', fields }: Props) { const [searchParams, setSearchParam] = useState(params) const [createdOnSearchOption, setCreatedOnSearchOption] = useState('') - const handleSearchParam = (event: any) => { + const handleSearchParam = (event: React.ChangeEvent) => { setSearchParam((prev: SearchParams) => ({ ...prev, - [event.target.name]: event.target.value, + [event.target?.name]: event.target.value, })) } @@ -58,7 +58,7 @@ function AdvancedSearch({ title = 'Advanced Search', fields }: Props) { const submitSearch = () => { const currentUrl = new URL(window.location.href) const searchUrl = new URL(currentUrl.origin + currentUrl.pathname) - Object.entries(searchParams).forEach(([key, value]: any) => { + Object.entries(searchParams).forEach(([key, value]: Array) => { if (!CommonUtils.isNullEmptyOrUndefinedString(value)) { searchUrl.searchParams.append(key, value) } @@ -73,7 +73,7 @@ function AdvancedSearch({ title = 'Advanced Search', fields }: Props) { fieldList = fields.map((field: Field) => { if (field.paramName === 'createdOn' && Array.isArray(field.value)) { return ( - <> +
{t(field.fieldName)} )} - +
) } else if (typeof field.value === 'string') { return (