Loading src/common/chat/get_platform_manager_for_chat.test.ts +69 −1 Original line number Diff line number Diff line import { GitLabPlatformForAccount, GitLabPlatformManager } from '../platform/gitlab_platform'; import { GitLabPlatformManagerForChat } from './get_platform_manager_for_chat'; import { GitLabEnvironment, GitLabPlatformManagerForChat, GITLAB_COM_URL, GITLAB_ORG_URL, GITLAB_DEVELOPMENT_URL, GITLAB_STAGING_URL, } from './get_platform_manager_for_chat'; import { account, gitlabPlatformForAccount } from '../test_utils/entities'; import { Account } from '../platform/gitlab_account'; import { createFakePartial } from '../test_utils/create_fake_partial'; Loading Loading @@ -116,4 +123,65 @@ describe('GitLabPlatformManagerForChat', () => { expect(await platformManagerForChat.getGitLabPlatform()).toBe(undefined); }); }); describe('when getGitLabEnvironment should return the correct instance', () => { let customGitlabPlatformForAccount: GitLabPlatformForAccount; beforeEach(() => { customGitlabPlatformForAccount = firstGitlabPlatformForAccount; jest .mocked(gitlabPlatformManager.getForAllAccounts) .mockResolvedValueOnce([firstGitlabPlatformForAccount]); }); it('should returns GITLAB_COM for GITLAB_COM_URL instance', async () => { customGitlabPlatformForAccount.account.instanceUrl = GITLAB_COM_URL; jest .mocked(getChatSupport) .mockResolvedValue({ hasSupportForChat: true, platform: customGitlabPlatformForAccount }); expect(await platformManagerForChat.getGitLabEnvironment()).toBe( GitLabEnvironment.GITLAB_COM, ); }); it('should returns GITLAB_ORG for GITLAB_COM_URL instance', async () => { customGitlabPlatformForAccount.account.instanceUrl = GITLAB_ORG_URL; jest .mocked(getChatSupport) .mockResolvedValue({ hasSupportForChat: true, platform: customGitlabPlatformForAccount }); expect(await platformManagerForChat.getGitLabEnvironment()).toBe( GitLabEnvironment.GITLAB_ORG, ); }); it('should returns GITLAB_DEVELOPMENT for GITLAB_DEVELOPMENT_URL instance', async () => { customGitlabPlatformForAccount.account.instanceUrl = GITLAB_DEVELOPMENT_URL; jest .mocked(getChatSupport) .mockResolvedValue({ hasSupportForChat: true, platform: customGitlabPlatformForAccount }); expect(await platformManagerForChat.getGitLabEnvironment()).toBe( GitLabEnvironment.GITLAB_DEVELOPMENT, ); }); it('should returns GITLAB_STAGING for GITLAB_STAGING_URL instance', async () => { customGitlabPlatformForAccount.account.instanceUrl = GITLAB_STAGING_URL; jest .mocked(getChatSupport) .mockResolvedValue({ hasSupportForChat: true, platform: customGitlabPlatformForAccount }); expect(await platformManagerForChat.getGitLabEnvironment()).toBe( GitLabEnvironment.GITLAB_STAGING, ); }); it('should returns GITLAB_STAGING_URL for any other instanceUrl', async () => { customGitlabPlatformForAccount.account.instanceUrl = ''; jest .mocked(getChatSupport) .mockResolvedValue({ hasSupportForChat: true, platform: customGitlabPlatformForAccount }); expect(await platformManagerForChat.getGitLabEnvironment()).toBe( GitLabEnvironment.GITLAB_SELF_MANAGED, ); }); }); }); src/common/chat/get_platform_manager_for_chat.ts +31 −0 Original line number Diff line number Diff line import { GitLabPlatformManager, GitLabPlatformForAccount } from '../platform/gitlab_platform'; import { getChatSupport } from './api/get_chat_support'; export const GITLAB_COM_URL: string = 'https://gitlab.com'; export const GITLAB_STAGING_URL: string = 'https://staging.gitlab.com'; export const GITLAB_ORG_URL: string = 'https://dev.gitlab.org'; export const GITLAB_DEVELOPMENT_URL: string = 'http://localhost'; export enum GitLabEnvironment { GITLAB_COM = 'production', GITLAB_STAGING = 'staging', GITLAB_ORG = 'org', GITLAB_DEVELOPMENT = 'development', GITLAB_SELF_MANAGED = 'self-managed', } export class GitLabPlatformManagerForChat { readonly #platformManager: GitLabPlatformManager; Loading Loading @@ -35,4 +48,22 @@ export class GitLabPlatformManagerForChat { return platform; } async getGitLabEnvironment(): Promise<GitLabEnvironment> { const platform = await this.getGitLabPlatform(); const instanceUrl = platform?.account.instanceUrl; switch (instanceUrl) { case GITLAB_COM_URL: return GitLabEnvironment.GITLAB_COM; case GITLAB_DEVELOPMENT_URL: return GitLabEnvironment.GITLAB_DEVELOPMENT; case GITLAB_STAGING_URL: return GitLabEnvironment.GITLAB_STAGING; case GITLAB_ORG_URL: return GitLabEnvironment.GITLAB_ORG; default: return GitLabEnvironment.GITLAB_SELF_MANAGED; } } } src/common/chat/gitlab_chat_controller.test.ts +17 −7 Original line number Diff line number Diff line import * as vscode from 'vscode'; import { GitLabChatController } from './gitlab_chat_controller'; import { GitLabPlatformManagerForChat } from './get_platform_manager_for_chat'; import { GitLabEnvironment, GitLabPlatformManagerForChat } from './get_platform_manager_for_chat'; import { GitLabChatRecord } from './gitlab_chat_record'; import { submitFeedback } from './utils/submit_feedback'; import { SubmitFeedbackParams, submitFeedback } from './utils/submit_feedback'; import { SPECIAL_MESSAGES } from './constants'; import { AiCompletionResponseMessageType } from '../api/graphql/ai_completion_response_channel'; import { createFakePartial } from '../test_utils/create_fake_partial'; const apiMock = { processNewUserPrompt: jest.fn(), pullAiMessage: jest.fn(), cleanChat: jest.fn(), subscribeToUpdates: jest.fn(), getGitLabEnvironment: jest.fn(), }; jest.mock('./gitlab_chat_api', () => ({ Loading @@ -36,10 +38,13 @@ jest.mock('./gitlab_chat_view', () => ({ })); describe('GitLabChatController', () => { let platformManager: GitLabPlatformManagerForChat; let controller: GitLabChatController; beforeEach(() => { const platformManager = createFakePartial<GitLabPlatformManagerForChat>({ getGitLabEnvironment: async () => GitLabEnvironment.GITLAB_COM, }); controller = new GitLabChatController(platformManager, {} as vscode.ExtensionContext); apiMock.processNewUserPrompt = jest.fn().mockResolvedValue({ aiAction: { Loading Loading @@ -307,15 +312,21 @@ describe('GitLabChatController', () => { describe('viewMessageHandler', () => { describe('trackFeedback', () => { it('calls submitFeedback when data is present', async () => { const expected: SubmitFeedbackParams = { extendedTextFeedback: 'free text', feedbackChoices: ['choice1', 'choice2'], gitlabEnvironment: GitLabEnvironment.GITLAB_COM, }; await controller.viewMessageHandler({ eventType: 'trackFeedback', data: { extendedTextFeedback: 'free text', feedbackChoices: ['choice1', 'choice2'], extendedTextFeedback: expected.extendedTextFeedback, feedbackChoices: expected.feedbackChoices, }, }); expect(submitFeedback).toHaveBeenCalledWith('free text', ['choice1', 'choice2']); expect(submitFeedback).toHaveBeenCalledWith(expected); }); it('does not call submitFeedback when no data is present', async () => { Loading @@ -326,7 +337,6 @@ describe('GitLabChatController', () => { expect(submitFeedback).not.toHaveBeenCalled(); }); }); describe('newPrompt', () => { it('processes new record', async () => { controller.processNewUserRecord = jest.fn(); Loading src/common/chat/gitlab_chat_controller.ts +10 −1 Original line number Diff line number Diff line Loading @@ -14,12 +14,15 @@ export class GitLabChatController implements vscode.WebviewViewProvider { readonly #api: GitLabChatApi; readonly #manager: GitLabPlatformManagerForChat; constructor(manager: GitLabPlatformManagerForChat, context: vscode.ExtensionContext) { this.chatHistory = []; this.#api = new GitLabChatApi(manager); this.#view = new GitLabChatView(context); this.#view.onViewMessage(this.viewMessageHandler.bind(this)); this.#view.onDidBecomeVisible(this.#restoreHistory.bind(this)); this.#manager = manager; } async resolveWebviewView(webviewView: vscode.WebviewView) { Loading @@ -40,7 +43,13 @@ export class GitLabChatController implements vscode.WebviewViewProvider { } case 'trackFeedback': { if (message.data) { await submitFeedback(message.data.extendedTextFeedback, message.data.feedbackChoices); const gitlabEnvironment = await this.#manager.getGitLabEnvironment(); await submitFeedback({ extendedTextFeedback: message.data.extendedTextFeedback, feedbackChoices: message.data.feedbackChoices, gitlabEnvironment, }); } break; Loading src/common/chat/utils/submit_feedback.test.ts +40 −12 Original line number Diff line number Diff line import { submitFeedback } from './submit_feedback'; import { GitLabEnvironment } from '../get_platform_manager_for_chat'; import { SubmitFeedbackParams, submitFeedback } from './submit_feedback'; jest.mock('../../snowplow/snowplow', () => ({ Snowplow: { Loading @@ -17,15 +18,22 @@ describe('submitFeedback', () => { describe('with feedback', () => { it('sends snowplow event', async () => { await submitFeedback('Freetext feedback', ['helpful', 'fast']); const expected: SubmitFeedbackParams = { extendedTextFeedback: 'Freetext feedback', feedbackChoices: ['helpful', 'fast'], gitlabEnvironment: GitLabEnvironment.GITLAB_COM, }; await submitFeedback(expected); const standardContext = { schema: 'iglu:com.gitlab/gitlab_standard/jsonschema/1-0-9', data: { extra: { extended_feedback: 'Freetext feedback', source: 'gitlab-vscode', extra: { extended_feedback: expected.extendedTextFeedback, }, environment: expected.gitlabEnvironment, }, }; Loading @@ -41,15 +49,22 @@ describe('submitFeedback', () => { }); it('sends snowplow event when choices are null', async () => { await submitFeedback('Freetext feedback', null); const expected: SubmitFeedbackParams = { extendedTextFeedback: 'Freetext feedback', feedbackChoices: null, gitlabEnvironment: GitLabEnvironment.GITLAB_COM, }; await submitFeedback(expected); const standardContext = { schema: 'iglu:com.gitlab/gitlab_standard/jsonschema/1-0-9', data: { extra: { extended_feedback: 'Freetext feedback', source: 'gitlab-vscode', extra: { extended_feedback: expected.extendedTextFeedback, }, environment: expected.gitlabEnvironment, }, }; Loading @@ -64,15 +79,22 @@ describe('submitFeedback', () => { }); it('sends snowplow event when free text feedback is null', async () => { await submitFeedback(null, ['helpful', 'fast']); const expected: SubmitFeedbackParams = { extendedTextFeedback: null, feedbackChoices: ['helpful', 'fast'], gitlabEnvironment: GitLabEnvironment.GITLAB_COM, }; await submitFeedback(expected); const standardContext = { schema: 'iglu:com.gitlab/gitlab_standard/jsonschema/1-0-9', data: { extra: { extended_feedback: null, source: 'gitlab-vscode', extra: { extended_feedback: expected.extendedTextFeedback, }, environment: expected.gitlabEnvironment, }, }; Loading @@ -89,14 +111,20 @@ describe('submitFeedback', () => { }); describe('with empty feedback', () => { const emptySubmitFeedbackParams: SubmitFeedbackParams = { extendedTextFeedback: '', feedbackChoices: [], gitlabEnvironment: GitLabEnvironment.GITLAB_COM, }; it('does not send a snowplow event', async () => { await submitFeedback('', []); await submitFeedback(emptySubmitFeedbackParams); expect(trackStructEvent).not.toHaveBeenCalled(); }); it('does not send a snowplow event when free text feedback and choices are null', async () => { await submitFeedback(null, null); await submitFeedback(emptySubmitFeedbackParams); expect(trackStructEvent).not.toHaveBeenCalled(); }); Loading Loading
src/common/chat/get_platform_manager_for_chat.test.ts +69 −1 Original line number Diff line number Diff line import { GitLabPlatformForAccount, GitLabPlatformManager } from '../platform/gitlab_platform'; import { GitLabPlatformManagerForChat } from './get_platform_manager_for_chat'; import { GitLabEnvironment, GitLabPlatformManagerForChat, GITLAB_COM_URL, GITLAB_ORG_URL, GITLAB_DEVELOPMENT_URL, GITLAB_STAGING_URL, } from './get_platform_manager_for_chat'; import { account, gitlabPlatformForAccount } from '../test_utils/entities'; import { Account } from '../platform/gitlab_account'; import { createFakePartial } from '../test_utils/create_fake_partial'; Loading Loading @@ -116,4 +123,65 @@ describe('GitLabPlatformManagerForChat', () => { expect(await platformManagerForChat.getGitLabPlatform()).toBe(undefined); }); }); describe('when getGitLabEnvironment should return the correct instance', () => { let customGitlabPlatformForAccount: GitLabPlatformForAccount; beforeEach(() => { customGitlabPlatformForAccount = firstGitlabPlatformForAccount; jest .mocked(gitlabPlatformManager.getForAllAccounts) .mockResolvedValueOnce([firstGitlabPlatformForAccount]); }); it('should returns GITLAB_COM for GITLAB_COM_URL instance', async () => { customGitlabPlatformForAccount.account.instanceUrl = GITLAB_COM_URL; jest .mocked(getChatSupport) .mockResolvedValue({ hasSupportForChat: true, platform: customGitlabPlatformForAccount }); expect(await platformManagerForChat.getGitLabEnvironment()).toBe( GitLabEnvironment.GITLAB_COM, ); }); it('should returns GITLAB_ORG for GITLAB_COM_URL instance', async () => { customGitlabPlatformForAccount.account.instanceUrl = GITLAB_ORG_URL; jest .mocked(getChatSupport) .mockResolvedValue({ hasSupportForChat: true, platform: customGitlabPlatformForAccount }); expect(await platformManagerForChat.getGitLabEnvironment()).toBe( GitLabEnvironment.GITLAB_ORG, ); }); it('should returns GITLAB_DEVELOPMENT for GITLAB_DEVELOPMENT_URL instance', async () => { customGitlabPlatformForAccount.account.instanceUrl = GITLAB_DEVELOPMENT_URL; jest .mocked(getChatSupport) .mockResolvedValue({ hasSupportForChat: true, platform: customGitlabPlatformForAccount }); expect(await platformManagerForChat.getGitLabEnvironment()).toBe( GitLabEnvironment.GITLAB_DEVELOPMENT, ); }); it('should returns GITLAB_STAGING for GITLAB_STAGING_URL instance', async () => { customGitlabPlatformForAccount.account.instanceUrl = GITLAB_STAGING_URL; jest .mocked(getChatSupport) .mockResolvedValue({ hasSupportForChat: true, platform: customGitlabPlatformForAccount }); expect(await platformManagerForChat.getGitLabEnvironment()).toBe( GitLabEnvironment.GITLAB_STAGING, ); }); it('should returns GITLAB_STAGING_URL for any other instanceUrl', async () => { customGitlabPlatformForAccount.account.instanceUrl = ''; jest .mocked(getChatSupport) .mockResolvedValue({ hasSupportForChat: true, platform: customGitlabPlatformForAccount }); expect(await platformManagerForChat.getGitLabEnvironment()).toBe( GitLabEnvironment.GITLAB_SELF_MANAGED, ); }); }); });
src/common/chat/get_platform_manager_for_chat.ts +31 −0 Original line number Diff line number Diff line import { GitLabPlatformManager, GitLabPlatformForAccount } from '../platform/gitlab_platform'; import { getChatSupport } from './api/get_chat_support'; export const GITLAB_COM_URL: string = 'https://gitlab.com'; export const GITLAB_STAGING_URL: string = 'https://staging.gitlab.com'; export const GITLAB_ORG_URL: string = 'https://dev.gitlab.org'; export const GITLAB_DEVELOPMENT_URL: string = 'http://localhost'; export enum GitLabEnvironment { GITLAB_COM = 'production', GITLAB_STAGING = 'staging', GITLAB_ORG = 'org', GITLAB_DEVELOPMENT = 'development', GITLAB_SELF_MANAGED = 'self-managed', } export class GitLabPlatformManagerForChat { readonly #platformManager: GitLabPlatformManager; Loading Loading @@ -35,4 +48,22 @@ export class GitLabPlatformManagerForChat { return platform; } async getGitLabEnvironment(): Promise<GitLabEnvironment> { const platform = await this.getGitLabPlatform(); const instanceUrl = platform?.account.instanceUrl; switch (instanceUrl) { case GITLAB_COM_URL: return GitLabEnvironment.GITLAB_COM; case GITLAB_DEVELOPMENT_URL: return GitLabEnvironment.GITLAB_DEVELOPMENT; case GITLAB_STAGING_URL: return GitLabEnvironment.GITLAB_STAGING; case GITLAB_ORG_URL: return GitLabEnvironment.GITLAB_ORG; default: return GitLabEnvironment.GITLAB_SELF_MANAGED; } } }
src/common/chat/gitlab_chat_controller.test.ts +17 −7 Original line number Diff line number Diff line import * as vscode from 'vscode'; import { GitLabChatController } from './gitlab_chat_controller'; import { GitLabPlatformManagerForChat } from './get_platform_manager_for_chat'; import { GitLabEnvironment, GitLabPlatformManagerForChat } from './get_platform_manager_for_chat'; import { GitLabChatRecord } from './gitlab_chat_record'; import { submitFeedback } from './utils/submit_feedback'; import { SubmitFeedbackParams, submitFeedback } from './utils/submit_feedback'; import { SPECIAL_MESSAGES } from './constants'; import { AiCompletionResponseMessageType } from '../api/graphql/ai_completion_response_channel'; import { createFakePartial } from '../test_utils/create_fake_partial'; const apiMock = { processNewUserPrompt: jest.fn(), pullAiMessage: jest.fn(), cleanChat: jest.fn(), subscribeToUpdates: jest.fn(), getGitLabEnvironment: jest.fn(), }; jest.mock('./gitlab_chat_api', () => ({ Loading @@ -36,10 +38,13 @@ jest.mock('./gitlab_chat_view', () => ({ })); describe('GitLabChatController', () => { let platformManager: GitLabPlatformManagerForChat; let controller: GitLabChatController; beforeEach(() => { const platformManager = createFakePartial<GitLabPlatformManagerForChat>({ getGitLabEnvironment: async () => GitLabEnvironment.GITLAB_COM, }); controller = new GitLabChatController(platformManager, {} as vscode.ExtensionContext); apiMock.processNewUserPrompt = jest.fn().mockResolvedValue({ aiAction: { Loading Loading @@ -307,15 +312,21 @@ describe('GitLabChatController', () => { describe('viewMessageHandler', () => { describe('trackFeedback', () => { it('calls submitFeedback when data is present', async () => { const expected: SubmitFeedbackParams = { extendedTextFeedback: 'free text', feedbackChoices: ['choice1', 'choice2'], gitlabEnvironment: GitLabEnvironment.GITLAB_COM, }; await controller.viewMessageHandler({ eventType: 'trackFeedback', data: { extendedTextFeedback: 'free text', feedbackChoices: ['choice1', 'choice2'], extendedTextFeedback: expected.extendedTextFeedback, feedbackChoices: expected.feedbackChoices, }, }); expect(submitFeedback).toHaveBeenCalledWith('free text', ['choice1', 'choice2']); expect(submitFeedback).toHaveBeenCalledWith(expected); }); it('does not call submitFeedback when no data is present', async () => { Loading @@ -326,7 +337,6 @@ describe('GitLabChatController', () => { expect(submitFeedback).not.toHaveBeenCalled(); }); }); describe('newPrompt', () => { it('processes new record', async () => { controller.processNewUserRecord = jest.fn(); Loading
src/common/chat/gitlab_chat_controller.ts +10 −1 Original line number Diff line number Diff line Loading @@ -14,12 +14,15 @@ export class GitLabChatController implements vscode.WebviewViewProvider { readonly #api: GitLabChatApi; readonly #manager: GitLabPlatformManagerForChat; constructor(manager: GitLabPlatformManagerForChat, context: vscode.ExtensionContext) { this.chatHistory = []; this.#api = new GitLabChatApi(manager); this.#view = new GitLabChatView(context); this.#view.onViewMessage(this.viewMessageHandler.bind(this)); this.#view.onDidBecomeVisible(this.#restoreHistory.bind(this)); this.#manager = manager; } async resolveWebviewView(webviewView: vscode.WebviewView) { Loading @@ -40,7 +43,13 @@ export class GitLabChatController implements vscode.WebviewViewProvider { } case 'trackFeedback': { if (message.data) { await submitFeedback(message.data.extendedTextFeedback, message.data.feedbackChoices); const gitlabEnvironment = await this.#manager.getGitLabEnvironment(); await submitFeedback({ extendedTextFeedback: message.data.extendedTextFeedback, feedbackChoices: message.data.feedbackChoices, gitlabEnvironment, }); } break; Loading
src/common/chat/utils/submit_feedback.test.ts +40 −12 Original line number Diff line number Diff line import { submitFeedback } from './submit_feedback'; import { GitLabEnvironment } from '../get_platform_manager_for_chat'; import { SubmitFeedbackParams, submitFeedback } from './submit_feedback'; jest.mock('../../snowplow/snowplow', () => ({ Snowplow: { Loading @@ -17,15 +18,22 @@ describe('submitFeedback', () => { describe('with feedback', () => { it('sends snowplow event', async () => { await submitFeedback('Freetext feedback', ['helpful', 'fast']); const expected: SubmitFeedbackParams = { extendedTextFeedback: 'Freetext feedback', feedbackChoices: ['helpful', 'fast'], gitlabEnvironment: GitLabEnvironment.GITLAB_COM, }; await submitFeedback(expected); const standardContext = { schema: 'iglu:com.gitlab/gitlab_standard/jsonschema/1-0-9', data: { extra: { extended_feedback: 'Freetext feedback', source: 'gitlab-vscode', extra: { extended_feedback: expected.extendedTextFeedback, }, environment: expected.gitlabEnvironment, }, }; Loading @@ -41,15 +49,22 @@ describe('submitFeedback', () => { }); it('sends snowplow event when choices are null', async () => { await submitFeedback('Freetext feedback', null); const expected: SubmitFeedbackParams = { extendedTextFeedback: 'Freetext feedback', feedbackChoices: null, gitlabEnvironment: GitLabEnvironment.GITLAB_COM, }; await submitFeedback(expected); const standardContext = { schema: 'iglu:com.gitlab/gitlab_standard/jsonschema/1-0-9', data: { extra: { extended_feedback: 'Freetext feedback', source: 'gitlab-vscode', extra: { extended_feedback: expected.extendedTextFeedback, }, environment: expected.gitlabEnvironment, }, }; Loading @@ -64,15 +79,22 @@ describe('submitFeedback', () => { }); it('sends snowplow event when free text feedback is null', async () => { await submitFeedback(null, ['helpful', 'fast']); const expected: SubmitFeedbackParams = { extendedTextFeedback: null, feedbackChoices: ['helpful', 'fast'], gitlabEnvironment: GitLabEnvironment.GITLAB_COM, }; await submitFeedback(expected); const standardContext = { schema: 'iglu:com.gitlab/gitlab_standard/jsonschema/1-0-9', data: { extra: { extended_feedback: null, source: 'gitlab-vscode', extra: { extended_feedback: expected.extendedTextFeedback, }, environment: expected.gitlabEnvironment, }, }; Loading @@ -89,14 +111,20 @@ describe('submitFeedback', () => { }); describe('with empty feedback', () => { const emptySubmitFeedbackParams: SubmitFeedbackParams = { extendedTextFeedback: '', feedbackChoices: [], gitlabEnvironment: GitLabEnvironment.GITLAB_COM, }; it('does not send a snowplow event', async () => { await submitFeedback('', []); await submitFeedback(emptySubmitFeedbackParams); expect(trackStructEvent).not.toHaveBeenCalled(); }); it('does not send a snowplow event when free text feedback and choices are null', async () => { await submitFeedback(null, null); await submitFeedback(emptySubmitFeedbackParams); expect(trackStructEvent).not.toHaveBeenCalled(); }); Loading