From c80c4b41803b9d8b07f09006b3dc071aa3b93e34 Mon Sep 17 00:00:00 2001 From: Dan Rivera Date: Wed, 30 Jul 2025 12:01:27 -0400 Subject: [PATCH] UI: Fix externally mounted engine tooltip bug (#31382) * adding clause for external plugin to tooltip * fixing mouseover event * fix check * pulling object out of list * fix used parameter * adding comments * fix * fixing tests * adding 1 more test * update tooltip func, remove excess params, update tests --- ui/app/components/secret-engine/list.hbs | 32 ++++------ ui/app/components/secret-engine/list.ts | 23 ++++++++ ui/app/helpers/engines-display-data.ts | 10 ++++ .../secret-engine-list-view-test.js | 53 +---------------- ui/tests/integration/components/list-test.js | 59 +++++++++++++++++-- 5 files changed, 98 insertions(+), 79 deletions(-) diff --git a/ui/app/components/secret-engine/list.hbs b/ui/app/components/secret-engine/list.hbs index 0c5b77413e..8534ffa6f1 100644 --- a/ui/app/components/secret-engine/list.hbs +++ b/ui/app/components/secret-engine/list.hbs @@ -48,21 +48,7 @@
{{#if backend.icon}} - + {{/if}} @@ -99,13 +85,15 @@ @hasChevron={{false}} data-test-popup-menu-trigger /> - - View configuration - + {{#if (not-eq (get (engines-display-data backend.type) "type") "generic")}} + + View configuration + + {{/if}} {{#if (not-eq backend.type "cubbyhole")}} { return sortedBackends; } + generateToolTipText = (backend: SecretsEngineResource) => { + const displayData = engineDisplayData(backend.type); + + if (!displayData) { + return; + } else if (backend.isSupportedBackend) { + if (backend.type === 'kv') { + // If the backend is a KV engine, include the version in the tooltip. + return `${displayData.displayName} version ${backend.version}`; + } else { + return `${displayData.displayName}`; + } + } else if (displayData.type === 'generic') { + // If a mounted engine type doesn't match any known type, the type is returned as 'generic' and set this tooltip. + // Handles issue when a user externally mounts an engine that doesn't follow the expected naming conventions for what's in the binary, despite being a valid engine. + return 'This plugin is not supported by the UI. Please use the CLI to manage this engine.'; + } else { + // If the engine type is recognized but not supported, we only show configuration view and set this tooltip. + return 'The UI only supports configuration views for these secret engines. The CLI must be used to manage other engine resources.'; + } + }; + // Filtering & searching get secretEngineArrayByType() { const arrayOfAllEngineTypes = this.sortedDisplayableBackends.map((modelObject) => modelObject.engineType); diff --git a/ui/app/helpers/engines-display-data.ts b/ui/app/helpers/engines-display-data.ts index 2de527e49a..a1548804e4 100644 --- a/ui/app/helpers/engines-display-data.ts +++ b/ui/app/helpers/engines-display-data.ts @@ -23,5 +23,15 @@ import { ALL_ENGINES } from 'vault/utils/all-engines-metadata'; */ export default function engineDisplayData(methodType: string) { const engine = ALL_ENGINES?.find((t) => t.type === methodType); + if (!engine && methodType) { + // Fallback to a generic engine if no match found but type is provided + return { + displayName: 'Generic plugin', + type: 'generic', + glyph: 'lock', + mountCategory: ['secret', 'auth'], + }; + } + return engine; } diff --git a/ui/tests/acceptance/secret-engine-list-view-test.js b/ui/tests/acceptance/secret-engine-list-view-test.js index 3a28b768fb..04e906a6cd 100644 --- a/ui/tests/acceptance/secret-engine-list-view-test.js +++ b/ui/tests/acceptance/secret-engine-list-view-test.js @@ -3,7 +3,7 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { click, fillIn, currentRouteName, visit, currentURL, triggerEvent } from '@ember/test-helpers'; +import { click, fillIn, currentRouteName, visit, currentURL } from '@ember/test-helpers'; import { selectChoose } from 'ember-power-select/test-support'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -86,57 +86,6 @@ module('Acceptance | secret-engine list view', function (hooks) { await runCmd(deleteEngineCmd('aws')); }); - test('hovering over the icon of an unsupported engine shows unsupported tooltip', async function (assert) { - await visit('/vault/secrets'); - await page.enableEngine(); - await click(MOUNT_BACKEND_FORM.mountType('nomad')); - await click(GENERAL.submitButton); - - await selectChoose(GENERAL.searchSelect.trigger('filter-by-engine-type'), 'nomad'); - - await triggerEvent('.hds-tooltip-button', 'mouseenter'); - assert - .dom('.hds-tooltip-container') - .hasText( - 'The UI only supports configuration views for these secret engines. The CLI must be used to manage other engine resources.', - 'shows tooltip text for unsupported engine' - ); - // cleanup - await runCmd(deleteEngineCmd('nomad')); - }); - - test('hovering over the icon of a supported engine shows engine name', async function (assert) { - await visit('/vault/secrets'); - await page.enableEngine(); - await click(MOUNT_BACKEND_FORM.mountType('ssh')); - await click(GENERAL.submitButton); - await click(GENERAL.breadcrumbLink('Secrets')); - - await selectChoose(GENERAL.searchSelect.trigger('filter-by-engine-type'), 'ssh'); - await triggerEvent('.hds-tooltip-button', 'mouseenter'); - assert.dom('.hds-tooltip-container').hasText('SSH', 'shows tooltip for SSH without version'); - - // cleanup - await runCmd(deleteEngineCmd('ssh')); - }); - - test('hovering over the icon of a kv engine shows engine name and version', async function (assert) { - await visit('/vault/secrets'); - - await page.enableEngine(); - await click(MOUNT_BACKEND_FORM.mountType('kv')); - await fillIn(GENERAL.inputByAttr('path'), `kv-${this.uid}`); - await click(GENERAL.submitButton); - await click(GENERAL.breadcrumbLink('Secrets')); - - await selectChoose(GENERAL.searchSelect.trigger('filter-by-engine-name'), `kv-${this.uid}`); - await triggerEvent('.hds-tooltip-button', 'mouseenter'); - assert.dom('.hds-tooltip-container').hasText('KV version 2', 'shows tooltip for kv version 2'); - - // cleanup - await runCmd(deleteEngineCmd('kv')); - }); - test('enterprise: cannot view list without permissions inside a namespace', async function (assert) { this.namespace = `ns-${this.uid}`; const enginePath1 = `kv-t1-${this.uid}`; diff --git a/ui/tests/integration/components/list-test.js b/ui/tests/integration/components/list-test.js index b1debf2426..107ed8ab49 100644 --- a/ui/tests/integration/components/list-test.js +++ b/ui/tests/integration/components/list-test.js @@ -5,14 +5,14 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'vault/tests/helpers'; -import { render, click, find, findAll } from '@ember/test-helpers'; +import { render, click, find, findAll, triggerEvent } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { v4 as uuidv4 } from 'uuid'; import sinon from 'sinon'; import { setupMirage } from 'ember-cli-mirage/test-support'; import { overrideResponse } from 'vault/tests/helpers/stubs'; import { clickTrigger } from 'ember-power-select/test-support/helpers'; - +import { selectChoose } from 'ember-power-select/test-support'; import { createSecretsEngine } from 'vault/tests/helpers/secret-engine/secret-engine-helpers'; import { SECRET_ENGINE_SELECTORS as SES } from 'vault/tests/helpers/secret-engine/secret-engine-selectors'; import { GENERAL } from 'vault/tests/helpers/general-selectors'; @@ -33,18 +33,19 @@ module('Integration | Component | secret-engine/list', function (hooks) { this.flashSuccessSpy = sinon.spy(this.flashMessages, 'success'); this.flashDangerSpy = sinon.spy(this.flashMessages, 'danger'); this.uid = uuidv4(); - // generate a model of cubbyhole, kv2, and nomad + // generate a model of cubbyhole, kv, and nomad this.secretEngineModels = [ createSecretsEngine(undefined, 'cubbyhole', 'cubbyhole-test'), - createSecretsEngine(undefined, 'kv', 'kv2-test'), + createSecretsEngine(undefined, 'kv', 'kv-test'), createSecretsEngine(undefined, 'aws', 'aws-1'), createSecretsEngine(undefined, 'aws', 'aws-2'), createSecretsEngine(undefined, 'nomad', 'nomad-test'), + createSecretsEngine(undefined, 'badType', 'external-test'), ]; }); test('it allows you to disable an engine', async function (assert) { - const enginePath = 'kv2-test'; + const enginePath = 'kv-test'; this.server.delete(`sys/mounts/${enginePath}`, () => { assert.true(true, 'Request is made to delete engine'); return overrideResponse(204); @@ -63,6 +64,54 @@ module('Integration | Component | secret-engine/list', function (hooks) { ); }); + test('hovering over the icon of an external unrecognized engine type sets unrecognized tooltip text', async function (assert) { + await render(hbs``); + + await selectChoose(GENERAL.searchSelect.trigger('filter-by-engine-name'), 'external-test'); + await triggerEvent('.hds-tooltip-button', 'mouseenter'); + + assert + .dom('.hds-tooltip-container') + .hasText( + 'This plugin is not supported by the UI. Please use the CLI to manage this engine.', + 'shows tooltip text for unsupported engine' + ); + }); + + test('hovering over the icon of an unsupported engine sets unsupported tooltip text', async function (assert) { + await render(hbs``); + + await selectChoose(GENERAL.searchSelect.trigger('filter-by-engine-type'), 'nomad'); + await triggerEvent('.hds-tooltip-button', 'mouseenter'); + + assert + .dom('.hds-tooltip-container') + .hasText( + 'The UI only supports configuration views for these secret engines. The CLI must be used to manage other engine resources.', + 'shows tooltip text for unsupported engine' + ); + }); + + test('hovering over the icon of a supported engine sets engine name as tooltip', async function (assert) { + await render(hbs``); + await selectChoose(GENERAL.searchSelect.trigger('filter-by-engine-name'), 'aws-1'); + + await triggerEvent('.hds-tooltip-button', 'mouseenter'); + + assert.dom('.hds-tooltip-container').hasText('AWS', 'shows tooltip text for supported engine with name'); + }); + + test('hovering over the icon of a kv engine shows engine name and version', async function (assert) { + await render(hbs``); + + await selectChoose(GENERAL.searchSelect.trigger('filter-by-engine-name'), `kv-test`); + + await triggerEvent('.hds-tooltip-button', 'mouseenter'); + assert + .dom('.hds-tooltip-container') + .hasText('KV version 1', 'shows tooltip text for kv engine with version'); + }); + test('it adds disabled css styling to unsupported secret engines', async function (assert) { await render(hbs``); assert