diff --git a/src/definitions/lightspeed.ts b/src/definitions/lightspeed.ts index 60ab48918..e04567e44 100644 --- a/src/definitions/lightspeed.ts +++ b/src/definitions/lightspeed.ts @@ -65,6 +65,7 @@ export const LIGHTSPEED_SUGGESTION_COMPLETION_URL = `${LIGHTSPEED_API_VERSION}/a export const LIGHTSPEED_SUGGESTION_FEEDBACK_URL = `${LIGHTSPEED_API_VERSION}/ai/feedback/`; export const LIGHTSPEED_SUGGESTION_CONTENT_MATCHES_URL = `${LIGHTSPEED_API_VERSION}/ai/contentmatches/`; export const LIGHTSPEED_ME_AUTH_URL = `/api/${LIGHTSPEED_API_VERSION}/me/`; +export const LIGHTSPEED_MARKDOWN_ME_AUTH_URL = `/api/${LIGHTSPEED_API_VERSION}/me/summary/`; export const LIGHTSPEED_FEEDBACK_FORM_URL = "https://red.ht/ansible-ai-feedback"; diff --git a/src/features/lightspeed/explorerWebviewViewProvider.ts b/src/features/lightspeed/explorerWebviewViewProvider.ts index fd10bcbf7..e1371b07e 100644 --- a/src/features/lightspeed/explorerWebviewViewProvider.ts +++ b/src/features/lightspeed/explorerWebviewViewProvider.ts @@ -14,7 +14,6 @@ import { } from "./utils/explorerView"; import { LightspeedUser } from "./lightspeedUser"; -import { getLoggedInUserDetails } from "./utils/webUtils"; import { isPlaybook } from "./playbookExplanation"; export class LightspeedExplorerWebviewViewProvider @@ -69,22 +68,14 @@ export class LightspeedExplorerWebviewViewProvider } private async _getWebviewContent(webview: Webview, extensionUri: Uri) { - const userDetails = - await this.lightspeedAuthenticatedUser.getLightspeedUserDetails(false); - if (userDetails) { - const sessionInfo = getLoggedInUserDetails(userDetails); - const userName = userDetails.displayNameWithUserType; - const userType = sessionInfo.userInfo?.userType || ""; - const userRole = - sessionInfo.userInfo?.role !== undefined - ? sessionInfo.userInfo?.role - : ""; + const content = + await this.lightspeedAuthenticatedUser.getLightspeedUserContent(); + + if (content !== "undefined") { return getWebviewContentWithActiveSession( webview, extensionUri, - userName, - userType, - userRole, + String(content), this.hasPlaybookOpened(), ); } else { diff --git a/src/features/lightspeed/lightspeedUser.ts b/src/features/lightspeed/lightspeedUser.ts index cd994908e..96d4de757 100644 --- a/src/features/lightspeed/lightspeedUser.ts +++ b/src/features/lightspeed/lightspeedUser.ts @@ -4,8 +4,10 @@ import { ANSIBLE_LIGHTSPEED_AUTH_ID, getBaseUri, getUserTypeLabel, + getLoggedInUserDetails, } from "./utils/webUtils"; import { + LIGHTSPEED_MARKDOWN_ME_AUTH_URL, LIGHTSPEED_ME_AUTH_URL, LightSpeedCommands, } from "../../definitions/lightspeed"; @@ -17,6 +19,7 @@ import { isSupportedCallback, } from "./lightSpeedOAuthProvider"; import { Log } from "../../utils/logger"; +import * as marked from "marked"; export class LightspeedAccessDenied extends Error { constructor(message: string) { @@ -54,6 +57,7 @@ export class LightspeedUser { private _userDetails: LightspeedUserDetails | undefined; private _logger: Log; private _extensionHost: ExtensionHostType; + private _markdownUserDetails: string | undefined; constructor( private readonly context: vscode.ExtensionContext, @@ -136,6 +140,45 @@ export class LightspeedUser { } } + public async getUserInfoFromMarkdown(token: string) { + this._logger.info( + "[ansible-lightspeed-user] Sending request for logged-in user info...", + ); + + try { + const { data } = await axios.get( + `${getBaseUri(this._settingsManager)}${LIGHTSPEED_MARKDOWN_ME_AUTH_URL}`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); + + const markdownData = marked.parse(data.content) as string; + + return markdownData; + } catch (error) { + if ( + axios.isAxiosError(error) && + error.response && + error.response.status === 401 + ) { + throw new LightspeedAccessDenied(error.message); + } else if (axios.isAxiosError(error)) { + this._logger.error( + `[ansible-lightspeed-user] error message: ${error.message}`, + ); + throw new Error(error.message); + } else { + this._logger.error( + `[ansible-lightspeed-user] unexpected error: ${error}`, + ); + throw new Error("An unexpected error occurred"); + } + } + } + public async getAuthProviderOrder() { // NOTE: We can't gate this check on if this extension is active, // because it only activates on an authentication request. @@ -234,6 +277,16 @@ export class LightspeedUser { ); this._session = session; + let markdownUserInfo: string = ""; + try { + markdownUserInfo = await this.getUserInfoFromMarkdown( + session.accessToken, + ); + } catch (error) { + markdownUserInfo = ""; + } + this._markdownUserDetails = markdownUserInfo; + const displayName = userinfo.external_username || userinfo.username || ""; const userTypeLabel = getUserTypeLabel( @@ -271,12 +324,14 @@ export class LightspeedUser { this._session = undefined; this._userDetails = undefined; + this._markdownUserDetails = undefined; this._userType = undefined; } public async refreshLightspeedUser() { this._session = undefined; this._userDetails = undefined; + this._markdownUserDetails = undefined; await this.setLightspeedUser(false); } @@ -300,6 +355,59 @@ export class LightspeedUser { return this._userDetails; } + public async getMarkdownLightspeedUserDetails( + createIfNone: boolean, + useProviderType: AuthProviderType | undefined = undefined, + ) { + // Ensure we don't try to get a lightspeed auth session when the provider is not initialized + if (!this._settingsManager.settings.lightSpeedService.enabled) { + return undefined; + } + if ( + this._markdownUserDetails && + (!useProviderType || useProviderType === this._userType) + ) { + return this._markdownUserDetails; + } + + await this.setLightspeedUser(createIfNone, useProviderType); + + return this._markdownUserDetails; + } + + public async getLightspeedUserContent() { + // Ensure we don't try to get a lightspeed auth session when the provider is not initialized + if (!this._settingsManager.settings.lightSpeedService.enabled) { + return undefined; + } + + const markdownUserDetails = + await this.getMarkdownLightspeedUserDetails(false); + const userDetails = await this.getLightspeedUserDetails(false); + + let content = "undefined"; + if (markdownUserDetails !== "") { + content = String(markdownUserDetails); + } else { + if (userDetails) { + const sessionInfo = getLoggedInUserDetails(userDetails); + const userName = userDetails.displayNameWithUserType; + const userType = sessionInfo.userInfo?.userType || ""; + const userRole = + sessionInfo.userInfo?.role !== undefined + ? sessionInfo.userInfo?.role + : ""; + content = ` +

