Loading src/browser/browser.ts +0 −4 Original line number Diff line number Diff line Loading @@ -4,7 +4,6 @@ import { activateCommon } from '../common/main'; import * as featureFlags from '../common/feature_flags'; import { registerLanguageServer } from '../common/language_server/register_language_server'; import { CodeSuggestions } from '../common/code_suggestions/code_suggestions'; import { getChatSupport } from '../common/chat/api/get_chat_support'; import { browserLanguageClientFactory } from './language_server/browser_language_client_factory'; import { createDependencyContainer } from './dependency_container_browser'; Loading @@ -17,9 +16,6 @@ export const activate = async (context: vscode.ExtensionContext) => { await vscode.commands.executeCommand('setContext', 'gitlab:noAccount', false); await vscode.commands.executeCommand('setContext', 'gitlab:validState', true); const duoChatAvailable: boolean = await getChatSupport(dependencyContainer.gitLabPlatformManager); await vscode.commands.executeCommand('setContext', 'gitlab:chatAvailable', duoChatAvailable); await activateCommon(context, dependencyContainer, outputChannel); if (featureFlags.isEnabled(featureFlags.FeatureFlag.LanguageServerWebIDE)) { Loading src/common/chat/api/get_chat_support.test.ts +26 −33 Original line number Diff line number Diff line import { GitLabPlatformManager } from '../../platform/gitlab_platform'; import { GitLabPlatformManagerForChat } from '../get_platform_manager_for_chat'; import { gitlabPlatformForAccount } from '../../test_utils/entities'; import { createFakePartial } from '../../test_utils/create_fake_partial'; import { log } from '../../log'; import { getChatSupport, ChatAvailableResponseType } from './get_chat_support'; jest.mock('../get_platform_manager_for_chat'); import { getChatSupport, ChatAvailableResponseType, ChatSupportResponseInterface, } from './get_chat_support'; describe('getChatSupport', () => { let manager: GitLabPlatformManager; let platformManagerForChat: GitLabPlatformManagerForChat; const mockApiResponse = (duoChatAvailable: boolean = true) => { const apiResponse: ChatAvailableResponseType = { currentUser: { duoChatAvailable, }, }; jest.mocked(platformManagerForChat.getGitLabPlatform).mockResolvedValue({ ...gitlabPlatformForAccount, fetchFromApi: jest.fn().mockResolvedValue(apiResponse), }); gitlabPlatformForAccount.fetchFromApi = jest.fn().mockResolvedValue(apiResponse); }; const platformWithoutChatEnabled: ChatSupportResponseInterface = { hasSupportForChat: false }; beforeEach(() => { manager = createFakePartial<GitLabPlatformManager>({ getForAllAccounts: jest.fn(), }); platformManagerForChat = createFakePartial<GitLabPlatformManagerForChat>({ getGitLabPlatform: jest.fn(), }); jest.mocked(GitLabPlatformManagerForChat).mockImplementation(() => platformManagerForChat); jest.spyOn(log, 'error'); }); Loading @@ -37,33 +26,37 @@ describe('getChatSupport', () => { jest.clearAllMocks(); }); it('returns false if there is no platform', async () => { jest.mocked(platformManagerForChat.getGitLabPlatform).mockResolvedValue(undefined); const result = await getChatSupport(manager); expect(result).toBe(false); it(`returns ${JSON.stringify(platformWithoutChatEnabled)} if there is no platform`, async () => { const result1 = await getChatSupport(); expect(result1).toEqual(platformWithoutChatEnabled); const result2 = await getChatSupport(undefined); expect(result2).toEqual(platformWithoutChatEnabled); }); it('returns false and logs if fetching `duoChatAvailable` fails', async () => { jest.mocked(platformManagerForChat.getGitLabPlatform).mockResolvedValue({ it(`returns ${JSON.stringify( platformWithoutChatEnabled, )} and logs if fetching 'duoChatAvailable' fails`, async () => { const result = await getChatSupport({ ...gitlabPlatformForAccount, fetchFromApi: jest.fn().mockRejectedValueOnce('foo'), }); const result = await getChatSupport(manager); expect(result).toBe(false); expect(result).toEqual(platformWithoutChatEnabled); expect(log.error).toHaveBeenCalledWith('foo'); }); it('returns false and does not log if the user does not have chat support', async () => { it(`returns ${JSON.stringify( platformWithoutChatEnabled, )} and does not log if the user does not have chat support`, async () => { mockApiResponse(false); const result = await getChatSupport(manager); expect(result).toBe(false); const result = await getChatSupport(gitlabPlatformForAccount); expect(result).toEqual(platformWithoutChatEnabled); expect(log.error).not.toHaveBeenCalled(); }); it('returns true if the user has chat support', async () => { it('returns gitlab platform if it has chat support', async () => { mockApiResponse(); const result = await getChatSupport(manager); expect(result).toBe(true); const result = await getChatSupport(gitlabPlatformForAccount); expect(result).toEqual({ hasSupportForChat: true, platform: gitlabPlatformForAccount }); expect(log.error).not.toHaveBeenCalled(); }); }); src/common/chat/api/get_chat_support.ts +23 −10 Original line number Diff line number Diff line import { gql } from 'graphql-request'; import { GraphQLRequest } from '../../platform/web_ide'; import { GitLabPlatformManager } from '../../platform/gitlab_platform'; import { GitLabPlatformManagerForChat } from '../get_platform_manager_for_chat'; import { GitLabPlatformForAccount } from '../../platform/gitlab_platform'; import { log } from '../../log'; const queryGetChatAvailability = gql` Loading @@ -12,29 +11,43 @@ const queryGetChatAvailability = gql` } `; export interface ChatSupportResponseInterface { hasSupportForChat: boolean; platform?: GitLabPlatformForAccount; } export type ChatAvailableResponseType = { currentUser: { duoChatAvailable: boolean; }; }; export async function getChatSupport(manager: GitLabPlatformManager): Promise<boolean> { let user; const platformManagerForChat = new GitLabPlatformManagerForChat(manager); export async function getChatSupport( platform?: GitLabPlatformForAccount | undefined, ): Promise<ChatSupportResponseInterface> { const request: GraphQLRequest<ChatAvailableResponseType> = { type: 'graphql', query: queryGetChatAvailability, variables: {}, }; const platform = await platformManagerForChat.getGitLabPlatform(); const noSupportResponse: ChatSupportResponseInterface = { hasSupportForChat: false }; if (!platform) { return false; return noSupportResponse; } try { user = await platform.fetchFromApi(request); return user.currentUser.duoChatAvailable; const { currentUser: { duoChatAvailable }, } = await platform.fetchFromApi(request); if (duoChatAvailable) { return { hasSupportForChat: duoChatAvailable, platform, }; } return noSupportResponse; } catch (e) { log.error(e); return false; return noSupportResponse; } } src/common/chat/get_platform_manager_for_chat.test.ts +43 −3 Original line number Diff line number Diff line Loading @@ -3,8 +3,12 @@ import { GitLabPlatformManagerForChat } 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'; import { getChatSupport, ChatSupportResponseInterface } from './api/get_chat_support'; jest.mock('../utils/extension_configuration'); jest.mock('./api/get_chat_support', () => ({ getChatSupport: jest.fn(), })); describe('GitLabPlatformManagerForChat', () => { let platformManagerForChat: GitLabPlatformManagerForChat; Loading Loading @@ -60,20 +64,56 @@ describe('GitLabPlatformManagerForChat', () => { .mockResolvedValueOnce([customGitlabPlatformForAccount]); }); it('returns gitlab platform for that account', async () => { it('returns undefined if the platform for the account does not have chat enabled', async () => { jest.mocked(getChatSupport).mockResolvedValue({ hasSupportForChat: false }); expect(await platformManagerForChat.getGitLabPlatform()).toBeUndefined(); }); it('returns gitlab platform for that account if chat is available for the platform', async () => { jest .mocked(getChatSupport) .mockResolvedValue({ hasSupportForChat: true, platform: customGitlabPlatformForAccount }); expect(await platformManagerForChat.getGitLabPlatform()).toBe(customGitlabPlatformForAccount); }); }); describe('when multiple gitlab accounts are available', () => { const firstPlatformWithChatEnabled: ChatSupportResponseInterface = { hasSupportForChat: true, platform: firstGitlabPlatformForAccount, }; const secondPlatformWithChatEnabled: ChatSupportResponseInterface = { hasSupportForChat: true, platform: secondGitLabPlatformForAccount, }; const platformWithoutChatEnabled: ChatSupportResponseInterface = { hasSupportForChat: false }; beforeEach(() => { jest .mocked(gitlabPlatformManager.getForAllAccounts) .mockResolvedValueOnce([firstGitlabPlatformForAccount, secondGitLabPlatformForAccount]); }); it('returns gitlab platform for the first linked account', async () => { expect(await platformManagerForChat.getGitLabPlatform()).toBe(firstGitlabPlatformForAccount); it.each` desc | firstResolve | secondResolve | expectedPlatform ${'the first has'} | ${firstPlatformWithChatEnabled} | ${platformWithoutChatEnabled} | ${firstGitlabPlatformForAccount} ${'the second has'} | ${platformWithoutChatEnabled} | ${secondPlatformWithChatEnabled} | ${secondGitLabPlatformForAccount} ${'several have'} | ${firstPlatformWithChatEnabled} | ${secondPlatformWithChatEnabled} | ${firstGitlabPlatformForAccount} `( 'returns the correct gitlab platform when $desc the chat enabled', async ({ firstResolve, secondResolve, expectedPlatform }) => { jest .mocked(getChatSupport) .mockResolvedValue(platformWithoutChatEnabled) .mockResolvedValueOnce(firstResolve) .mockResolvedValueOnce(secondResolve); expect(await platformManagerForChat.getGitLabPlatform()).toBe(expectedPlatform); }, ); it('correctly returns undefined if none of the platforms have chat enabled', async () => { jest.mocked(getChatSupport).mockResolvedValue(platformWithoutChatEnabled); expect(await platformManagerForChat.getGitLabPlatform()).toBe(undefined); }); }); }); src/common/chat/get_platform_manager_for_chat.ts +14 −2 Original line number Diff line number Diff line import { GitLabPlatformManager, GitLabPlatformForAccount } from '../platform/gitlab_platform'; import { getChatSupport } from './api/get_chat_support'; export class GitLabPlatformManagerForChat { readonly #platformManager: GitLabPlatformManager; Loading @@ -17,10 +18,21 @@ export class GitLabPlatformManagerForChat { async getGitLabPlatform(): Promise<GitLabPlatformForAccount | undefined> { const platforms = await this.#platformManager.getForAllAccounts(); if (platforms.length === 0) { if (!platforms.length) { return undefined; } return platforms[0]; let platform: GitLabPlatformForAccount | undefined; // Using a for await loop in this context because we want to stop // evaluating accounts as soon as we find one with code suggestions enabled for await (const result of platforms.map(getChatSupport)) { if (result.hasSupportForChat) { platform = result.platform; break; } } return platform; } } Loading
src/browser/browser.ts +0 −4 Original line number Diff line number Diff line Loading @@ -4,7 +4,6 @@ import { activateCommon } from '../common/main'; import * as featureFlags from '../common/feature_flags'; import { registerLanguageServer } from '../common/language_server/register_language_server'; import { CodeSuggestions } from '../common/code_suggestions/code_suggestions'; import { getChatSupport } from '../common/chat/api/get_chat_support'; import { browserLanguageClientFactory } from './language_server/browser_language_client_factory'; import { createDependencyContainer } from './dependency_container_browser'; Loading @@ -17,9 +16,6 @@ export const activate = async (context: vscode.ExtensionContext) => { await vscode.commands.executeCommand('setContext', 'gitlab:noAccount', false); await vscode.commands.executeCommand('setContext', 'gitlab:validState', true); const duoChatAvailable: boolean = await getChatSupport(dependencyContainer.gitLabPlatformManager); await vscode.commands.executeCommand('setContext', 'gitlab:chatAvailable', duoChatAvailable); await activateCommon(context, dependencyContainer, outputChannel); if (featureFlags.isEnabled(featureFlags.FeatureFlag.LanguageServerWebIDE)) { Loading
src/common/chat/api/get_chat_support.test.ts +26 −33 Original line number Diff line number Diff line import { GitLabPlatformManager } from '../../platform/gitlab_platform'; import { GitLabPlatformManagerForChat } from '../get_platform_manager_for_chat'; import { gitlabPlatformForAccount } from '../../test_utils/entities'; import { createFakePartial } from '../../test_utils/create_fake_partial'; import { log } from '../../log'; import { getChatSupport, ChatAvailableResponseType } from './get_chat_support'; jest.mock('../get_platform_manager_for_chat'); import { getChatSupport, ChatAvailableResponseType, ChatSupportResponseInterface, } from './get_chat_support'; describe('getChatSupport', () => { let manager: GitLabPlatformManager; let platformManagerForChat: GitLabPlatformManagerForChat; const mockApiResponse = (duoChatAvailable: boolean = true) => { const apiResponse: ChatAvailableResponseType = { currentUser: { duoChatAvailable, }, }; jest.mocked(platformManagerForChat.getGitLabPlatform).mockResolvedValue({ ...gitlabPlatformForAccount, fetchFromApi: jest.fn().mockResolvedValue(apiResponse), }); gitlabPlatformForAccount.fetchFromApi = jest.fn().mockResolvedValue(apiResponse); }; const platformWithoutChatEnabled: ChatSupportResponseInterface = { hasSupportForChat: false }; beforeEach(() => { manager = createFakePartial<GitLabPlatformManager>({ getForAllAccounts: jest.fn(), }); platformManagerForChat = createFakePartial<GitLabPlatformManagerForChat>({ getGitLabPlatform: jest.fn(), }); jest.mocked(GitLabPlatformManagerForChat).mockImplementation(() => platformManagerForChat); jest.spyOn(log, 'error'); }); Loading @@ -37,33 +26,37 @@ describe('getChatSupport', () => { jest.clearAllMocks(); }); it('returns false if there is no platform', async () => { jest.mocked(platformManagerForChat.getGitLabPlatform).mockResolvedValue(undefined); const result = await getChatSupport(manager); expect(result).toBe(false); it(`returns ${JSON.stringify(platformWithoutChatEnabled)} if there is no platform`, async () => { const result1 = await getChatSupport(); expect(result1).toEqual(platformWithoutChatEnabled); const result2 = await getChatSupport(undefined); expect(result2).toEqual(platformWithoutChatEnabled); }); it('returns false and logs if fetching `duoChatAvailable` fails', async () => { jest.mocked(platformManagerForChat.getGitLabPlatform).mockResolvedValue({ it(`returns ${JSON.stringify( platformWithoutChatEnabled, )} and logs if fetching 'duoChatAvailable' fails`, async () => { const result = await getChatSupport({ ...gitlabPlatformForAccount, fetchFromApi: jest.fn().mockRejectedValueOnce('foo'), }); const result = await getChatSupport(manager); expect(result).toBe(false); expect(result).toEqual(platformWithoutChatEnabled); expect(log.error).toHaveBeenCalledWith('foo'); }); it('returns false and does not log if the user does not have chat support', async () => { it(`returns ${JSON.stringify( platformWithoutChatEnabled, )} and does not log if the user does not have chat support`, async () => { mockApiResponse(false); const result = await getChatSupport(manager); expect(result).toBe(false); const result = await getChatSupport(gitlabPlatformForAccount); expect(result).toEqual(platformWithoutChatEnabled); expect(log.error).not.toHaveBeenCalled(); }); it('returns true if the user has chat support', async () => { it('returns gitlab platform if it has chat support', async () => { mockApiResponse(); const result = await getChatSupport(manager); expect(result).toBe(true); const result = await getChatSupport(gitlabPlatformForAccount); expect(result).toEqual({ hasSupportForChat: true, platform: gitlabPlatformForAccount }); expect(log.error).not.toHaveBeenCalled(); }); });
src/common/chat/api/get_chat_support.ts +23 −10 Original line number Diff line number Diff line import { gql } from 'graphql-request'; import { GraphQLRequest } from '../../platform/web_ide'; import { GitLabPlatformManager } from '../../platform/gitlab_platform'; import { GitLabPlatformManagerForChat } from '../get_platform_manager_for_chat'; import { GitLabPlatformForAccount } from '../../platform/gitlab_platform'; import { log } from '../../log'; const queryGetChatAvailability = gql` Loading @@ -12,29 +11,43 @@ const queryGetChatAvailability = gql` } `; export interface ChatSupportResponseInterface { hasSupportForChat: boolean; platform?: GitLabPlatformForAccount; } export type ChatAvailableResponseType = { currentUser: { duoChatAvailable: boolean; }; }; export async function getChatSupport(manager: GitLabPlatformManager): Promise<boolean> { let user; const platformManagerForChat = new GitLabPlatformManagerForChat(manager); export async function getChatSupport( platform?: GitLabPlatformForAccount | undefined, ): Promise<ChatSupportResponseInterface> { const request: GraphQLRequest<ChatAvailableResponseType> = { type: 'graphql', query: queryGetChatAvailability, variables: {}, }; const platform = await platformManagerForChat.getGitLabPlatform(); const noSupportResponse: ChatSupportResponseInterface = { hasSupportForChat: false }; if (!platform) { return false; return noSupportResponse; } try { user = await platform.fetchFromApi(request); return user.currentUser.duoChatAvailable; const { currentUser: { duoChatAvailable }, } = await platform.fetchFromApi(request); if (duoChatAvailable) { return { hasSupportForChat: duoChatAvailable, platform, }; } return noSupportResponse; } catch (e) { log.error(e); return false; return noSupportResponse; } }
src/common/chat/get_platform_manager_for_chat.test.ts +43 −3 Original line number Diff line number Diff line Loading @@ -3,8 +3,12 @@ import { GitLabPlatformManagerForChat } 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'; import { getChatSupport, ChatSupportResponseInterface } from './api/get_chat_support'; jest.mock('../utils/extension_configuration'); jest.mock('./api/get_chat_support', () => ({ getChatSupport: jest.fn(), })); describe('GitLabPlatformManagerForChat', () => { let platformManagerForChat: GitLabPlatformManagerForChat; Loading Loading @@ -60,20 +64,56 @@ describe('GitLabPlatformManagerForChat', () => { .mockResolvedValueOnce([customGitlabPlatformForAccount]); }); it('returns gitlab platform for that account', async () => { it('returns undefined if the platform for the account does not have chat enabled', async () => { jest.mocked(getChatSupport).mockResolvedValue({ hasSupportForChat: false }); expect(await platformManagerForChat.getGitLabPlatform()).toBeUndefined(); }); it('returns gitlab platform for that account if chat is available for the platform', async () => { jest .mocked(getChatSupport) .mockResolvedValue({ hasSupportForChat: true, platform: customGitlabPlatformForAccount }); expect(await platformManagerForChat.getGitLabPlatform()).toBe(customGitlabPlatformForAccount); }); }); describe('when multiple gitlab accounts are available', () => { const firstPlatformWithChatEnabled: ChatSupportResponseInterface = { hasSupportForChat: true, platform: firstGitlabPlatformForAccount, }; const secondPlatformWithChatEnabled: ChatSupportResponseInterface = { hasSupportForChat: true, platform: secondGitLabPlatformForAccount, }; const platformWithoutChatEnabled: ChatSupportResponseInterface = { hasSupportForChat: false }; beforeEach(() => { jest .mocked(gitlabPlatformManager.getForAllAccounts) .mockResolvedValueOnce([firstGitlabPlatformForAccount, secondGitLabPlatformForAccount]); }); it('returns gitlab platform for the first linked account', async () => { expect(await platformManagerForChat.getGitLabPlatform()).toBe(firstGitlabPlatformForAccount); it.each` desc | firstResolve | secondResolve | expectedPlatform ${'the first has'} | ${firstPlatformWithChatEnabled} | ${platformWithoutChatEnabled} | ${firstGitlabPlatformForAccount} ${'the second has'} | ${platformWithoutChatEnabled} | ${secondPlatformWithChatEnabled} | ${secondGitLabPlatformForAccount} ${'several have'} | ${firstPlatformWithChatEnabled} | ${secondPlatformWithChatEnabled} | ${firstGitlabPlatformForAccount} `( 'returns the correct gitlab platform when $desc the chat enabled', async ({ firstResolve, secondResolve, expectedPlatform }) => { jest .mocked(getChatSupport) .mockResolvedValue(platformWithoutChatEnabled) .mockResolvedValueOnce(firstResolve) .mockResolvedValueOnce(secondResolve); expect(await platformManagerForChat.getGitLabPlatform()).toBe(expectedPlatform); }, ); it('correctly returns undefined if none of the platforms have chat enabled', async () => { jest.mocked(getChatSupport).mockResolvedValue(platformWithoutChatEnabled); expect(await platformManagerForChat.getGitLabPlatform()).toBe(undefined); }); }); });
src/common/chat/get_platform_manager_for_chat.ts +14 −2 Original line number Diff line number Diff line import { GitLabPlatformManager, GitLabPlatformForAccount } from '../platform/gitlab_platform'; import { getChatSupport } from './api/get_chat_support'; export class GitLabPlatformManagerForChat { readonly #platformManager: GitLabPlatformManager; Loading @@ -17,10 +18,21 @@ export class GitLabPlatformManagerForChat { async getGitLabPlatform(): Promise<GitLabPlatformForAccount | undefined> { const platforms = await this.#platformManager.getForAllAccounts(); if (platforms.length === 0) { if (!platforms.length) { return undefined; } return platforms[0]; let platform: GitLabPlatformForAccount | undefined; // Using a for await loop in this context because we want to stop // evaluating accounts as soon as we find one with code suggestions enabled for await (const result of platforms.map(getChatSupport)) { if (result.hasSupportForChat) { platform = result.platform; break; } } return platform; } }