From 9281b5298b5ebafa257e5fc9dc68afb3c7350d00 Mon Sep 17 00:00:00 2001 From: Omkar Phansopkar Date: Sat, 2 Sep 2023 00:12:48 +0530 Subject: [PATCH 1/2] Implemented close file button, menu item and before new scan import Signed-off-by: Omkar Phansopkar --- src/components/FileTree/FileTree.tsx | 5 ++- src/constants/IpcConnection.ts | 37 +++++++++++---------- src/contexts/dbContext.tsx | 20 +++++++++++ src/mainActions.ts | 4 +++ src/mainMenu.ts | 13 +++++++- src/pages/Home/Home.tsx | 21 +++++++++++- src/pages/Home/home.css | 13 ++++++-- src/pages/ScanInfo/ScanInfo.tsx | 2 ++ src/services/importedJsonTypes.ts | 1 + src/services/models/header.ts | 2 ++ src/services/workbenchDB.ts | 33 ++++++++++++------ src/utils/parsers.ts | 2 ++ tests/test-scans/headers/expectedHeaders.ts | 3 ++ 13 files changed, 122 insertions(+), 34 deletions(-) diff --git a/src/components/FileTree/FileTree.tsx b/src/components/FileTree/FileTree.tsx index 3d60fa22..c3ed124d 100644 --- a/src/components/FileTree/FileTree.tsx +++ b/src/components/FileTree/FileTree.tsx @@ -41,7 +41,10 @@ const FileTree = (props: React.HTMLProps) => { }, [currentPath]); useEffect(() => { - if (!initialized || !db || !importedSqliteFilePath) return; + if (!initialized || !db || !importedSqliteFilePath) { + setTreeData(null); + return; + } db.findAllJSTree().then((treeData) => { // Wrap with react-scroll wrapper diff --git a/src/constants/IpcConnection.ts b/src/constants/IpcConnection.ts index c00b90ab..aed7ad02 100644 --- a/src/constants/IpcConnection.ts +++ b/src/constants/IpcConnection.ts @@ -5,33 +5,34 @@ export interface ErrorInfo { } export const OPEN_DIALOG_CHANNEL = { - JSON: 'open-json-file', - SQLITE_PATH_FOR_JSON: 'choose-sqlite-path-for-json-file', - SQLITE: 'open-sqlite-file', - SAVE_SQLITE: 'save-sqlite-file', -} + JSON: "open-json-file", + SQLITE_PATH_FOR_JSON: "choose-sqlite-path-for-json-file", + SQLITE: "open-sqlite-file", + SAVE_SQLITE: "save-sqlite-file", +}; export const IMPORT_REPLY_CHANNEL = { - JSON: 'import-json-reply', - SQLITE: 'import-sqlite-reply', -} + JSON: "import-json-reply", + SQLITE: "import-sqlite-reply", +}; export const SAVE_REPLY_CHANNEL = { - SQLITE: 'save-sqlite-reply', -} + SQLITE: "save-sqlite-reply", +}; export const UTIL_CHANNEL = { - SET_CURRENT_FILE_TITLE: 'set-current-file-title', -} + SET_CURRENT_FILE_TITLE: "set-current-file-title", + CLOSE_FILE: "close-file", +}; -export const NAVIGATION_CHANNEL = 'NAVIGATE_TO'; +export const NAVIGATION_CHANNEL = "NAVIGATE_TO"; export type NAVIGATION_CHANNEL_MESSAGE = string; export const GENERAL_ACTIONS = { - ZOOM_IN: 'zoom_in', - ZOOM_OUT: 'zoom_out', - ZOOM_RESET: 'zoom_reset', -} + ZOOM_IN: "zoom_in", + ZOOM_OUT: "zoom_out", + ZOOM_RESET: "zoom_reset", +}; export interface SQLITE_PATH_FOR_JSON_REQUEST_FORMAT { jsonFilePath: string; @@ -45,4 +46,4 @@ export interface SQLITE_IMPORT_REPLY_FORMAT { } export interface SQLITE_SAVE_REPLY_FORMAT { sqliteFilePath: string; -} \ No newline at end of file +} diff --git a/src/contexts/dbContext.tsx b/src/contexts/dbContext.tsx index 24c0db94..a5a94b55 100644 --- a/src/contexts/dbContext.tsx +++ b/src/contexts/dbContext.tsx @@ -42,6 +42,7 @@ interface WorkbenchContextProperties extends BasicValueState { processingQuery: boolean; startImport: () => void; abortImport: () => void; + closeFile: () => void, startProcessing: () => void; endProcessing: () => void; sqliteParser: (sqliteFilePath: string, preventNavigation?: boolean) => void; @@ -71,6 +72,7 @@ export const defaultWorkbenchContextValue: WorkbenchContextProperties = { updateLoadingStatus: () => null, startImport: () => null, abortImport: () => null, + closeFile: () => null, startProcessing: () => null, endProcessing: () => null, updateCurrentPath: () => null, @@ -113,6 +115,7 @@ export const WorkbenchDBProvider = ( const startImport = () => { updateLoadingStatus(0); + setProcessingQuery(false); setValue({ db: null, initialized: false, @@ -123,6 +126,18 @@ export const WorkbenchDBProvider = ( const abortImport = () => updateLoadingStatus(null); + const closeFile = () => { + updateLoadingStatus(null); + setProcessingQuery(false); + setValue({ + db: null, + initialized: false, + importedSqliteFilePath: null, + scanInfo: null, + }); + navigate(ROUTES.HOME) + } + const updateWorkbenchDB = async (db: WorkbenchDB, sqliteFilePath: string) => { updateLoadingStatus(100); setValue({ @@ -440,6 +455,10 @@ export const WorkbenchDBProvider = ( } } ); + ipcRenderer.on( + UTIL_CHANNEL.CLOSE_FILE, + closeFile + ) // Remove all listeners on window unmount return () => { @@ -461,6 +480,7 @@ export const WorkbenchDBProvider = ( importJsonFile, startImport, abortImport, + closeFile, startProcessing: () => setProcessingQuery(true), endProcessing: () => setProcessingQuery(false), updateCurrentPath, diff --git a/src/mainActions.ts b/src/mainActions.ts index cae7fd60..8986768f 100644 --- a/src/mainActions.ts +++ b/src/mainActions.ts @@ -113,6 +113,10 @@ export function saveSqliteFile(mainWindow: BrowserWindow) { }); } +export function closeFile(mainWindow: BrowserWindow) { + mainWindow.webContents.send(UTIL_CHANNEL.CLOSE_FILE); +} + export function showErrorDialog(err: ErrorInfo) { console.log("Showing error to user:", err); dialog.showErrorBox(err.title, err.message); diff --git a/src/mainMenu.ts b/src/mainMenu.ts index 57940004..c6ad2127 100644 --- a/src/mainMenu.ts +++ b/src/mainMenu.ts @@ -1,6 +1,11 @@ import { GENERAL_ACTIONS, NAVIGATION_CHANNEL } from "./constants/IpcConnection"; import { app, BrowserWindow, MenuItem, shell } from "electron"; -import { importJsonFile, openSqliteFile, saveSqliteFile } from "./mainActions"; +import { + closeFile, + importJsonFile, + openSqliteFile, + saveSqliteFile, +} from "./mainActions"; import { ROUTES } from "./constants/routes"; import { createWindow } from "./main"; import { WORKBENCH_VERSION } from "./constants/general"; @@ -46,6 +51,12 @@ function getTemplate() { click: (_: MenuItem, currentWindow: BrowserWindow) => importJsonFile(currentWindow), }, + { + label: "Close File", + // accelerator: "CmdOrCtrl+", + click: (_: MenuItem, currentWindow: BrowserWindow) => + closeFile(currentWindow), + }, // @TODO-discuss This is duplicated in App's menu tab, is it necessary under file tab also ?? // ...( // isMac ? [ diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index 030de6fc..ce61752b 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -3,7 +3,9 @@ import electron from "electron"; import * as electronFs from "fs"; import { toast } from "react-toastify"; import React, { useMemo, useState } from "react"; +import { Button } from "react-bootstrap"; import { + faClose, faCogs, faDatabase, faFileCode, @@ -32,9 +34,11 @@ const Home = () => { db, loadingStatus, initialized, + scanInfo, + importedSqliteFilePath, jsonParser, sqliteParser, - importedSqliteFilePath, + closeFile, } = useWorkbenchDB(); const [historyRefreshToken, setRefreshToken] = useState(0); @@ -114,6 +118,21 @@ const Home = () => { OR Simply drop your json / sqlite file anywhere in the app !! + {db && initialized && ( +
+
+ Imported scan - {scanInfo.json_file_name} + +
+
+ )}

Recent files
diff --git a/src/pages/Home/home.css b/src/pages/Home/home.css index f85aabdd..313f668c 100644 --- a/src/pages/Home/home.css +++ b/src/pages/Home/home.css @@ -41,16 +41,25 @@ .drop-instruction { text-align: center; width: 100%; - margin-top: 50px; + margin-top: 25px; color: var(--lightTextColor); } .quicklinks, +.currentscan, .history { - margin-top: 15px; padding-left: 2rem; } +.currentscan { + margin-top: 35px; + margin-bottom: 5px; +} + +.quicklinks { + margin-top: 10px; +} + .history table { min-width: 80%; max-width: 95%; diff --git a/src/pages/ScanInfo/ScanInfo.tsx b/src/pages/ScanInfo/ScanInfo.tsx index d7b00717..48c07ccd 100644 --- a/src/pages/ScanInfo/ScanInfo.tsx +++ b/src/pages/ScanInfo/ScanInfo.tsx @@ -19,6 +19,8 @@ const ScanInfo = () => { {scanInfo ? ( + {scanInfo.json_file_name} + {scanInfo.tool_name}{scanInfo.tool_version} diff --git a/src/services/importedJsonTypes.ts b/src/services/importedJsonTypes.ts index 758cbb7d..e6149d53 100644 --- a/src/services/importedJsonTypes.ts +++ b/src/services/importedJsonTypes.ts @@ -1,6 +1,7 @@ import { JSON_Type } from "./models/databaseUtils"; export interface ParsedJsonHeader { + json_file_name: string; tool_name: string; tool_version: string; notice: string; diff --git a/src/services/models/header.ts b/src/services/models/header.ts index 4d5ffd03..f041d9e4 100644 --- a/src/services/models/header.ts +++ b/src/services/models/header.ts @@ -19,6 +19,7 @@ import { JSON_Type, jsonDataType } from "./databaseUtils"; export interface HeaderAttributes { id: number; + json_file_name: string; tool_name: string; tool_version: string; notice: string; @@ -52,6 +53,7 @@ export default function headerModel(sequelize: Sequelize) { primaryKey: true, type: DataTypes.INTEGER, }, + json_file_name: DataTypes.STRING, tool_name: DataTypes.STRING, tool_version: DataTypes.STRING, notice: DataTypes.STRING, diff --git a/src/services/workbenchDB.ts b/src/services/workbenchDB.ts index 7377f1ab..d94223eb 100644 --- a/src/services/workbenchDB.ts +++ b/src/services/workbenchDB.ts @@ -61,7 +61,7 @@ const { version: workbenchVersion } = packageJson; * * The config will load an existing database or will create a new, empty * database if none exists. For a new database, the data is loaded from a JSON - * file by calling addFromJson(jsonFileName). + * file by calling addFromJson(jsonFilePath). * * @param config * @param config.dbName @@ -271,16 +271,16 @@ export class WorkbenchDB { // Add rows to the flattened files table from a ScanCode json object addFromJson( - jsonFileName: string, + jsonFilePath: string, onProgressUpdate: (progress: number) => void ): Promise { - if (!jsonFileName) { - throw new Error("Invalid json file name: " + jsonFileName); + if (!jsonFilePath) { + throw new Error("Invalid json file name: " + jsonFilePath); } - // console.log("Adding from json with params", { jsonFileName, workbenchVersion, onProgressUpdate }); + // console.log("Adding from json with params", { jsonFilePath, workbenchVersion, onProgressUpdate }); - const stream = fs.createReadStream(jsonFileName, { encoding: "utf8" }); + const stream = fs.createReadStream(jsonFilePath, { encoding: "utf8" }); let files_count = 0; let dirs_count = 0; let index = 0; @@ -303,7 +303,7 @@ export class WorkbenchDB { let batchCount = 0; let TopLevelData: TopLevelDataFormat = { header: null, - parsedHeader: this._parseHeader(workbenchVersion, {}), + parsedHeader: this._parseHeader(jsonFilePath, workbenchVersion, {}), packages: [], dependencies: [], license_clues: [], @@ -318,7 +318,10 @@ export class WorkbenchDB { stream .pipe(JSONStream.parse("files.*")) // files field is piped to 'data' & rest to 'header' (Includes other top level fields) .on("header", (rawTopLevelData: any) => { - TopLevelData = this._parseTopLevelFields(rawTopLevelData); + TopLevelData = this._parseTopLevelFields( + jsonFilePath, + rawTopLevelData + ); files_count = Number(TopLevelData.parsedHeader.files_count); promiseChain = promiseChain @@ -443,11 +446,18 @@ export class WorkbenchDB { } // Helper function for parsing Toplevel data - _parseTopLevelFields(rawTopLevelData: any): TopLevelDataFormat { + _parseTopLevelFields( + jsonFilePath: string, + rawTopLevelData: any + ): TopLevelDataFormat { const header = rawTopLevelData.headers ? rawTopLevelData.headers[0] || {} : {}; - const parsedHeader = this._parseHeader(workbenchVersion, header); + const parsedHeader = this._parseHeader( + jsonFilePath, + workbenchVersion, + header + ); const packages = rawTopLevelData.packages || []; const dependencies = rawTopLevelData.dependencies || []; const license_detections: TopLevelLicenseDetection[] = ( @@ -493,10 +503,11 @@ export class WorkbenchDB { }; } - _parseHeader(workbenchVersion: string, header: any) { + _parseHeader(jsonFilePath: string, workbenchVersion: string, header: any) { const input = header.options?.input || []; delete header.options?.input; const parsedHeader: ParsedJsonHeader = { + json_file_name: path.basename(jsonFilePath), tool_name: header.tool_name, tool_version: header.tool_version, notice: header.notice, diff --git a/src/utils/parsers.ts b/src/utils/parsers.ts index 5122a721..ef8733f9 100644 --- a/src/utils/parsers.ts +++ b/src/utils/parsers.ts @@ -33,6 +33,7 @@ export enum ScanOptionKeys { } export interface ScanInfo { + json_file_name: string; tool_name: string; tool_version: string; notice: string; @@ -65,6 +66,7 @@ export function parseScanInfo( ); const parsedScanInfo: ScanInfo = { + json_file_name: rawInfo.getDataValue("json_file_name") || "Not available", tool_name: rawInfo.getDataValue("tool_name") || "", tool_version: rawInfo.getDataValue("tool_version") || "", notice: rawInfo.getDataValue("notice") || "", diff --git a/tests/test-scans/headers/expectedHeaders.ts b/tests/test-scans/headers/expectedHeaders.ts index cb9d61fb..23a524a3 100644 --- a/tests/test-scans/headers/expectedHeaders.ts +++ b/tests/test-scans/headers/expectedHeaders.ts @@ -8,6 +8,7 @@ export const HeaderSamples: { { jsonFileName: "headerless.json", expectedHeaders: { + json_file_name: "headerless.json", tool_name: "", tool_version: "", notice: "", @@ -33,6 +34,7 @@ export const HeaderSamples: { { jsonFileName: "minimal.json", expectedHeaders: { + json_file_name: "minimal.json", tool_name: "scancode-toolkit", tool_version: "32.0.0rc3", notice: @@ -60,6 +62,7 @@ export const HeaderSamples: { { jsonFileName: "withOptions.json", expectedHeaders: { + json_file_name: "withOptions.json", tool_name: "scancode-toolkit", tool_version: "32.0.0rc3", notice: From f39c86a2fd56543d7ee0cbd1c365be2df842884e Mon Sep 17 00:00:00 2001 From: Omkar Phansopkar Date: Tue, 12 Sep 2023 00:57:08 +0530 Subject: [PATCH 2/2] Fixed previous title preserved on closing file Signed-off-by: Omkar Phansopkar --- src/constants/IpcConnection.ts | 1 + src/constants/general.ts | 2 ++ src/contexts/dbContext.tsx | 12 +++++------- src/main.ts | 3 ++- src/mainActions.ts | 6 +++++- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/constants/IpcConnection.ts b/src/constants/IpcConnection.ts index aed7ad02..21309b48 100644 --- a/src/constants/IpcConnection.ts +++ b/src/constants/IpcConnection.ts @@ -22,6 +22,7 @@ export const SAVE_REPLY_CHANNEL = { export const UTIL_CHANNEL = { SET_CURRENT_FILE_TITLE: "set-current-file-title", + RESET_FILE_TITLE: "reset-current-file-title", CLOSE_FILE: "close-file", }; diff --git a/src/constants/general.ts b/src/constants/general.ts index 3f9471ae..7594aa46 100644 --- a/src/constants/general.ts +++ b/src/constants/general.ts @@ -1,3 +1,5 @@ import packageJson from "../../package.json"; +export const WORKBENCH_TITLE = "Scancode workbench"; + export const WORKBENCH_VERSION = packageJson.version; \ No newline at end of file diff --git a/src/contexts/dbContext.tsx b/src/contexts/dbContext.tsx index a5a94b55..935f58d4 100644 --- a/src/contexts/dbContext.tsx +++ b/src/contexts/dbContext.tsx @@ -42,7 +42,7 @@ interface WorkbenchContextProperties extends BasicValueState { processingQuery: boolean; startImport: () => void; abortImport: () => void; - closeFile: () => void, + closeFile: () => void; startProcessing: () => void; endProcessing: () => void; sqliteParser: (sqliteFilePath: string, preventNavigation?: boolean) => void; @@ -135,8 +135,9 @@ export const WorkbenchDBProvider = ( importedSqliteFilePath: null, scanInfo: null, }); - navigate(ROUTES.HOME) - } + navigate(ROUTES.HOME); + ipcRenderer.send(UTIL_CHANNEL.RESET_FILE_TITLE); + }; const updateWorkbenchDB = async (db: WorkbenchDB, sqliteFilePath: string) => { updateLoadingStatus(100); @@ -455,10 +456,7 @@ export const WorkbenchDBProvider = ( } } ); - ipcRenderer.on( - UTIL_CHANNEL.CLOSE_FILE, - closeFile - ) + ipcRenderer.on(UTIL_CHANNEL.CLOSE_FILE, closeFile); // Remove all listeners on window unmount return () => { diff --git a/src/main.ts b/src/main.ts index 6725eb2e..73c03d05 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,6 +10,7 @@ import { import getTemplate from "./mainMenu"; import { setUpGlobalIpcListeners, setUpWindowListeners } from "./mainActions"; +import { WORKBENCH_TITLE } from "./constants/general"; // This allows TypeScript to pick up the magic constant that's auto-generated by Forge's Webpack // plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on @@ -50,7 +51,7 @@ export const createWindow = (): void => { // Create the browser window. const mainWindow = new BrowserWindow({ - title: "Scancode workbench", + title: WORKBENCH_TITLE, width: 1200, height: 800, x, diff --git a/src/mainActions.ts b/src/mainActions.ts index 8986768f..75ff4a2c 100644 --- a/src/mainActions.ts +++ b/src/mainActions.ts @@ -13,6 +13,7 @@ import { UTIL_CHANNEL, } from "./constants/IpcConnection"; import { figureOutDefaultSqliteFilePath } from "./utils/paths"; +import { WORKBENCH_TITLE } from "./constants/general"; export function chooseSqlitePathForJsonImport( mainWindow: BrowserWindow, @@ -127,7 +128,7 @@ export function setCurrentFileTitle(mainWindow: BrowserWindow, title: string) { console.log("Main window not found:", title, mainWindow); return; } - const titleString = "Scancode Workbench" + (title ? ` - ${title}` : ""); + const titleString = WORKBENCH_TITLE + (title ? ` - ${title}` : ""); mainWindow.setTitle(titleString); } @@ -152,6 +153,9 @@ export function setUpGlobalIpcListeners() { ipcMain.on(UTIL_CHANNEL.SET_CURRENT_FILE_TITLE, (e, title: string) => setCurrentFileTitle(getSenderWindow(e), title) ); + ipcMain.on(UTIL_CHANNEL.RESET_FILE_TITLE, (e) => + setCurrentFileTitle(getSenderWindow(e), "") + ); ipcMain.on(OPEN_ERROR_DIALOG_CHANNEL, (_, err: ErrorInfo) => showErrorDialog(err) );