Logged in as: ${userName}

+

User Type: ${userType}

+ ${userRole ? "Role: " + userRole : ""} + `; + } + } + + return content; + } + public async rhUserHasSeat(): Promise { const userDetails = await this.getLightspeedUserDetails(false); diff --git a/src/features/lightspeed/utils/explorerView.ts b/src/features/lightspeed/utils/explorerView.ts index 19e1349e6..60314258c 100644 --- a/src/features/lightspeed/utils/explorerView.ts +++ b/src/features/lightspeed/utils/explorerView.ts @@ -52,9 +52,7 @@ export function getWebviewContentWithLoginForm( export function getWebviewContentWithActiveSession( webview: Webview, extensionUri: Uri, - userName: string, - userType: string, - userRole: string, + content: string, has_playbook_opened: boolean, ) { const webviewUri = getUri(webview, extensionUri, [ @@ -98,9 +96,7 @@ export function getWebviewContentWithActiveSession(
- Logged in as: ${userName}
- User Type: ${userType}
- ${userRole ? "Role: " + userRole : ""} + ${content} ${explainForm}
diff --git a/test/mockLightspeedServer/meMarkdown.ts b/test/mockLightspeedServer/meMarkdown.ts new file mode 100644 index 000000000..a726f7627 --- /dev/null +++ b/test/mockLightspeedServer/meMarkdown.ts @@ -0,0 +1,7 @@ +// import { options } from "./server"; +export function meMarkdown() { + return { + content: + "Logged in as: ONE_CLICK_USER (unlicensed)\n\n User Type: Unlicensed", + }; +} diff --git a/test/mockLightspeedServer/server.ts b/test/mockLightspeedServer/server.ts index de1e5fd3b..b8c722dea 100644 --- a/test/mockLightspeedServer/server.ts +++ b/test/mockLightspeedServer/server.ts @@ -12,6 +12,7 @@ import morgan from "morgan"; import fs from "fs"; import path from "path"; import yargs from "yargs"; +import { meMarkdown } from "./meMarkdown"; // eslint-disable-next-line @typescript-eslint/no-explicit-any export let options: any = readOptions(process.argv.splice(2)); @@ -100,6 +101,10 @@ export default class Server { return res.send(me()); }); + app.get(`${API_ROOT}/me/summary`, (req, res) => { + return res.send(meMarkdown()); + }); + app.get("/o/authorize", (req: { query: { redirect_uri: string } }, res) => { logger.info(req.query); const redirectUri = decodeURIComponent(req.query.redirect_uri); diff --git a/test/testScripts/lightspeed/testLightspeedUser.test.ts b/test/testScripts/lightspeed/testLightspeedUser.test.ts index f20daa190..0ab1d1e31 100644 --- a/test/testScripts/lightspeed/testLightspeedUser.test.ts +++ b/test/testScripts/lightspeed/testLightspeedUser.test.ts @@ -66,6 +66,44 @@ function testGetUserInfo() { }); } +function testGetUserInfoFromMarkdown() { + describe("Test LightspeedUser.getUserInfoFromMarkdown", function () { + it("Returns /me/summary endpoint results with valid access token", async function () { + const accessToken = + (await lightSpeedManager.lightspeedAuthenticatedUser.getLightspeedUserAccessToken()) as string; + const markdownUserInfo: string = + await lightSpeedManager.lightspeedAuthenticatedUser.getUserInfoFromMarkdown( + accessToken, + ); + + assert.isNotNull(markdownUserInfo); + }); + }); +} + +function testGetMarkdownLightspeedUserDetails() { + describe("test LightspeedUser.getMarkdownLightspeedUserDetails", function () { + it("Returns formatted content from /me/summary endpoint", async function () { + const markdownUserDetails = + await lightSpeedManager.lightspeedAuthenticatedUser.getMarkdownLightspeedUserDetails( + false, + ); + + assert.isNotNull(markdownUserDetails); + }); + }); +} + +function testGetLightspeedUserContent() { + describe("test LightspeedUser.getLightspeedUserContent", function () { + it("Returns proper HTML markdown based on whether /me/summary is available", async function () { + const content = + await lightSpeedManager.lightspeedAuthenticatedUser.getLightspeedUserContent(); + assert.isNotNull(content); + }); + }); +} + function testGetAuthProviderOrder() { describe("Test LightspeedUser.getAuthProviderOrder", function () { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -234,6 +272,9 @@ export function testLightspeedUser(): void { testIsSupportedCallback(); testIsLightspeedUserAuthProviderType(); testGetUserInfo(); + testGetUserInfoFromMarkdown(); + testGetMarkdownLightspeedUserDetails(); + testGetLightspeedUserContent(); testGetAuthProviderOrder(); testRedHatSignInCommand(); }