diff --git a/src/Frontend/Components/AttributionCountPerSourcePerLicenseTable/AttributionCountPerSourcePerLicenseTable.tsx b/src/Frontend/Components/AttributionCountPerSourcePerLicenseTable/AttributionCountPerSourcePerLicenseTable.tsx index a01816c4e..e26569823 100644 --- a/src/Frontend/Components/AttributionCountPerSourcePerLicenseTable/AttributionCountPerSourcePerLicenseTable.tsx +++ b/src/Frontend/Components/AttributionCountPerSourcePerLicenseTable/AttributionCountPerSourcePerLicenseTable.tsx @@ -2,10 +2,20 @@ // SPDX-FileCopyrightText: TNG Technology Consulting GmbH // // SPDX-License-Identifier: Apache-2.0 -import { sortBy } from 'lodash'; +import MuiBox from '@mui/material/Box'; +import MuiTable from '@mui/material/Table'; +import MuiTableBody from '@mui/material/TableBody'; +import MuiTableCell from '@mui/material/TableCell'; +import MuiTableContainer from '@mui/material/TableContainer'; +import MuiTableFooter from '@mui/material/TableFooter'; +import MuiTableHead from '@mui/material/TableHead'; +import MuiTableRow from '@mui/material/TableRow'; +import MuiTypography from '@mui/material/Typography'; +import { Criticality } from '../../../shared/shared-types'; +import { text } from '../../../shared/text'; +import { OpossumColors, tableClasses } from '../../shared-styles'; import { LicenseCounts, LicenseNamesWithCriticality } from '../../types/types'; -import { ProjectLicensesTable } from '../ProjectLicensesTable/ProjectLicensesTable'; const classes = { container: { @@ -14,9 +24,7 @@ const classes = { }, }; -const LICENSE_COLUMN_NAME_IN_TABLE = 'License name'; -const FOOTER_TITLE = 'Total'; -const TOTAL_SOURCES_TITLE = 'Total'; +const TOTAL_SOURCE_NAME = 'Total'; interface AttributionCountPerSourcePerLicenseTableProps { licenseCounts: LicenseCounts; @@ -40,34 +48,142 @@ export const AttributionCountPerSourcePerLicenseTable: React.FC< .reduce((partialSum, num) => partialSum + num, 0) .toString(); - const footerRow = [FOOTER_TITLE] - .concat(totalNumberOfAttributionsPerSource) - .concat(totalNumberOfAttributions); - const headerRow = [LICENSE_COLUMN_NAME_IN_TABLE] - .concat(sourceNames) - .concat(TOTAL_SOURCES_TITLE) - .map( - (sourceName) => sourceName.charAt(0).toUpperCase() + sourceName.slice(1), - ); + const footerRow = [ + text.attributionCountPerSourcePerLicenseTable.footerTitle, + '', + ...totalNumberOfAttributionsPerSource, + totalNumberOfAttributions, + ]; + + const headerRow = [ + text.attributionCountPerSourcePerLicenseTable.columnNames.licenseName, + text.attributionCountPerSourcePerLicenseTable.columnNames.criticality, + ...sourceNames, + text.attributionCountPerSourcePerLicenseTable.columnNames.totalSources, + ].map( + (sourceName) => sourceName.charAt(0).toUpperCase() + sourceName.slice(1), + ); Object.entries( props.licenseCounts.attributionCountPerSourcePerLicense, ).forEach( ([licenseName, value]) => - (value[TOTAL_SOURCES_TITLE] = + (value[TOTAL_SOURCE_NAME] = props.licenseCounts.totalAttributionsPerLicense[licenseName]), ); + const tableHead = ( + + + {headerRow.map((columnHeader, columnIndex) => ( + + {columnHeader} + + ))} + + + ); + + const buildTableRow = (licenseName: string, rowIndex: number) => { + const licenseNameCell = ( + + {licenseName} + + ); + + const licenseCriticality = props.licenseNamesWithCriticality[licenseName]; + const criticalityColor = + licenseCriticality === Criticality.High + ? OpossumColors.orange + : licenseCriticality === Criticality.Medium + ? OpossumColors.mediumOrange + : undefined; + + const criticalityCell = ( + + {licenseCriticality ?? '-'} + + ); + + const buildCountBySourceCell = + (columnOffset: number) => (sourceName: string, sourceIdx: number) => { + const columnIndex = columnOffset + sourceIdx; + + return ( + + {props.licenseCounts.attributionCountPerSourcePerLicense[ + licenseName + ][sourceName] || '-'} + + ); + }; + + const singleCells = [licenseNameCell, criticalityCell]; + + return ( + + {singleCells.concat( + sourceNames + .concat(TOTAL_SOURCE_NAME) + .map(buildCountBySourceCell(singleCells.length)), + )} + + ); + }; + + const tableFooter = ( + + + {footerRow.map((total, columnIndex) => ( + + {total} + + ))} + + + ); + return ( - + + {props.title} + + + {tableHead} + + {Object.keys(props.licenseNamesWithCriticality) + .toSorted() + .map(buildTableRow)} + + {tableFooter} + + + ); }; diff --git a/src/Frontend/Components/CriticalLicensesTable/CriticalLicensesTable.tsx b/src/Frontend/Components/CriticalLicensesTable/CriticalLicensesTable.tsx deleted file mode 100644 index 43c6f5ee4..000000000 --- a/src/Frontend/Components/CriticalLicensesTable/CriticalLicensesTable.tsx +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-FileCopyrightText: Meta Platforms, Inc. and its affiliates -// SPDX-FileCopyrightText: TNG Technology Consulting GmbH -// -// SPDX-License-Identifier: Apache-2.0 -import { sortBy } from 'lodash'; - -import { Criticality } from '../../../shared/shared-types'; -import { text } from '../../../shared/text'; -import { clickableIcon } from '../../shared-styles'; -import { LicenseNamesWithCriticality } from '../../types/types'; -import { ProjectLicensesTable } from '../ProjectLicensesTable/ProjectLicensesTable'; - -const LICENSE_COLUMN_NAME_IN_TABLE = 'License name'; -const FOOTER_TITLE = 'Total'; -const TABLE_COLUMN_NAMES = [ - LICENSE_COLUMN_NAME_IN_TABLE, - text.projectStatisticsPopup.criticalLicensesSignalCountColumnName, -]; - -const classes = { - container: { - maxHeight: '400px', - maxWidth: '500px', - marginBottom: '3px', - }, - clickableIcon, - iconButton: { - marginLeft: '8px', - }, -}; - -interface CriticalLicensesTableProps { - totalAttributionsPerLicense: { [licenseName: string]: number }; - licenseNamesWithCriticality: LicenseNamesWithCriticality; - title: string; -} - -interface LicenseNameAndTotalNumberOfAttributions { - licenseName: string; - totalNumberOfAttributions: number; -} - -export const CriticalLicensesTable: React.FC = ( - props, -) => { - const allLicensesWithCriticality = Object.entries( - props.licenseNamesWithCriticality, - ).map((licenseNameAndCriticality) => { - return { - licenseName: licenseNameAndCriticality[0], - criticality: licenseNameAndCriticality[1], - }; - }); - const highCriticalityLicenseNames = getLicenseNamesByCriticality( - allLicensesWithCriticality, - Criticality.High, - ); - const mediumCriticalityLicenseNames = getLicenseNamesByCriticality( - allLicensesWithCriticality, - Criticality.Medium, - ); - const highCriticalityLicensesTotalAttributions: Array = - getCriticalLicenseNamesWithTheirTotalAttributions( - props.totalAttributionsPerLicense, - highCriticalityLicenseNames, - ); - const mediumCriticalityLicensesTotalAttributions: Array = - getCriticalLicenseNamesWithTheirTotalAttributions( - props.totalAttributionsPerLicense, - mediumCriticalityLicenseNames, - ); - const sortedCriticalLicensesTotalAttributions: Array = - sortLicenseNamesWithTotalAttributions( - highCriticalityLicensesTotalAttributions, - ).concat( - sortLicenseNamesWithTotalAttributions( - mediumCriticalityLicensesTotalAttributions, - ), - ); - - return ( - attribution.licenseName, - )} - tableContent={Object.fromEntries( - sortedCriticalLicensesTotalAttributions.map( - ({ licenseName, totalNumberOfAttributions }) => [ - licenseName, - { - [text.projectStatisticsPopup - .criticalLicensesSignalCountColumnName]: - totalNumberOfAttributions, - }, - ], - ), - )} - tableFooter={[FOOTER_TITLE].concat( - getTotalNumberOfAttributions( - sortedCriticalLicensesTotalAttributions, - ).toString(), - )} - licenseNamesWithCriticality={props.licenseNamesWithCriticality} - /> - ); -}; - -function getLicenseNamesByCriticality( - allLicensesWithCriticality: Array<{ - licenseName: string; - criticality: Criticality | undefined; - }>, - criticality: Criticality, -): Array { - return allLicensesWithCriticality - .map((licenseNameAndCriticality) => - licenseNameAndCriticality.criticality === criticality - ? licenseNameAndCriticality.licenseName - : '', - ) - .filter((licenseName) => licenseName !== ''); -} - -function getCriticalLicenseNamesWithTheirTotalAttributions( - totalAttributionsPerLicense: { [licenseName: string]: number }, - criticalLicenseNames: Array, -): Array { - const licenseNamesAndTheirTotalAttributions = criticalLicenseNames.map( - (criticalLicenseName) => { - return { - licenseName: criticalLicenseName, - totalNumberOfAttributions: - totalAttributionsPerLicense[criticalLicenseName], - }; - }, - ); - return licenseNamesAndTheirTotalAttributions.sort(); -} - -function getTotalNumberOfAttributions( - licenseNamesAndTheirTotalAttributions: Array<{ - licenseName: string; - totalNumberOfAttributions: number; - }>, -): number { - return licenseNamesAndTheirTotalAttributions - .map( - (licenseNameAndTotalAttributions) => - licenseNameAndTotalAttributions.totalNumberOfAttributions, - ) - .reduce((total, value) => total + value, 0); -} - -function sortLicenseNamesWithTotalAttributions( - licenseNamesWithTotalAttributions: Array<{ - licenseName: string; - totalNumberOfAttributions: number; - }>, -): Array<{ licenseName: string; totalNumberOfAttributions: number }> { - return sortBy( - licenseNamesWithTotalAttributions, - ({ licenseName }) => licenseName, - ); -} diff --git a/src/Frontend/Components/ProjectLicensesTable/ProjectLicensesTable.tsx b/src/Frontend/Components/ProjectLicensesTable/ProjectLicensesTable.tsx deleted file mode 100644 index d3a30c8a7..000000000 --- a/src/Frontend/Components/ProjectLicensesTable/ProjectLicensesTable.tsx +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-FileCopyrightText: Meta Platforms, Inc. and its affiliates -// SPDX-FileCopyrightText: TNG Technology Consulting GmbH -// -// SPDX-License-Identifier: Apache-2.0 -import MuiBox from '@mui/material/Box'; -import MuiTable from '@mui/material/Table'; -import MuiTableBody from '@mui/material/TableBody'; -import MuiTableCell from '@mui/material/TableCell'; -import MuiTableContainer from '@mui/material/TableContainer'; -import MuiTableFooter from '@mui/material/TableFooter'; -import MuiTableHead from '@mui/material/TableHead'; -import MuiTableRow from '@mui/material/TableRow'; -import MuiTypography from '@mui/material/Typography'; - -import { Criticality } from '../../../shared/shared-types'; -import { OpossumColors, tableClasses } from '../../shared-styles'; -import { LicenseNamesWithCriticality } from '../../types/types'; - -const PLACEHOLDER_ATTRIBUTION_COUNT = '-'; - -interface TableContent { - [rowName: string]: { [columnName: string]: number }; -} - -interface ProjectLicensesTableProps { - title: string; - containerStyle: { [key: string]: string | number }; - columnHeaders: Array; - columnNames: Array; - rowNames: Array; - tableContent: TableContent; - tableFooter?: Array; - licenseNamesWithCriticality: LicenseNamesWithCriticality; -} - -export const ProjectLicensesTable: React.FC = ( - props, -) => { - return ( - - {props.title} - - - - - {props.columnHeaders.map((columnHeader, columnIndex) => ( - - {columnHeader} - - ))} - - - - {props.rowNames.map((rowName, rowIndex) => ( - - {props.columnNames.map((columnName, columnIndex) => ( - - {columnIndex === 0 ? ( - {rowName} - ) : ( - props.tableContent[rowName][columnName] || - PLACEHOLDER_ATTRIBUTION_COUNT - )} - - ))} - - ))} - - {props.tableFooter && ( - - - {props.tableFooter.map((total, columnIndex) => ( - - {total} - - ))} - - - )} - - - - ); -}; diff --git a/src/Frontend/Components/ProjectStatisticsPopup/ProjectStatisticsPopup.tsx b/src/Frontend/Components/ProjectStatisticsPopup/ProjectStatisticsPopup.tsx index 9478d5fe4..53984a514 100644 --- a/src/Frontend/Components/ProjectStatisticsPopup/ProjectStatisticsPopup.tsx +++ b/src/Frontend/Components/ProjectStatisticsPopup/ProjectStatisticsPopup.tsx @@ -21,7 +21,6 @@ import { AccordionWithPieChart } from '../AccordionWithPieChart/AccordionWithPie import { AttributionCountPerSourcePerLicenseTable } from '../AttributionCountPerSourcePerLicenseTable/AttributionCountPerSourcePerLicenseTable'; import { AttributionPropertyCountTable } from '../AttributionPropertyCountTable/AttributionPropertyCountTable'; import { Checkbox } from '../Checkbox/Checkbox'; -import { CriticalLicensesTable } from '../CriticalLicensesTable/CriticalLicensesTable'; import { NotificationPopup } from '../NotificationPopup/NotificationPopup'; import { aggregateAttributionPropertiesFromAttributions, @@ -117,13 +116,6 @@ export const ProjectStatisticsPopup: React.FC = () => { .attributionPropertyCountTable } /> - diff --git a/src/Frontend/Components/ProjectStatisticsPopup/__tests__/ProjectStatisticsPopup.test.tsx b/src/Frontend/Components/ProjectStatisticsPopup/__tests__/ProjectStatisticsPopup.test.tsx index f9408d023..7e27820c6 100644 --- a/src/Frontend/Components/ProjectStatisticsPopup/__tests__/ProjectStatisticsPopup.test.tsx +++ b/src/Frontend/Components/ProjectStatisticsPopup/__tests__/ProjectStatisticsPopup.test.tsx @@ -218,8 +218,8 @@ describe('The ProjectStatisticsPopup', () => { ), ], }); - expect(screen.getAllByText('License name')).toHaveLength(2); - expect(screen.getAllByText('Total')).toHaveLength(3); + expect(screen.getAllByText('License Name')).toHaveLength(1); + expect(screen.getAllByText('Total')).toHaveLength(2); expect(screen.getByText('Follow up')).toBeInTheDocument(); expect(screen.getByText('First party')).toBeInTheDocument(); }); diff --git a/src/e2e-tests/__tests__/project-statistics.test.ts b/src/e2e-tests/__tests__/project-statistics.test.ts index d1f91b578..2aa0cd82d 100644 --- a/src/e2e-tests/__tests__/project-statistics.test.ts +++ b/src/e2e-tests/__tests__/project-statistics.test.ts @@ -54,7 +54,7 @@ test('hidden signals are ignored for project statistics', async ({ await menuBar.openProjectStatistics(); await projectStatisticsPopup.assert.titleIsVisible(); - await projectStatisticsPopup.assert.criticalLicenseCount(2); + await projectStatisticsPopup.assert.totalSignalCount(2); await projectStatisticsPopup.closeButton.click(); await resourcesTree.goto(resourceName1); @@ -67,5 +67,5 @@ test('hidden signals are ignored for project statistics', async ({ await signalsPanel.packageCard.assert.isHidden(packageInfo3); await menuBar.openProjectStatistics(); - await projectStatisticsPopup.assert.criticalLicenseCount(1); + await projectStatisticsPopup.assert.totalSignalCount(1); }); diff --git a/src/e2e-tests/page-objects/ProjectStatisticsPopup.ts b/src/e2e-tests/page-objects/ProjectStatisticsPopup.ts index 624bdfbc7..847d68ef5 100644 --- a/src/e2e-tests/page-objects/ProjectStatisticsPopup.ts +++ b/src/e2e-tests/page-objects/ProjectStatisticsPopup.ts @@ -10,18 +10,18 @@ export class ProjectStatisticsPopup { private readonly node: Locator; readonly title: Locator; readonly closeButton: Locator; - readonly totalCriticalLicensesCount: Locator; + readonly totalSignalCount: Locator; constructor(window: Page) { this.node = window.getByLabel('project statistics'); this.title = this.node.getByRole('heading').getByText('Project Statistics'); this.closeButton = this.node.getByRole('button', { name: 'Close' }); - const signalsCount = window.getByText( - text.projectStatisticsPopup.criticalLicensesSignalCountColumnName, - ); - this.totalCriticalLicensesCount = this.node + this.totalSignalCount = this.node .getByRole('table') - .filter({ has: signalsCount }) + .filter({ + hasText: + text.attributionCountPerSourcePerLicenseTable.columnNames.licenseName, + }) .getByRole('row') .last() .getByRole('cell') @@ -35,10 +35,8 @@ export class ProjectStatisticsPopup { titleIsHidden: async (): Promise => { await expect(this.title).toBeHidden(); }, - criticalLicenseCount: async (count: number): Promise => { - await expect(this.totalCriticalLicensesCount).toContainText( - count.toString(), - ); + totalSignalCount: async (count: number): Promise => { + await expect(this.totalSignalCount).toContainText(count.toString()); }, }; } diff --git a/src/shared/text.ts b/src/shared/text.ts index 984e0af91..a9d2ddfb9 100644 --- a/src/shared/text.ts +++ b/src/shared/text.ts @@ -170,7 +170,6 @@ export const text = { charts: { licenseCountsTable: 'Signals per Sources', attributionPropertyCountTable: 'Attributions Overview', - criticalLicensesTable: 'Critical Licenses', pieChartsSectionHeader: 'Pie Charts', mostFrequentLicenseCountPieChart: 'Most Frequent Licenses', criticalSignalsCountPieChart: 'Signals by Criticality', @@ -178,6 +177,14 @@ export const text = { incompleteAttributionsPieChart: 'Incomplete Attributions', }, }, + attributionCountPerSourcePerLicenseTable: { + footerTitle: 'Total', + columnNames: { + licenseName: 'License Name', + criticality: 'Criticality', + totalSources: 'Total', + }, + }, unsavedChangesPopup: { title: 'Unsaved Changes', message: 'You have unsaved changes. What would you like to do?',