Commit fac29a01 authored by Dheeraj Joshi's avatar Dheeraj Joshi 🔴
Browse files

feat: add webview for security findings

parent 6302a8cb
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -297,6 +297,11 @@
        "enablement": "gitlab.canScrollToBottom",
        "category": "GitLab",
        "icon": "$(fold-down)"
      },
      {
        "command": "gl.viewSecurityFinding",
        "title": "View Security finding details",
        "category": "GitLab"
      }
    ],
    "menus": {
+8 −0
Original line number Diff line number Diff line
@@ -15,6 +15,13 @@ export async function copyPendingJobAssets() {
  );
}

export async function copySecurityFindingAssets() {
  return copyFile(
    path.join(root, 'webviews/security_finding.html'),
    path.join(root, `dist-desktop/webviews/security_finding.html`),
  );
}

export async function compileSource() {
  await run('tsc', ['-p', root]);
}
@@ -69,6 +76,7 @@ export async function buildDesktop() {
  await Promise.all([
    prepareWebviews(desktopWebviews, ENVIRONMENTS.DESKTOP),
    copyPendingJobAssets(),
    copySecurityFindingAssets(),
    checkAndBuildExtension(['--minify']),
    generateAssets(packageJson, ENVIRONMENTS.DESKTOP),
  ]);
+7 −1
Original line number Diff line number Diff line
import { copyPendingJobAssets, watchDesktop, watchWebviews } from './utils/desktop_jobs.mjs';
import {
  copyPendingJobAssets,
  copySecurityFindingAssets,
  watchDesktop,
  watchWebviews,
} from './utils/desktop_jobs.mjs';
import { commonJobs, generateAssets, writePackageJson } from './utils/common_jobs.mjs';
import { createDesktopPackageJson } from './utils/packages.mjs';
import { ENVIRONMENTS } from './constants.mjs';
@@ -11,6 +16,7 @@ async function main() {
    writePackageJson(packageJson, ENVIRONMENTS.DESKTOP),
    generateAssets(packageJson, ENVIRONMENTS.DESKTOP),
    copyPendingJobAssets(),
    copySecurityFindingAssets(),
  ]);

  const abortController = new AbortController();
+37 −0
Original line number Diff line number Diff line
import * as vscode from 'vscode';
import { asMock } from '../test_utils/as_mock';
import { createExtensionContext, securityReport } from '../test_utils/entities';
import { SecurityFindingWebviewController } from './security_finding_controller';

const finding = securityReport.fixed[0];

describe('SecurityFindingWebviewController', () => {
  let controller: SecurityFindingWebviewController;

  beforeEach(async () => {
    asMock(vscode.window.createWebviewPanel).mockImplementation(() => ({
      webview: {},
      onDidDispose: jest.fn(),
      reveal: jest.fn(),
    }));

    controller = new SecurityFindingWebviewController();
    await controller.init(createExtensionContext());
  });
  afterEach(() => {
    jest.resetAllMocks();
  });

  it('creates and updates panel correctly', () => {
    const panel = controller.open(finding);
    expect(panel.title).toBe(finding.title);

    const webviewHtml = panel.webview.html;
    expect(webviewHtml).toContain(finding.title);
    expect(webviewHtml).toContain(finding.description);
    expect(webviewHtml).toContain(finding.severity);

    controller.open({ ...finding, title: 'test' });
    expect(panel.title).toBe('test');
  });
});
+72 −0
Original line number Diff line number Diff line
import { promises as fs } from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import assert from 'assert';
import { WEBVIEW_SECURITY_FINDING } from '../constants';
import { GqlSecurityFinding } from '../gitlab/security_findings/api/get_security_finding_report';

export type SecurityFindingWebviewPanel = vscode.WebviewPanel & {
  finding: GqlSecurityFinding;
};

export class SecurityFindingWebviewController {
  async init(context: vscode.ExtensionContext) {
    this.#context = context;

    this.#htmlContent = await fs.readFile(
      path.join(this.#context.extensionPath, 'webviews/security_finding.html'),
      'utf-8',
    );
  }

  #context?: vscode.ExtensionContext;

  #panel?: SecurityFindingWebviewPanel;

  #htmlContent = '';

  #createEmptyPanel(): SecurityFindingWebviewPanel {
    const panel = vscode.window.createWebviewPanel(
      WEBVIEW_SECURITY_FINDING,
      '',
      vscode.ViewColumn.Active,
      {},
    ) as SecurityFindingWebviewPanel;

    this.#panel = panel;

    panel.onDidDispose(() => {
      if (this.#panel === panel) {
        this.#panel = undefined;
      }
    });

    return panel;
  }

  createOrUpdateWebview(
    finding: GqlSecurityFinding,
    existingPanel?: SecurityFindingWebviewPanel,
  ): SecurityFindingWebviewPanel {
    assert(this.#context);
    const { title, description, severity } = finding;
    const panel = existingPanel ?? this.#createEmptyPanel();

    panel.title = title;
    panel.finding = finding;

    panel.webview.html = this.#htmlContent
      .replace('{{title}}', title)
      .replace('{{description}}', description)
      .replace('{{severity}}', severity);
    return panel;
  }

  open(item: GqlSecurityFinding): SecurityFindingWebviewPanel {
    const panel = this.createOrUpdateWebview(item, this.#panel);
    panel.reveal();
    return panel;
  }
}

export const securityFindingWebviewController = new SecurityFindingWebviewController();
Loading