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
This commit is contained in:
Dan Rivera 2025-07-30 12:01:27 -04:00 committed by GitHub
parent 3a37fe4cca
commit c80c4b4180
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 98 additions and 79 deletions

View File

@ -48,21 +48,7 @@
<div>
<div class="has-text-grey is-grid align-items-center linked-block-title">
{{#if backend.icon}}
<Hds::TooltipButton
@text={{if
backend.isSupportedBackend
(concat
(get (engines-display-data backend.type) "displayName")
(if
(eq backend.version 2)
" version 2"
(if (and (eq backend.version 1) (eq backend.type "kv")) " version 1" "")
)
)
"The UI only supports configuration views for these secret engines. The CLI must be used to manage other engine resources."
}}
aria-label="Type of backend"
>
<Hds::TooltipButton aria-label="Type of backend" @text={{this.generateToolTipText backend}}>
<Icon @name={{backend.icon}} class="has-text-grey-light" />
</Hds::TooltipButton>
{{/if}}
@ -99,13 +85,15 @@
@hasChevron={{false}}
data-test-popup-menu-trigger
/>
<dd.Interactive
@route={{backend.backendConfigurationLink}}
@model={{backend.id}}
data-test-popup-menu="view-configuration"
>
View configuration
</dd.Interactive>
{{#if (not-eq (get (engines-display-data backend.type) "type") "generic")}}
<dd.Interactive
@route={{backend.backendConfigurationLink}}
@model={{backend.id}}
data-test-popup-menu="view-configuration"
>
View configuration
</dd.Interactive>
{{/if}}
{{#if (not-eq backend.type "cubbyhole")}}
<dd.Interactive
@color="critical"

View File

@ -13,6 +13,7 @@ import type FlashMessageService from 'vault/services/flash-messages';
import type SecretsEngineResource from 'vault/resources/secrets/engine';
import type ApiService from 'vault/services/api';
import type RouterService from '@ember/routing/router-service';
import engineDisplayData from 'vault/helpers/engines-display-data';
/**
* @module SecretEngineList handles the display of the list of secret engines, including the filtering.
@ -67,6 +68,28 @@ export default class SecretEngineList extends Component<Args> {
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);

View File

@ -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;
}

View File

@ -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}`;

View File

@ -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`<SecretEngine::List @secretEngines={{this.secretEngineModels}} />`);
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`<SecretEngine::List @secretEngines={{this.secretEngineModels}} />`);
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`<SecretEngine::List @secretEngines={{this.secretEngineModels}} />`);
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`<SecretEngine::List @secretEngines={{this.secretEngineModels}}/>`);
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`<SecretEngine::List @secretEngines={{this.secretEngineModels}} />`);
assert