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

Feat: Import ScanCode Json Files #2740

Merged
merged 17 commits into from
Feb 11, 2025
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
"test:unit": "jest",
"test:changed": "jest --onlyChanged",
"test:watch": "jest --watch",
"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\"",
"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\"",
"test:e2e:ci": "yarn playwright test -c src/e2e-tests/playwright.config.ts",
"lint": "eslint --fix",
"lint-check": "eslint",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ describe('openFileFromCli', () => {

it.each`
inputFileName | extraParameter
${'inputFile.json'} | ${null}
${'inputFile.json.gz'} | ${null}
${'inputFile.json'} | ${'--dev'}
${'inputFile.opossum'} | ${null}
${'inputFile.opossum'} | ${'--dev'}
`(
'calls openFile with input file $inputFileName and extraParameter $extraParameter',
async ({ inputFileName, extraParameter }) => {
Expand Down
21 changes: 16 additions & 5 deletions src/ElectronBackend/main/listeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
ExportSpdxDocumentYamlArgs,
ExportType,
FileFormatInfo,
FileType,
OpenLinkArgs,
PackageInfo,
SaveFileArgs,
Expand All @@ -31,6 +32,7 @@ import {
} from '../errorHandling/errorHandling';
import { loadInputAndOutputFromFilePath } from '../input/importFromFile';
import { serializeAttributions } from '../input/parseInputData';
import { convertScancodeToOpossum } from '../opossum-file/convertScancodeToOpossum';
import { writeCsvToFile } from '../output/writeCsvToFile';
import { writeSpdxFile } from '../output/writeSpdxFile';
import { GlobalBackendState, OpossumOutputFile } from '../types/types';
Expand Down Expand Up @@ -180,13 +182,15 @@ export function getImportFileConvertAndLoadListener(
): (
_: Electron.IpcMainInvokeEvent,
resourceFilePath: string,
fileType: FileType,
opossumFilePath: string,
) => Promise<boolean | null> {
return createListenerCallbackWithErrorHandling(
mainWindow,
async (
_: Electron.IpcMainInvokeEvent,
resourceFilePath: string,
fileType: FileType,
opossumFilePath: string,
) => {
if (!resourceFilePath.trim() || !fs.existsSync(resourceFilePath)) {
Expand Down Expand Up @@ -215,11 +219,18 @@ export function getImportFileConvertAndLoadListener(
resourceFilePath = tryToGetInputFileFromOutputFile(resourceFilePath);
}

await writeOpossumFile({
path: opossumFilePath,
input: getInputJson(resourceFilePath),
output: getOutputJson(resourceFilePath),
});
switch (fileType) {
case FileType.LEGACY_OPOSSUM:
await writeOpossumFile({
path: opossumFilePath,
input: getInputJson(resourceFilePath),
output: getOutputJson(resourceFilePath),
});
break;
case FileType.SCANCODE_JSON:
await convertScancodeToOpossum(resourceFilePath, opossumFilePath);
break;
}

logger.info('Updating global backend state');
initializeGlobalBackendState(opossumFilePath, true);
Expand Down
12 changes: 11 additions & 1 deletion src/ElectronBackend/main/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
import { app, BrowserWindow, Menu, shell } from 'electron';

import { AllowedFrontendChannels } from '../../shared/ipc-channels';
import { ExportType, FileFormatInfo } from '../../shared/shared-types';
import {
ExportType,
FileFormatInfo,
FileType,
} from '../../shared/shared-types';
import { isFileLoaded } from '../utils/getLoadedFile';
import { getGlobalBackendState } from './globalBackendState';
import {
Expand Down Expand Up @@ -83,9 +87,15 @@ const INITIALLY_DISABLED_ITEMS_INFO: Record<

export const importFileFormats: Array<FileFormatInfo> = [
{
fileType: FileType.LEGACY_OPOSSUM,
name: 'Legacy Opossum File',
extensions: ['json', 'json.gz'],
},
{
fileType: FileType.SCANCODE_JSON,
name: 'ScanCode File',
extensions: ['json'],
},
];

export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
import { BrowserWindow } from 'electron';
import log from 'electron-log';

import { convertScancodeToOpossum } from '../opossum-file/convertScancodeToOpossum';
import { getFilePathWithAppendix } from '../utils/getFilePathWithAppendix';
import { handleOpeningFile } from './listeners';
import { activateMenuItems } from './menu';

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

function fileHasValidEnding(arg: string): boolean {
return (
arg.endsWith('.json') ||
arg.endsWith('.json.gz') ||
arg.endsWith('.opossum')
);
return arg.endsWith('.opossum');
}

for (const arg of process.argv) {
Expand All @@ -42,15 +36,6 @@ export async function openFileFromCliOrEnvVariableIfProvided(
}
}

const inputScanCodeFileFromEnvVariable: string | undefined =
process.env.SCANCODE_JSON;
if (!inputFileName && inputScanCodeFileFromEnvVariable) {
inputFileName = await convertScancodeToOpossum(
inputScanCodeFileFromEnvVariable,
getFilePathWithAppendix(inputScanCodeFileFromEnvVariable, '.opossum'),
);
}

if (inputFileName) {
await handleOpeningFile(mainWindow, inputFileName, activateMenuItems);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,16 @@ import { parseOpossumFile } from '../../input/parseFile';
import { isOpossumFileFormat } from '../../utils/isOpossumFileFormat';
import { convertScancodeToOpossum } from '../convertScancodeToOpossum';

function getTempPath(): string {
return join(tmpdir(), uniqueId('opossum_'));
}

describe('successfulConversionOfScanCodeFile', () => {
const SCANCODE_TEST_FILE = join(__dirname, 'scancode.json');

it('should convert the ScanCode file and return a path to a valid .opossum file', async () => {
const path = await convertScancodeToOpossum(
SCANCODE_TEST_FILE,
getTempPath(),
);
expect(existsSync(path)).toBe(true);
expect(isOpossumFileFormat(path)).toBe(true);
const opossumPath = join(tmpdir(), `${uniqueId('opossum_')}.opossum`);
await convertScancodeToOpossum(SCANCODE_TEST_FILE, opossumPath);
expect(existsSync(opossumPath)).toBe(true);
expect(isOpossumFileFormat(opossumPath)).toBe(true);

const parsingResult = await parseOpossumFile(path);
const parsingResult = await parseOpossumFile(opossumPath);
expect(parsingResult).toHaveProperty('input');
});
});
10 changes: 3 additions & 7 deletions src/ElectronBackend/opossum-file/convertScancodeToOpossum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,23 @@ import { app } from 'electron';
import { join } from 'path';
import { promisify } from 'util';

import { getFilePathWithAppendix } from '../utils/getFilePathWithAppendix';

const execFile = promisify(execFileCallback);

const OPOSSUM_FILE_EXECUTABLE = join(
app?.getAppPath?.() ?? './',
process.env.NODE_ENV === 'e2e' ? '../..' : '',
'bin/opossum-file',
);

export async function convertScancodeToOpossum(
pathToScanCode: string,
pathToOutput: string,
): Promise<string> {
const pathToOpossum = getFilePathWithAppendix(pathToOutput, '.opossum');

pathToOpossum: string,
): Promise<void> {
await execFile(OPOSSUM_FILE_EXECUTABLE, [
'generate',
'-o',
pathToOpossum,
'--scan-code-json',
pathToScanCode,
]);
return pathToOpossum;
}
3 changes: 2 additions & 1 deletion src/ElectronBackend/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ const electronAPI: ElectronAPI = {
ipcRenderer.invoke(IpcChannel.ImportFileSelectInput, fileFormat),
importFileSelectSaveLocation: (defaultPath) =>
ipcRenderer.invoke(IpcChannel.ImportFileSelectSaveLocation, defaultPath),
importFileConvertAndLoad: (inputFilePath, opossumFilePath) =>
importFileConvertAndLoad: (inputFilePath, fileType, opossumFilePath) =>
ipcRenderer.invoke(
IpcChannel.ImportFileConvertAndLoad,
inputFilePath,
fileType,
opossumFilePath,
),
exportFile: (args) => ipcRenderer.invoke(IpcChannel.ExportFile, args),
Expand Down
4 changes: 2 additions & 2 deletions src/Frontend/Components/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useSignalsWorker } from '../../web-workers/use-signals-worker';
import { AuditView } from '../AuditView/AuditView';
import { ErrorFallback } from '../ErrorFallback/ErrorFallback';
import { GlobalPopup } from '../GlobalPopup/GlobalPopup';
import { ImportDialog } from '../ImportDialog/ImportDialog';
import { ImportDialogProvider } from '../ImportDialog/ImportDialogProvider';
import { ProcessPopup } from '../ProcessPopup/ProcessPopup';
import { ReportView } from '../ReportView/ReportView';
import { TopBar } from '../TopBar/TopBar';
Expand All @@ -40,7 +40,7 @@ export function App() {
<ErrorBoundary FallbackComponent={ErrorFallback}>
<GlobalPopup />
<ProcessPopup />
<ImportDialog />
<ImportDialogProvider />
<TopBar />
{renderView()}
</ErrorBoundary>
Expand Down
5 changes: 4 additions & 1 deletion src/Frontend/Components/FilePathInput/FilePathInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//
// SPDX-License-Identifier: Apache-2.0
import { AttachFile } from '@mui/icons-material';
import { TooltipProps } from '@mui/material/Tooltip';
import MuiTypography from '@mui/material/Typography';

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

export const FilePathInput: React.FC<FilePathInputProps> = (props) => {
Expand All @@ -34,10 +36,11 @@ export const FilePathInput: React.FC<FilePathInputProps> = (props) => {
startIcon={<AttachFile sx={baseIcon} />}
cursor={'pointer'}
showTooltip={true}
tooltipProps={props.tooltipProps}
// using a custom input component allows us to disable a lot of TextField
// behavior (e.g. horizontal text scrolling) that we don't want here
inputComponent={CustomInput}
sx={{ width: 600, marginTop: '20px' }}
sx={{ marginTop: '20px' }}
/>
);
};
47 changes: 17 additions & 30 deletions src/Frontend/Components/ImportDialog/ImportDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,20 @@ import { AllowedFrontendChannels } from '../../../shared/ipc-channels';
import { FileFormatInfo, Log } from '../../../shared/shared-types';
import { text } from '../../../shared/text';
import { getDotOpossumFilePath } from '../../../shared/write-file';
import {
LoggingListener,
ShowImportDialogListener,
useIpcRenderer,
} from '../../util/use-ipc-renderer';
import { LoggingListener, useIpcRenderer } from '../../util/use-ipc-renderer';
import { FilePathInput } from '../FilePathInput/FilePathInput';
import { LogDisplay } from '../LogDisplay/LogDisplay';
import { NotificationPopup } from '../NotificationPopup/NotificationPopup';

export const ImportDialog: React.FC = () => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const [fileFormat, setFileFormat] = useState<FileFormatInfo>({
name: '',
extensions: [],
});

function resetState() {
setInputFilePath('');
setOpossumFilePath('');
setCurrentLog(null);
}

useIpcRenderer<ShowImportDialogListener>(
AllowedFrontendChannels.ImportFileShowDialog,
(_, fileFormat) => {
resetState();
setFileFormat(fileFormat);
setIsOpen(true);
},
[],
);
export interface ImportDialogProps {
fileFormat: FileFormatInfo;
closeDialog: () => void;
}

export const ImportDialog: React.FC<ImportDialogProps> = ({
fileFormat,
closeDialog,
}) => {
const [inputFilePath, setInputFilePath] = useState<string>('');
const [opossumFilePath, setOpossumFilePath] = useState<string>('');

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

function onCancel(): void {
setIsOpen(false);
closeDialog();
}

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

const success = await window.electronAPI.importFileConvertAndLoad(
inputFilePath,
fileFormat.fileType,
opossumFilePath,
);

if (success) {
setIsOpen(false);
closeDialog();
}

setIsLoading(false);
Expand All @@ -113,6 +96,9 @@ export const ImportDialog: React.FC = () => {
return (
<NotificationPopup
header={text.importDialog.title(fileFormat)}
width={'80vw'}
minWidth={'300px'}
maxWidth={'700px'}
content={
<div style={{ display: 'flex', flexDirection: 'column' }}>
<MuiTypography>{text.importDialog.explanationText[0]}</MuiTypography>
Expand All @@ -126,6 +112,7 @@ export const ImportDialog: React.FC = () => {
)}
text={inputFilePath}
onClick={selectInputFilePath}
tooltipProps={{ placement: 'top' }}
/>
<FilePathInput
label={text.importDialog.opossumFilePath.textFieldLabel(
Expand All @@ -136,7 +123,7 @@ export const ImportDialog: React.FC = () => {
/>
</div>
}
isOpen={isOpen}
isOpen={true}
customAction={
currentLog ? (
<MuiBox
Expand Down
34 changes: 34 additions & 0 deletions src/Frontend/Components/ImportDialog/ImportDialogProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: Meta Platforms, Inc. and its affiliates
// SPDX-FileCopyrightText: TNG Technology Consulting GmbH <https://www.tngtech.com>
//
// SPDX-License-Identifier: Apache-2.0
import { useState } from 'react';

import { AllowedFrontendChannels } from '../../../shared/ipc-channels';
import { FileFormatInfo } from '../../../shared/shared-types';
import {
ShowImportDialogListener,
useIpcRenderer,
} from '../../util/use-ipc-renderer';
import { ImportDialog } from './ImportDialog';

export const ImportDialogProvider: React.FC = () => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const [fileFormat, setFileFormat] = useState<FileFormatInfo>();

useIpcRenderer<ShowImportDialogListener>(
AllowedFrontendChannels.ImportFileShowDialog,
(_, fileFormat) => {
setFileFormat(fileFormat);
setIsOpen(true);
},
[],
);

return isOpen && fileFormat ? (
<ImportDialog
fileFormat={fileFormat}
closeDialog={() => setIsOpen(false)}
/>
) : undefined;
};
Loading
Loading