Skip to content

Commit 8fa8234

Browse files
authored
Merge pull request #2740 from opossum-tool/feat/import-scancode
Feat: Import ScanCode Json Files
2 parents 9a35b67 + f183774 commit 8fa8234

22 files changed

+45660
-107
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
"test:unit": "jest",
119119
"test:changed": "jest --onlyChanged",
120120
"test:watch": "jest --watch",
121-
"test:e2e": "start-server-and-test \"tsc -p src/ElectronBackend && vite -m e2e\" http-get://localhost:5173/index.html \"yarn playwright test -c src/e2e-tests/playwright.config.ts\"",
121+
"test:e2e": "NODE_ENV=e2e start-server-and-test \"tsc -p src/ElectronBackend && vite -m e2e\" http-get://localhost:5173/index.html \"yarn playwright test -c src/e2e-tests/playwright.config.ts\"",
122122
"test:e2e:ci": "yarn playwright test -c src/e2e-tests/playwright.config.ts",
123123
"lint": "eslint --fix",
124124
"lint-check": "eslint",

src/ElectronBackend/main/__tests__/openFileFromCliOrEnvVariableIfProvided.test.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ describe('openFileFromCli', () => {
2525

2626
it.each`
2727
inputFileName | extraParameter
28-
${'inputFile.json'} | ${null}
29-
${'inputFile.json.gz'} | ${null}
30-
${'inputFile.json'} | ${'--dev'}
28+
${'inputFile.opossum'} | ${null}
29+
${'inputFile.opossum'} | ${'--dev'}
3130
`(
3231
'calls openFile with input file $inputFileName and extraParameter $extraParameter',
3332
async ({ inputFileName, extraParameter }) => {

src/ElectronBackend/main/listeners.ts

+16-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
ExportSpdxDocumentYamlArgs,
2020
ExportType,
2121
FileFormatInfo,
22+
FileType,
2223
OpenLinkArgs,
2324
PackageInfo,
2425
SaveFileArgs,
@@ -31,6 +32,7 @@ import {
3132
} from '../errorHandling/errorHandling';
3233
import { loadInputAndOutputFromFilePath } from '../input/importFromFile';
3334
import { serializeAttributions } from '../input/parseInputData';
35+
import { convertScancodeToOpossum } from '../opossum-file/convertScancodeToOpossum';
3436
import { writeCsvToFile } from '../output/writeCsvToFile';
3537
import { writeSpdxFile } from '../output/writeSpdxFile';
3638
import { GlobalBackendState, OpossumOutputFile } from '../types/types';
@@ -180,13 +182,15 @@ export function getImportFileConvertAndLoadListener(
180182
): (
181183
_: Electron.IpcMainInvokeEvent,
182184
resourceFilePath: string,
185+
fileType: FileType,
183186
opossumFilePath: string,
184187
) => Promise<boolean | null> {
185188
return createListenerCallbackWithErrorHandling(
186189
mainWindow,
187190
async (
188191
_: Electron.IpcMainInvokeEvent,
189192
resourceFilePath: string,
193+
fileType: FileType,
190194
opossumFilePath: string,
191195
) => {
192196
if (!resourceFilePath.trim() || !fs.existsSync(resourceFilePath)) {
@@ -215,11 +219,18 @@ export function getImportFileConvertAndLoadListener(
215219
resourceFilePath = tryToGetInputFileFromOutputFile(resourceFilePath);
216220
}
217221

218-
await writeOpossumFile({
219-
path: opossumFilePath,
220-
input: getInputJson(resourceFilePath),
221-
output: getOutputJson(resourceFilePath),
222-
});
222+
switch (fileType) {
223+
case FileType.LEGACY_OPOSSUM:
224+
await writeOpossumFile({
225+
path: opossumFilePath,
226+
input: getInputJson(resourceFilePath),
227+
output: getOutputJson(resourceFilePath),
228+
});
229+
break;
230+
case FileType.SCANCODE_JSON:
231+
await convertScancodeToOpossum(resourceFilePath, opossumFilePath);
232+
break;
233+
}
223234

224235
logger.info('Updating global backend state');
225236
initializeGlobalBackendState(opossumFilePath, true);

src/ElectronBackend/main/menu.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
import { app, BrowserWindow, Menu, shell } from 'electron';
77

88
import { AllowedFrontendChannels } from '../../shared/ipc-channels';
9-
import { ExportType, FileFormatInfo } from '../../shared/shared-types';
9+
import {
10+
ExportType,
11+
FileFormatInfo,
12+
FileType,
13+
} from '../../shared/shared-types';
1014
import { isFileLoaded } from '../utils/getLoadedFile';
1115
import { getGlobalBackendState } from './globalBackendState';
1216
import {
@@ -83,9 +87,15 @@ const INITIALLY_DISABLED_ITEMS_INFO: Record<
8387

8488
export const importFileFormats: Array<FileFormatInfo> = [
8589
{
90+
fileType: FileType.LEGACY_OPOSSUM,
8691
name: 'Legacy Opossum File',
8792
extensions: ['json', 'json.gz'],
8893
},
94+
{
95+
fileType: FileType.SCANCODE_JSON,
96+
name: 'ScanCode File',
97+
extensions: ['json'],
98+
},
8999
];
90100

91101
export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {

src/ElectronBackend/main/openFileFromCliOrEnvVariableIfProvided.ts

+1-16
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
import { BrowserWindow } from 'electron';
66
import log from 'electron-log';
77

8-
import { convertScancodeToOpossum } from '../opossum-file/convertScancodeToOpossum';
9-
import { getFilePathWithAppendix } from '../utils/getFilePathWithAppendix';
108
import { handleOpeningFile } from './listeners';
119
import { activateMenuItems } from './menu';
1210

@@ -16,11 +14,7 @@ export async function openFileFromCliOrEnvVariableIfProvided(
1614
let inputFileName: string | null = null;
1715

1816
function fileHasValidEnding(arg: string): boolean {
19-
return (
20-
arg.endsWith('.json') ||
21-
arg.endsWith('.json.gz') ||
22-
arg.endsWith('.opossum')
23-
);
17+
return arg.endsWith('.opossum');
2418
}
2519

2620
for (const arg of process.argv) {
@@ -42,15 +36,6 @@ export async function openFileFromCliOrEnvVariableIfProvided(
4236
}
4337
}
4438

45-
const inputScanCodeFileFromEnvVariable: string | undefined =
46-
process.env.SCANCODE_JSON;
47-
if (!inputFileName && inputScanCodeFileFromEnvVariable) {
48-
inputFileName = await convertScancodeToOpossum(
49-
inputScanCodeFileFromEnvVariable,
50-
getFilePathWithAppendix(inputScanCodeFileFromEnvVariable, '.opossum'),
51-
);
52-
}
53-
5439
if (inputFileName) {
5540
await handleOpeningFile(mainWindow, inputFileName, activateMenuItems);
5641
}

src/ElectronBackend/opossum-file/__tests__/convertToOpossum.test.ts

+5-11
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,16 @@ import { parseOpossumFile } from '../../input/parseFile';
1111
import { isOpossumFileFormat } from '../../utils/isOpossumFileFormat';
1212
import { convertScancodeToOpossum } from '../convertScancodeToOpossum';
1313

14-
function getTempPath(): string {
15-
return join(tmpdir(), uniqueId('opossum_'));
16-
}
17-
1814
describe('successfulConversionOfScanCodeFile', () => {
1915
const SCANCODE_TEST_FILE = join(__dirname, 'scancode.json');
2016

2117
it('should convert the ScanCode file and return a path to a valid .opossum file', async () => {
22-
const path = await convertScancodeToOpossum(
23-
SCANCODE_TEST_FILE,
24-
getTempPath(),
25-
);
26-
expect(existsSync(path)).toBe(true);
27-
expect(isOpossumFileFormat(path)).toBe(true);
18+
const opossumPath = join(tmpdir(), `${uniqueId('opossum_')}.opossum`);
19+
await convertScancodeToOpossum(SCANCODE_TEST_FILE, opossumPath);
20+
expect(existsSync(opossumPath)).toBe(true);
21+
expect(isOpossumFileFormat(opossumPath)).toBe(true);
2822

29-
const parsingResult = await parseOpossumFile(path);
23+
const parsingResult = await parseOpossumFile(opossumPath);
3024
expect(parsingResult).toHaveProperty('input');
3125
});
3226
});

src/ElectronBackend/opossum-file/convertScancodeToOpossum.ts

+3-7
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,23 @@ import { app } from 'electron';
77
import { join } from 'path';
88
import { promisify } from 'util';
99

10-
import { getFilePathWithAppendix } from '../utils/getFilePathWithAppendix';
11-
1210
const execFile = promisify(execFileCallback);
1311

1412
const OPOSSUM_FILE_EXECUTABLE = join(
1513
app?.getAppPath?.() ?? './',
14+
process.env.NODE_ENV === 'e2e' ? '../..' : '',
1615
'bin/opossum-file',
1716
);
1817

1918
export async function convertScancodeToOpossum(
2019
pathToScanCode: string,
21-
pathToOutput: string,
22-
): Promise<string> {
23-
const pathToOpossum = getFilePathWithAppendix(pathToOutput, '.opossum');
24-
20+
pathToOpossum: string,
21+
): Promise<void> {
2522
await execFile(OPOSSUM_FILE_EXECUTABLE, [
2623
'generate',
2724
'-o',
2825
pathToOpossum,
2926
'--scan-code-json',
3027
pathToScanCode,
3128
]);
32-
return pathToOpossum;
3329
}

src/ElectronBackend/preload.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ const electronAPI: ElectronAPI = {
1717
ipcRenderer.invoke(IpcChannel.ImportFileSelectInput, fileFormat),
1818
importFileSelectSaveLocation: (defaultPath) =>
1919
ipcRenderer.invoke(IpcChannel.ImportFileSelectSaveLocation, defaultPath),
20-
importFileConvertAndLoad: (inputFilePath, opossumFilePath) =>
20+
importFileConvertAndLoad: (inputFilePath, fileType, opossumFilePath) =>
2121
ipcRenderer.invoke(
2222
IpcChannel.ImportFileConvertAndLoad,
2323
inputFilePath,
24+
fileType,
2425
opossumFilePath,
2526
),
2627
exportFile: (args) => ipcRenderer.invoke(IpcChannel.ExportFile, args),

src/Frontend/Components/App/App.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { useSignalsWorker } from '../../web-workers/use-signals-worker';
1515
import { AuditView } from '../AuditView/AuditView';
1616
import { ErrorFallback } from '../ErrorFallback/ErrorFallback';
1717
import { GlobalPopup } from '../GlobalPopup/GlobalPopup';
18-
import { ImportDialog } from '../ImportDialog/ImportDialog';
18+
import { ImportDialogProvider } from '../ImportDialog/ImportDialogProvider';
1919
import { ProcessPopup } from '../ProcessPopup/ProcessPopup';
2020
import { ReportView } from '../ReportView/ReportView';
2121
import { TopBar } from '../TopBar/TopBar';
@@ -40,7 +40,7 @@ export function App() {
4040
<ErrorBoundary FallbackComponent={ErrorFallback}>
4141
<GlobalPopup />
4242
<ProcessPopup />
43-
<ImportDialog />
43+
<ImportDialogProvider />
4444
<TopBar />
4545
{renderView()}
4646
</ErrorBoundary>

src/Frontend/Components/FilePathInput/FilePathInput.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//
44
// SPDX-License-Identifier: Apache-2.0
55
import { AttachFile } from '@mui/icons-material';
6+
import { TooltipProps } from '@mui/material/Tooltip';
67
import MuiTypography from '@mui/material/Typography';
78

89
import { baseIcon } from '../../shared-styles';
@@ -23,6 +24,7 @@ interface FilePathInputProps {
2324
label: string;
2425
text: string;
2526
onClick: () => void;
27+
tooltipProps?: Partial<TooltipProps>;
2628
}
2729

2830
export const FilePathInput: React.FC<FilePathInputProps> = (props) => {
@@ -34,10 +36,11 @@ export const FilePathInput: React.FC<FilePathInputProps> = (props) => {
3436
startIcon={<AttachFile sx={baseIcon} />}
3537
cursor={'pointer'}
3638
showTooltip={true}
39+
tooltipProps={props.tooltipProps}
3740
// using a custom input component allows us to disable a lot of TextField
3841
// behavior (e.g. horizontal text scrolling) that we don't want here
3942
inputComponent={CustomInput}
40-
sx={{ width: 600, marginTop: '20px' }}
43+
sx={{ marginTop: '20px' }}
4144
/>
4245
);
4346
};

src/Frontend/Components/ImportDialog/ImportDialog.tsx

+17-30
Original file line numberDiff line numberDiff line change
@@ -10,38 +10,20 @@ import { AllowedFrontendChannels } from '../../../shared/ipc-channels';
1010
import { FileFormatInfo, Log } from '../../../shared/shared-types';
1111
import { text } from '../../../shared/text';
1212
import { getDotOpossumFilePath } from '../../../shared/write-file';
13-
import {
14-
LoggingListener,
15-
ShowImportDialogListener,
16-
useIpcRenderer,
17-
} from '../../util/use-ipc-renderer';
13+
import { LoggingListener, useIpcRenderer } from '../../util/use-ipc-renderer';
1814
import { FilePathInput } from '../FilePathInput/FilePathInput';
1915
import { LogDisplay } from '../LogDisplay/LogDisplay';
2016
import { NotificationPopup } from '../NotificationPopup/NotificationPopup';
2117

22-
export const ImportDialog: React.FC = () => {
23-
const [isOpen, setIsOpen] = useState<boolean>(false);
24-
const [fileFormat, setFileFormat] = useState<FileFormatInfo>({
25-
name: '',
26-
extensions: [],
27-
});
28-
29-
function resetState() {
30-
setInputFilePath('');
31-
setOpossumFilePath('');
32-
setCurrentLog(null);
33-
}
34-
35-
useIpcRenderer<ShowImportDialogListener>(
36-
AllowedFrontendChannels.ImportFileShowDialog,
37-
(_, fileFormat) => {
38-
resetState();
39-
setFileFormat(fileFormat);
40-
setIsOpen(true);
41-
},
42-
[],
43-
);
18+
export interface ImportDialogProps {
19+
fileFormat: FileFormatInfo;
20+
closeDialog: () => void;
21+
}
4422

23+
export const ImportDialog: React.FC<ImportDialogProps> = ({
24+
fileFormat,
25+
closeDialog,
26+
}) => {
4527
const [inputFilePath, setInputFilePath] = useState<string>('');
4628
const [opossumFilePath, setOpossumFilePath] = useState<string>('');
4729

@@ -92,19 +74,20 @@ export const ImportDialog: React.FC = () => {
9274
}
9375

9476
function onCancel(): void {
95-
setIsOpen(false);
77+
closeDialog();
9678
}
9779

9880
async function onConfirm(): Promise<void> {
9981
setIsLoading(true);
10082

10183
const success = await window.electronAPI.importFileConvertAndLoad(
10284
inputFilePath,
85+
fileFormat.fileType,
10386
opossumFilePath,
10487
);
10588

10689
if (success) {
107-
setIsOpen(false);
90+
closeDialog();
10891
}
10992

11093
setIsLoading(false);
@@ -113,6 +96,9 @@ export const ImportDialog: React.FC = () => {
11396
return (
11497
<NotificationPopup
11598
header={text.importDialog.title(fileFormat)}
99+
width={'80vw'}
100+
minWidth={'300px'}
101+
maxWidth={'700px'}
116102
content={
117103
<div style={{ display: 'flex', flexDirection: 'column' }}>
118104
<MuiTypography>{text.importDialog.explanationText[0]}</MuiTypography>
@@ -126,6 +112,7 @@ export const ImportDialog: React.FC = () => {
126112
)}
127113
text={inputFilePath}
128114
onClick={selectInputFilePath}
115+
tooltipProps={{ placement: 'top' }}
129116
/>
130117
<FilePathInput
131118
label={text.importDialog.opossumFilePath.textFieldLabel(
@@ -136,7 +123,7 @@ export const ImportDialog: React.FC = () => {
136123
/>
137124
</div>
138125
}
139-
isOpen={isOpen}
126+
isOpen={true}
140127
customAction={
141128
currentLog ? (
142129
<MuiBox
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-FileCopyrightText: Meta Platforms, Inc. and its affiliates
2+
// SPDX-FileCopyrightText: TNG Technology Consulting GmbH <https://www.tngtech.com>
3+
//
4+
// SPDX-License-Identifier: Apache-2.0
5+
import { useState } from 'react';
6+
7+
import { AllowedFrontendChannels } from '../../../shared/ipc-channels';
8+
import { FileFormatInfo } from '../../../shared/shared-types';
9+
import {
10+
ShowImportDialogListener,
11+
useIpcRenderer,
12+
} from '../../util/use-ipc-renderer';
13+
import { ImportDialog } from './ImportDialog';
14+
15+
export const ImportDialogProvider: React.FC = () => {
16+
const [isOpen, setIsOpen] = useState<boolean>(false);
17+
const [fileFormat, setFileFormat] = useState<FileFormatInfo>();
18+
19+
useIpcRenderer<ShowImportDialogListener>(
20+
AllowedFrontendChannels.ImportFileShowDialog,
21+
(_, fileFormat) => {
22+
setFileFormat(fileFormat);
23+
setIsOpen(true);
24+
},
25+
[],
26+
);
27+
28+
return isOpen && fileFormat ? (
29+
<ImportDialog
30+
fileFormat={fileFormat}
31+
closeDialog={() => setIsOpen(false)}
32+
/>
33+
) : undefined;
34+
};

0 commit comments

Comments
 (0)