From 2d3ddd8d744cc1ab48326627866367c78c0833cf Mon Sep 17 00:00:00 2001 From: Abdelrahman Shawki Hassan Date: Mon, 14 Oct 2024 13:18:15 +0200 Subject: [PATCH] feat: clear cache command (#542) * feat: clear cache command * chore: lint * fix: use types for Git extension * chore: update CHANGELOG --- CHANGELOG.md | 2 ++ package.json | 5 +++ src/snyk/base/modules/baseSnykModule.ts | 2 ++ src/snyk/common/constants/commands.ts | 3 +- src/snyk/common/constants/general.ts | 2 ++ src/snyk/common/git.ts | 23 +++++++++++++ src/snyk/common/services/CacheService.ts | 20 +++++++++++ src/snyk/extension.ts | 42 ++++++++++++++++++++++++ 8 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/snyk/common/git.ts create mode 100644 src/snyk/common/services/CacheService.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d900d016d..aa3ade33e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## [2.19.0] - Moved delta scan preview setting to settings page. - New error message in UI when net new scan is done on an invalid repository. Net new scans only work on Git. +- Clear in Memory cache when branch is changed. +- Added Clear Persisted Cache command. ## [2.18.2] - Update Language Server Protocol version to 15. diff --git a/package.json b/package.json index a3e4fbc89..ddb171b16 100644 --- a/package.json +++ b/package.json @@ -476,6 +476,11 @@ "command": "snyk.showLsOutputChannel", "title": "Show Language Server Output Channel", "category": "Snyk" + }, + { + "command": "snyk.clearPersistedCache", + "title": "Clear Persisted Cache", + "category": "Snyk" } ] }, diff --git a/src/snyk/base/modules/baseSnykModule.ts b/src/snyk/base/modules/baseSnykModule.ts index 5570aced7..3ddf7d2ee 100644 --- a/src/snyk/base/modules/baseSnykModule.ts +++ b/src/snyk/base/modules/baseSnykModule.ts @@ -4,6 +4,7 @@ import { IWorkspaceTrust, WorkspaceTrust } from '../../common/configuration/trus import { ExperimentService } from '../../common/experiment/services/experimentService'; import { ILanguageServer } from '../../common/languageServer/languageServer'; import { CodeIssueData, IacIssueData } from '../../common/languageServer/types'; +import { IClearCacheService } from '../../common/services/CacheService'; import { ContextService, IContextService } from '../../common/services/contextService'; import { DownloadService } from '../../common/services/downloadService'; import { FeatureFlagService } from '../../common/services/featureFlagService'; @@ -36,6 +37,7 @@ export default abstract class BaseSnykModule implements IBaseSnykModule { protected configurationWatcher: IWatcher; readonly contextService: IContextService; + cacheService: IClearCacheService; readonly openerService: IOpenerService; readonly viewManagerService: IViewManagerService; protected authService: IAuthenticationService; diff --git a/src/snyk/common/constants/commands.ts b/src/snyk/common/constants/commands.ts index 527296e6c..73275290a 100644 --- a/src/snyk/common/constants/commands.ts +++ b/src/snyk/common/constants/commands.ts @@ -20,13 +20,14 @@ export const SNYK_SHOW_ERROR_FROM_CONTEXT_COMMAND = 'snyk.showErrorFromContext'; export const SNYK_GET_LESSON_COMMAND = 'snyk.getLearnLesson'; export const SNYK_GET_SETTINGS_SAST_ENABLED = 'snyk.getSettingsSastEnabled'; export const SNYK_SET_BASE_BRANCH_COMMAND = 'snyk.setBaseBranch'; -// commands export const SNYK_LOGIN_COMMAND = 'snyk.login'; export const SNYK_WORKSPACE_SCAN_COMMAND = 'snyk.workspace.scan'; export const SNYK_TRUST_WORKSPACE_FOLDERS_COMMAND = 'snyk.trustWorkspaceFolders'; export const SNYK_GET_ACTIVE_USER = 'snyk.getActiveUser'; export const SNYK_CODE_FIX_DIFFS_COMMAND = 'snyk.code.fixDiffs'; export const SNYK_FEATURE_FLAG_COMMAND = 'snyk.getFeatureFlagStatus'; +export const SNYK_CLEAR_CACHE_COMMAND = 'snyk.clearCache'; +export const SNYK_CLEAR_PERSISTED_CACHE_COMMAND = 'snyk.clearPersistedCache'; // custom Snyk constants used in commands export const SNYK_CONTEXT_PREFIX = 'snyk:'; diff --git a/src/snyk/common/constants/general.ts b/src/snyk/common/constants/general.ts index f2229f563..8a1b06cf8 100644 --- a/src/snyk/common/constants/general.ts +++ b/src/snyk/common/constants/general.ts @@ -9,3 +9,5 @@ export const COMMAND_DEBOUNCE_INTERVAL = 200; // 200 milliseconds export const DEFAULT_SCAN_DEBOUNCE_INTERVAL = 1000; // 1 second export const DEFAULT_LS_DEBOUNCE_INTERVAL = 1000; // 1 second export const REFRESH_VIEW_DEBOUNCE_INTERVAL = 200; // 200 milliseconds +export const InMemory = 'inMemory'; +export const Persisted = 'persisted'; diff --git a/src/snyk/common/git.ts b/src/snyk/common/git.ts new file mode 100644 index 000000000..b1c847979 --- /dev/null +++ b/src/snyk/common/git.ts @@ -0,0 +1,23 @@ +import * as vscode from 'vscode'; + +export interface GitExtension { + getAPI(version: number): GitAPI; +} + +export interface GitAPI { + repositories: Repository[]; +} + +export interface Repository { + rootUri: vscode.Uri; + state: RepositoryState; +} + +export interface RepositoryState { + HEAD: Branch | undefined; + onDidChange: vscode.Event; +} + +export interface Branch { + name?: string; +} diff --git a/src/snyk/common/services/CacheService.ts b/src/snyk/common/services/CacheService.ts new file mode 100644 index 000000000..27f4b749c --- /dev/null +++ b/src/snyk/common/services/CacheService.ts @@ -0,0 +1,20 @@ +import { IVSCodeCommands } from '../vscode/commands'; +import { SNYK_CLEAR_CACHE_COMMAND } from '../constants/commands'; + +export interface IClearCacheService { + clearCache(folderUri?: string, cacheType?: string): Promise; +} + +export class ClearCacheService implements IClearCacheService { + constructor(private commandExecutor: IVSCodeCommands) {} + + async clearCache(folderUri?: string, cacheType?: string): Promise { + try { + const uri = folderUri || ''; + const type = cacheType || ''; + await this.commandExecutor.executeCommand(SNYK_CLEAR_CACHE_COMMAND, uri, type); + } catch (error) { + console.warn(`[ClearCacheService] Failed to clear cache`); + } + } +} diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index f603a4a88..8da5bd241 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -10,6 +10,7 @@ import { OpenIssueCommandArg } from './common/commands/types'; import { configuration } from './common/configuration/instance'; import { SnykConfiguration } from './common/configuration/snykConfiguration'; import { + SNYK_CLEAR_PERSISTED_CACHE_COMMAND, SNYK_DCIGNORE_COMMAND, SNYK_ENABLE_CODE_COMMAND, SNYK_IGNORE_ISSUE_COMMAND, @@ -78,6 +79,9 @@ import { OssVulnerabilityCountService } from './snykOss/services/vulnerabilityCo import { FeatureFlagService } from './common/services/featureFlagService'; import { DiagnosticsIssueProvider } from './common/services/diagnosticsService'; import { CodeIssueData, IacIssueData, OssIssueData } from './common/languageServer/types'; +import { ClearCacheService } from './common/services/CacheService'; +import { InMemory, Persisted } from './common/constants/general'; +import { GitAPI, GitExtension, Repository } from './common/git'; class SnykExtension extends SnykLib implements IExtension { public async activate(vscodeContext: vscode.ExtensionContext): Promise { @@ -91,11 +95,44 @@ class SnykExtension extends SnykLib implements IExtension { try { await this.initializeExtension(vscodeContext, snykConfiguration); + this.configureGitHandlers(); } catch (e) { ErrorHandler.handle(e, Logger); } } + private configureGitHandlers(): void { + // Get the Git extension + const gitExtension = vscode.extensions.getExtension('vscode.git')?.exports; + + if (!gitExtension) { + return; + } + + // Get the API from the Git extension + const git: GitAPI = gitExtension.getAPI(1); + + // Check if there are any repositories + const repositories: Repository[] = git?.repositories; + if (!repositories || repositories.length === 0) { + return; + } + const previousBranches = new Map(); + // Register event listener for changes in each repository + repositories.forEach((repo: Repository) => { + let previousBranch = repo.state.HEAD?.name; + previousBranches.set(repo, previousBranch); + repo.state.onDidChange(async () => { + const currentBranch = repo.state.HEAD?.name; + const storedPreviousBranch = previousBranches.get(repo); + if (currentBranch !== storedPreviousBranch) { + await this.cacheService.clearCache(repo.rootUri.toString(), InMemory); + previousBranches.set(repo, currentBranch); + } + }); + }); + } + private async getSnykConfiguration(): Promise { try { return await SnykConfiguration.get(extensionContext.extensionPath, configuration.isDevelopment); @@ -139,6 +176,7 @@ class SnykExtension extends SnykLib implements IExtension { ); this.learnService = new LearnService(vsCodeCommands); + this.cacheService = new ClearCacheService(vsCodeCommands); this.codeSettings = new CodeSettings(this.contextService, configuration, this.openerService, vsCodeCommands); @@ -425,6 +463,10 @@ class SnykExtension extends SnykLib implements IExtension { ), vscode.commands.registerCommand(SNYK_INITIATE_LOGIN_COMMAND, () => this.commandController.initiateLogin()), vscode.commands.registerCommand(SNYK_SET_TOKEN_COMMAND, () => this.commandController.setToken()), + vscode.commands.registerCommand( + SNYK_CLEAR_PERSISTED_CACHE_COMMAND, + async () => await this.cacheService.clearCache('', Persisted), + ), vscode.commands.registerCommand(SNYK_ENABLE_CODE_COMMAND, () => this.commandController.executeCommand(SNYK_ENABLE_CODE_COMMAND, () => this.enableCode()), ),