From 5c3f39a7ebd6443499e3f89fd92a609e123e624b Mon Sep 17 00:00:00 2001 From: Ganesh Nalawade Date: Thu, 19 Oct 2023 16:18:40 +0530 Subject: [PATCH] Fix for multi-task and single-task lightspeed suggestions (#992) * Fix for multi-task and single-task lightspeed suggestions * Remove whitespaces from blank lines in between multi-task suggestion * Show user information message is model Id is invalid. * If file is under playbook folder don't trigger single-task suggestion in play context. * Add information message for user if both Github copilot for Ansible and Lightspeed setting in enabled. * Don't trigger single-task suggestion if `- name: ` pattern if found under `vars` context. * Remove `ansible_type` field from content matches display as it is no longer used. * If user is not logged in and lightspeed setting is enabled show login message for mulit-task suggestion trigger request. * remove ansible_type field from content match params * update log message --------- Co-authored-by: Ansible-lightspeed-Bot --- src/features/lightspeed/api.ts | 34 +++++++++++++++++-- src/features/lightspeed/base.ts | 21 ++++++++++-- .../lightspeed/contentMatchesWebview.ts | 1 - src/features/lightspeed/inlineSuggestions.ts | 12 +++---- .../lightspeed/lightSpeedOAuthProvider.ts | 9 +++-- src/features/lightspeed/utils/data.ts | 26 ++++++++++---- src/interfaces/lightspeed.ts | 1 - 7 files changed, 81 insertions(+), 23 deletions(-) diff --git a/src/features/lightspeed/api.ts b/src/features/lightspeed/api.ts index 0cce09a00..159d6a752 100644 --- a/src/features/lightspeed/api.ts +++ b/src/features/lightspeed/api.ts @@ -76,6 +76,11 @@ export class LightSpeedAPI { console.error("Ansible Lightspeed instance is not initialized."); return {} as CompletionResponseParams; } + console.log( + `[ansible-lightspeed] Completion request sent to lightspeed: ${JSON.stringify( + inputData + )}` + ); try { const response = await axiosInstance.post( LIGHTSPEED_SUGGESTION_COMPLETION_URL, @@ -112,6 +117,23 @@ export class LightSpeedAPI { vscode.window.showErrorMessage( "Bad Request response. Please try again." ); + } else if (err?.response?.status === 403) { + const responseErrorData = >( + err?.response?.data + ); + if ( + responseErrorData && + responseErrorData.hasOwnProperty("message") && + responseErrorData.message?.includes("WCA Model ID is invalid") + ) { + vscode.window.showErrorMessage( + `Model ID "${this.settingsManager.settings.lightSpeedService.model}" is invalid. Please contact your administrator.` + ); + } else { + vscode.window.showErrorMessage( + `User not authorized to access Ansible Lightspeed.` + ); + } } else if (err?.response?.status.toString().startsWith("5")) { vscode.window.showErrorMessage( "Ansible Lightspeed encountered an error. Try again after some time." @@ -165,7 +187,11 @@ export class LightSpeedAPI { if (Object.keys(inputData).length === 0) { return {} as FeedbackResponseParams; } - + console.log( + `[ansible-lightspeed] Feedback request sent to lightspeed: ${JSON.stringify( + inputData + )}` + ); try { const response = await axiosInstance.post( LIGHTSPEED_SUGGESTION_FEEDBACK_URL, @@ -177,7 +203,6 @@ export class LightSpeedAPI { if (showInfoMessage) { vscode.window.showInformationMessage("Thanks for your feedback!"); } - console.log(`Event sent to lightspeed: ${JSON.stringify(inputData)}}`); return response.data; } catch (error) { const err = error as AxiosError; @@ -217,6 +242,11 @@ export class LightSpeedAPI { return {} as ContentMatchesResponseParams; } try { + console.log( + `[ansible-lightspeed] Content Match request sent to lightspeed: ${JSON.stringify( + inputData + )}` + ); const response = await axiosInstance.post( LIGHTSPEED_SUGGESTION_CONTENT_MATCHES_URL, inputData, diff --git a/src/features/lightspeed/base.ts b/src/features/lightspeed/base.ts index c74e98970..51c54f777 100644 --- a/src/features/lightspeed/base.ts +++ b/src/features/lightspeed/base.ts @@ -21,6 +21,7 @@ import { LightspeedStatusBar } from "./statusBar"; import { IVarsFileContext } from "../../interfaces/lightspeed"; import { getCustomRolePaths, getCommonRoles } from "../utils/ansible"; import { watchRolesDirectory } from "./utils/watchers"; +import { LightSpeedServiceSettings } from "../../interfaces/extensionSettings"; export class LightSpeedManager { private context; @@ -82,9 +83,10 @@ export class LightSpeedManager { } public async reInitialize(): Promise { - const lightspeedEnabled = await vscode.workspace - .getConfiguration("ansible") - .get("lightspeed.enabled"); + const lightspeedSettings = ( + vscode.workspace.getConfiguration("ansible").get("lightspeed") + ); + const lightspeedEnabled = lightspeedSettings.enabled; if (!lightspeedEnabled) { await this.resetContext(); @@ -94,6 +96,19 @@ export class LightSpeedManager { } else { this.lightSpeedAuthenticationProvider.initialize(); this.setContext(); + if (lightspeedSettings.suggestions.enabled) { + const githubConfig = (( + vscode.workspace.getConfiguration("github") + )) as { + copilot: { enable?: { ansible?: boolean } }; + }; + const copilotEnableForAnsible = githubConfig.copilot.enable?.ansible; + if (copilotEnableForAnsible) { + vscode.window.showInformationMessage( + "Please disable GitHub Copilot for Ansible Lightspeed file types to use Ansible Lightspeed." + ); + } + } } } diff --git a/src/features/lightspeed/contentMatchesWebview.ts b/src/features/lightspeed/contentMatchesWebview.ts index d82e228da..ee89583b8 100644 --- a/src/features/lightspeed/contentMatchesWebview.ts +++ b/src/features/lightspeed/contentMatchesWebview.ts @@ -182,7 +182,6 @@ export class ContentMatchesWebview implements vscode.WebviewViewProvider {
  • Path: ${contentMatchResponse.path}
  • Data Source: ${contentMatchResponse.data_source}
  • License: ${contentMatchResponse.license}
  • -
  • Ansible type: ${contentMatchResponse.ansible_type}
  • Score: ${contentMatchResponse.score}
  • diff --git a/src/features/lightspeed/inlineSuggestions.ts b/src/features/lightspeed/inlineSuggestions.ts index 6c2b5fd3c..ae6afa99e 100644 --- a/src/features/lightspeed/inlineSuggestions.ts +++ b/src/features/lightspeed/inlineSuggestions.ts @@ -144,11 +144,8 @@ export async function getInlineSuggestionItems( let suggestionMatchType: LIGHTSPEED_SUGGESTION_TYPE | undefined = undefined; - let rhUserHasSeat = + const rhUserHasSeat = await lightSpeedManager.lightSpeedAuthenticationProvider.rhUserHasSeat(); - if (rhUserHasSeat === undefined) { - rhUserHasSeat = false; - } const lineToExtractPrompt = document.lineAt(currentPosition.line - 1); const taskMatchedPattern = @@ -224,7 +221,7 @@ export async function getInlineSuggestionItems( ); if (suggestionMatchType === "MULTI-TASK") { - if (!rhUserHasSeat) { + if (rhUserHasSeat === false) { console.debug( "[inline-suggestions] Multitask suggestions not supported for a non seat user." ); @@ -254,7 +251,7 @@ export async function getInlineSuggestionItems( } if ( suggestionMatchType === "SINGLE-TASK" && - !shouldRequestInlineSuggestions(parsedAnsibleDocument) + !shouldRequestInlineSuggestions(parsedAnsibleDocument, ansibleFileType) ) { return []; } @@ -319,6 +316,7 @@ export async function getInlineSuggestionItems( result.predictions.forEach((prediction) => { let insertText = prediction; insertText = adjustInlineSuggestionIndent(prediction, currentPosition); + insertText = insertText.replace(/^[ \t]+(?=\r?\n)/gm, ""); insertTexts.push(insertText); const inlineSuggestionItem = new vscode.InlineCompletionItem(insertText); @@ -359,7 +357,7 @@ async function requestInlineSuggest( parsedAnsibleDocument: any, documentUri: string, activityId: string, - rhUserHasSeat: boolean, + rhUserHasSeat: boolean | undefined, documentDirPath: string, documentFilePath: string, ansibleFileType: IAnsibleFileType diff --git a/src/features/lightspeed/lightSpeedOAuthProvider.ts b/src/features/lightspeed/lightSpeedOAuthProvider.ts index ed25c43ec..e3db6ce4d 100644 --- a/src/features/lightspeed/lightSpeedOAuthProvider.ts +++ b/src/features/lightspeed/lightSpeedOAuthProvider.ts @@ -584,11 +584,14 @@ export class LightSpeedAuthenticationProvider return userAuth; } - public async rhUserHasSeat(): Promise { + public async rhUserHasSeat(): Promise { const authSession = await this.getLightSpeedAuthSession(); - if (authSession?.rhUserHasSeat) { + if (authSession === undefined) { + return undefined; + } else if (authSession?.rhUserHasSeat) { return true; + } else { + return false; } - return false; } } diff --git a/src/features/lightspeed/utils/data.ts b/src/features/lightspeed/utils/data.ts index 1564d6b64..f61cd4d13 100644 --- a/src/features/lightspeed/utils/data.ts +++ b/src/features/lightspeed/utils/data.ts @@ -24,22 +24,36 @@ import { } from "../../../definitions/lightspeed"; export function shouldRequestInlineSuggestions( - parsedAnsibleDocument: yaml.YAMLMap[] + parsedAnsibleDocument: yaml.YAMLMap[], + ansibleFileType: IAnsibleFileType ): boolean { const lastObject = parsedAnsibleDocument[parsedAnsibleDocument.length - 1]; if (typeof lastObject !== "object") { return false; } - // for the last entry in list check if the inline suggestion - // triggered is in Ansible play context by checking for "hosts" keyword. + const objectKeys = Object.keys(lastObject); + const lastParentKey = objectKeys[objectKeys.length - 1]; + + // check if single-task trigger is in play context or not + if (lastParentKey === "name" && objectKeys.includes("hosts")) { + return false; + } + + // check if single-task trigger is in vars context or not + if (lastParentKey === "vars" || lastParentKey === "vars_files") { + return false; + } + + // for file identified as playbook, check single task trigger in task context if ( - objectKeys[objectKeys.length - 1] === "name" && - objectKeys.includes("hosts") + ansibleFileType === "playbook" && + !["tasks", "pre_tasks", "post_tasks", "handlers"].some((key) => + objectKeys.includes(key) + ) ) { return false; } - return true; } diff --git a/src/interfaces/lightspeed.ts b/src/interfaces/lightspeed.ts index cf1484738..5979089b4 100644 --- a/src/interfaces/lightspeed.ts +++ b/src/interfaces/lightspeed.ts @@ -92,7 +92,6 @@ export interface IContentMatchParams { path: string; license: string; data_source: string; - ansible_type: string; score: number; }