Unverified Commit b2ea32cb authored by Denys Mishunov's avatar Denys Mishunov
Browse files

feat(chat): set up Duo Chat WebView with initial state

At the moment, the only prop passed down to
the WebView is the slash commands, but any
static information, needed for the Web View
can be passed in the same way
parent f98cc317
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
import { defaultSlashCommands } from './gitlab_chat_slash_commands';

describe('GitLab Chat Slash Commands', () => {
  it('returns pre-defined set of slash commands', () => {
    expect(defaultSlashCommands).toEqual([
      expect.objectContaining({ name: '/reset' }),
      expect.objectContaining({ name: '/clean' }),
      expect.objectContaining({ name: '/tests' }),
      expect.objectContaining({ name: '/refactor' }),
      expect.objectContaining({ name: '/explain' }),
    ]);
  });
});
+39 −0
Original line number Diff line number Diff line
export interface GitlabChatSlashCommand {
  name: string;
  description: string;
  shouldSubmit?: boolean;
}

const ResetCommand: GitlabChatSlashCommand = {
  name: '/reset',
  description: 'Reset conversation, ignore the previous messages.',
  shouldSubmit: true,
};

const CleanCommand: GitlabChatSlashCommand = {
  name: '/clean',
  description: 'Delet all messages in this conversation.',
};

const TestsCommand: GitlabChatSlashCommand = {
  name: '/tests',
  description: 'Write tests for the selected snippet.',
};

const RefactorCommand: GitlabChatSlashCommand = {
  name: '/refactor',
  description: 'Refactor the selected snippet.',
};

const ExplainCommand: GitlabChatSlashCommand = {
  name: '/explain',
  description: 'Explain the selected snippet.',
};

export const defaultSlashCommands: GitlabChatSlashCommand[] = [
  ResetCommand,
  CleanCommand,
  TestsCommand,
  RefactorCommand,
  ExplainCommand,
];
+4 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ import * as vscode from 'vscode';
import { GitLabChatView } from './gitlab_chat_view';
import { GitLabChatRecord } from './gitlab_chat_record';
import { prepareWebviewSource } from '../utils/webviews/prepare_webview_source';
import { defaultSlashCommands } from './gitlab_chat_slash_commands';

jest.mock('../utils/webviews/wait_for_webview');
jest.mock('../utils/webviews/prepare_webview_source', () => ({
@@ -56,6 +57,9 @@ describe('GitLabChatView', () => {
        webview.webview,
        context,
        'gitlab_duo_chat',
        {
          slashCommands: defaultSlashCommands,
        },
      );
    });
  });
+11 −0
Original line number Diff line number Diff line
@@ -3,6 +3,8 @@ import { GitLabChatRecord } from './gitlab_chat_record';
import { waitForWebview } from '../utils/webviews/wait_for_webview';
import { log } from '../log';
import { prepareWebviewSource } from '../utils/webviews/prepare_webview_source';
import { defaultSlashCommands } from './gitlab_chat_slash_commands';
import type { GitlabChatSlashCommand } from './gitlab_chat_slash_commands';

export const CHAT_SIDEBAR_VIEW_ID = 'gl.chatView';

@@ -40,6 +42,10 @@ interface FeedbackMessage {
  };
}

interface WebViewInitialStateInterface {
  slashCommands: GitlabChatSlashCommand[];
}

export type ViewEmittedMessage = NewPromptMessage | FeedbackMessage | CleanChatMessage;

export class GitLabChatView {
@@ -66,10 +72,15 @@ export class GitLabChatView {
      enableScripts: true,
    };

    const initialState: WebViewInitialStateInterface = {
      slashCommands: defaultSlashCommands,
    };

    this.#chatView.webview.html = await prepareWebviewSource(
      this.#chatView.webview,
      this.#context,
      'gitlab_duo_chat',
      initialState,
    );

    await waitForWebview(this.#chatView.webview);
+18 −3
Original line number Diff line number Diff line
@@ -18,7 +18,7 @@ describe('prepareWebViewSource', () => {
      <link rel="stylesheet" href="/gitlab_duo_chat/assets/index.css">
    </head>
    <body>
      <div id="app"></div>
      <div id="app" data-initial-state='{{initialState}}'></div>
    </body>
  </html>
  `;
@@ -35,13 +35,17 @@ describe('prepareWebViewSource', () => {
      <link rel="stylesheet" href="file:///foo/bar/webviews/gitlab_duo_chat/assets/index.css">
    </head>
    <body>
      <div id="app"></div>
      <div id="app" data-initial-state=''></div>
    </body>
  </html>
  `;
  let context: vscode.ExtensionContext;
  let webview: vscode.Webview;

  const setHTMLFixture = (htmlContent: string) => {
    document.body.innerHTML = htmlContent;
  };

  beforeEach(() => {
    context = {
      extensionUri: vscode.Uri.file('/foo/bar'),
@@ -59,7 +63,18 @@ describe('prepareWebViewSource', () => {

  it('returns WebView source with inserted nonce and assets', async () => {
    const result = await prepareWebviewSource(webview, context, 'gitlab_duo_chat');

    expect(result).toStrictEqual(expectedHTML);
  });

  it('returns WebView source with initial app state set', async () => {
    const initState = {
      foo: 'bar',
    };
    const result = await prepareWebviewSource(webview, context, 'gitlab_duo_chat', initState);
    const expectedHtmlString = expectedHTML.replace(
      "data-initial-state=''",
      `data-initial-state='${JSON.stringify(initState)}'`,
    );
    expect(result).toStrictEqual(expectedHtmlString);
  });
});
Loading