UI: Add Skip password rotation field on DB connection creation (#29820)

* adding skip flag to db creation

* update field name & add default val change to static role

* transfer both fields to be toggle buttons

* add changelog

* test updates

* leftover

* test fixes

* fix tests pt2

* test pt3

* adding conditional to disable role type selection

* adding alert when overriding db default

* cleanup

* pr comments pt1 -  updates to logic, adding empty state  & form field test

* moving empty state placement

* updating form field logic for subtext, test fixes

* restructuring a bit to use a getter / eliminate separate function

* update

* fix typo, bring back tests

* fixes and cleanup
This commit is contained in:
Dan Rivera 2025-03-12 12:44:32 -04:00 committed by GitHub
parent 360e2f9d83
commit 404356a805
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 187 additions and 63 deletions

3
changelog/29820.txt Normal file
View file

@ -0,0 +1,3 @@
```release-note:improvement
ui/database: Adding input field for setting skip static role password rotation for database connection config, updating static role skip field to use toggle button
```

View file

@ -46,7 +46,7 @@ export default class DatabaseRoleEdit extends Component {
isValid() {
const { isValid, state } = this.args.model.validate();
this.modelValidations = isValid ? null : state;
this.invalidFormAlert = 'There was an error submitting this form.';
this.invalidFormAlert = isValid ? '' : 'There was an error submitting this form.';
return isValid;
}
@ -67,7 +67,8 @@ export default class DatabaseRoleEdit extends Component {
}
return warnings;
}
get databaseType() {
get databaseParams() {
const backend = this.args.model?.backend;
const dbs = this.args.model?.database || [];
if (!backend || dbs.length === 0) {
@ -75,7 +76,10 @@ export default class DatabaseRoleEdit extends Component {
}
return this.store
.queryRecord('database/connection', { id: dbs[0], backend })
.then((record) => record.plugin_name)
.then(({ plugin_name, skip_static_role_rotation_import }) => ({
plugin_name,
skip_static_role_rotation_import,
}))
.catch(() => null);
}
@ -110,6 +114,7 @@ export default class DatabaseRoleEdit extends Component {
this.resetErrors();
const { mode, model } = this.args;
if (!this.isValid()) return;
if (mode === 'create') {
model.id = model.name;
const path = model.type === 'static' ? 'static-roles' : 'roles';

View file

@ -18,25 +18,41 @@ import { getStatementFields, getRoleFields } from '../utils/model-helpers/databa
* @param {object} model - ember data model which should be updated on change
* @param {string} [roleType] - role type controls which attributes are shown
* @param {string} [mode=create] - mode of the form (eg. create or edit)
* @param {string} [dbType=default] - type of database, eg 'mongodb-database-plugin'
* @param {object} dbParams - holds database config values, { plugin_name: string [eg 'mongodb-database-plugin'], skip_static_role_rotation_import: boolean }
*/
export default class DatabaseRoleSettingForm extends Component {
get dbConfig() {
return this.args.dbParams;
}
get settingFields() {
const dbValues = this.args.dbParams;
if (!this.args.roleType) return null;
const dbValidFields = getRoleFields(this.args.roleType);
return this.args.attrs.filter((a) => {
// Sets default value for skip_import_rotation based on parent db config value
if (a.name === 'skip_import_rotation' && this.args.mode === 'create') {
a.options.defaultValue = dbValues?.skip_static_role_rotation_import;
}
return dbValidFields.includes(a.name);
});
}
get statementFields() {
const type = this.args.roleType;
const plugin = this.args.dbType;
if (!type) return null;
const dbValidFields = getStatementFields(type, plugin);
const dbValidFields = getStatementFields(type, this.dbConfig ? this.dbConfig.plugin_name : null);
return this.args.attrs.filter((a) => {
return dbValidFields.includes(a.name);
});
}
get isOverridden() {
if (this.args.mode !== 'create' || !this.dbConfig) return null;
const dbSkip = this.dbConfig.skip_static_role_rotation_import;
const staticVal = this.args.model.get('skip_import_rotation');
return this.args.mode === 'create' && dbSkip !== staticVal;
}
}

View file

@ -200,6 +200,14 @@ export default Model.extend({
}),
// ENTERPRISE ONLY
skip_static_role_rotation_import: attr({
editType: 'toggleButton',
label: 'Skip initial rotation on static roles',
helperTextDisabled: 'Vault automatically rotates static roles upon their initial creation.',
helperTextEnabled: 'Vault will not automatically rotate static role passwords upon creation.',
defaultValue: false,
}),
self_managed: attr('boolean', {
subText:
'Allows onboarding static roles with a rootless connection configuration. Mutually exclusive with username and password. If true, will force verify_connection to be false.',

View file

@ -8,6 +8,7 @@ import { getRoleFields } from 'vault/utils/model-helpers/database-helpers';
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
import { withModelValidations } from 'vault/decorators/model-validations';
const validations = {
name: [{ type: 'presence', message: 'Role name is required.' }],
database: [{ type: 'presence', message: 'Database is required.' }],
type: [{ type: 'presence', message: 'Type is required.' }],
username: [
@ -69,9 +70,10 @@ export default class RoleModel extends Model {
rotation_period;
@attr({
label: 'Skip initial rotation',
editType: 'boolean',
defaultValue: false,
subText: 'When unchecked, Vault automatically rotates the password upon creation.',
editType: 'toggleButton',
defaultValue: false, // this defaultValue will be set in database-role-setting-form.js based on parent database value
helperTextDisabled: 'Vault will rotate password for this static role on creation.',
helperTextEnabled: "Vault will not rotate this role's password on creation.",
})
skip_import_rotation;
@attr('array', {

View file

@ -106,7 +106,23 @@
{{#if (eq @mode "edit")}}
<ReadonlyFormField @attr={{attr}} @value={{get @model attr.name}} />
{{else if (not-eq attr.options.readOnly true)}}
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} @modelValidations={{this.modelValidations}} />
{{#if (eq attr.name "type")}}
{{#if @model.database}}
<FormField
data-test-field={{true}}
@attr={{attr}}
@model={{@model}}
@modelValidations={{this.modelValidations}}
/>
{{/if}}
{{else}}
<FormField
data-test-field={{true}}
@attr={{attr}}
@model={{@model}}
@modelValidations={{this.modelValidations}}
/>
{{/if}}
{{! TODO: If database && !updateDB show warning }}
{{#if (get this.warningMessages attr.name)}}
<Hds::Alert @type="inline" @color="warning" class="has-top-margin-negative-s has-bottom-margin-s" as |A|>
@ -116,14 +132,21 @@
{{/if}}
{{/if}}
{{/each}}
<DatabaseRoleSettingForm
@attrs={{@model.roleSettingAttrs}}
@roleType={{@model.type}}
@model={{@model}}
@mode={{@mode}}
@dbType={{await this.databaseType}}
@modelValidations={{this.modelValidations}}
/>
{{#if @model.database}}
<DatabaseRoleSettingForm
@attrs={{@model.roleSettingAttrs}}
@roleType={{@model.type}}
@model={{@model}}
@mode={{@mode}}
@dbParams={{await this.databaseParams}}
@modelValidations={{this.modelValidations}}
/>
{{else}}
<EmptyState
@title="No database connection selected"
@message="Choose a connection to be able to configure a role type."
/>
{{/if}}
<div class="field is-fullwidth box is-bottomless">
<Hds::ButtonSet>
<Hds::Button

View file

@ -8,10 +8,16 @@
{{#if this.settingFields}}
<div class="form-section">
{{#each this.settingFields as |attr|}}
{{#if (and (eq @mode "edit") (eq attr.name "username"))}}
{{#if (and (eq @mode "edit") (includes attr.name (array "skip_import_rotation" "username")))}}
<ReadonlyFormField @attr={{attr}} @value={{get @model attr.name}} />
{{else}}
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} @modelValidations={{@modelValidations}} />
{{#if (and (eq attr.name "skip_import_rotation") this.isOverridden)}}
<Hds::Alert @type="inline" @color="warning" class="has-top-margin-negative-s" as |A|>
<A.Title>Warning</A.Title>
<A.Description>This will override the connection default for this role.</A.Description>
</Hds::Alert>
{{/if}}
{{/if}}
{{/each}}

View file

@ -22,6 +22,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
{ attr: 'tls_server_name', group: 'pluginConfig' },
{ attr: 'insecure', group: 'pluginConfig' },
{ attr: 'username_template', group: 'pluginConfig' },
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
],
},
{
@ -37,6 +38,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
{ attr: 'password', group: 'pluginConfig', show: false },
{ attr: 'write_concern', group: 'pluginConfig' },
{ attr: 'username_template', group: 'pluginConfig' },
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
{ attr: 'tls', group: 'pluginConfig', subgroup: 'TLS options' },
{ attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' },
{ attr: 'root_rotation_statements', group: 'statements' },
@ -57,6 +59,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
{ attr: 'max_open_connections', group: 'pluginConfig' },
{ attr: 'max_idle_connections', group: 'pluginConfig' },
{ attr: 'max_connection_lifetime', group: 'pluginConfig' },
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
{ attr: 'root_rotation_statements', group: 'statements' },
],
},
@ -75,6 +78,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
{ attr: 'max_idle_connections', group: 'pluginConfig' },
{ attr: 'max_connection_lifetime', group: 'pluginConfig' },
{ attr: 'username_template', group: 'pluginConfig' },
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
{ attr: 'tls', group: 'pluginConfig', subgroup: 'TLS options' },
{ attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' },
{ attr: 'root_rotation_statements', group: 'statements' },
@ -95,6 +99,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
{ attr: 'max_idle_connections', group: 'pluginConfig' },
{ attr: 'max_connection_lifetime', group: 'pluginConfig' },
{ attr: 'username_template', group: 'pluginConfig' },
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
{ attr: 'tls', group: 'pluginConfig', subgroup: 'TLS options' },
{ attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' },
{ attr: 'root_rotation_statements', group: 'statements' },
@ -115,6 +120,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
{ attr: 'max_idle_connections', group: 'pluginConfig' },
{ attr: 'max_connection_lifetime', group: 'pluginConfig' },
{ attr: 'username_template', group: 'pluginConfig' },
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
{ attr: 'tls', group: 'pluginConfig', subgroup: 'TLS options' },
{ attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' },
{ attr: 'root_rotation_statements', group: 'statements' },
@ -135,6 +141,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
{ attr: 'max_idle_connections', group: 'pluginConfig' },
{ attr: 'max_connection_lifetime', group: 'pluginConfig' },
{ attr: 'username_template', group: 'pluginConfig' },
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
{ attr: 'tls', group: 'pluginConfig', subgroup: 'TLS options' },
{ attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' },
{ attr: 'root_rotation_statements', group: 'statements' },
@ -155,6 +162,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
{ attr: 'max_idle_connections', group: 'pluginConfig' },
{ attr: 'max_connection_lifetime', group: 'pluginConfig' },
{ attr: 'username_template', group: 'pluginConfig' },
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
{ attr: 'root_rotation_statements', group: 'statements' },
],
},
@ -180,6 +188,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
{ attr: 'disable_escaping', group: 'pluginConfig' },
{ attr: 'root_rotation_statements', group: 'statements' },
{ attr: 'self_managed', group: 'pluginConfig', isEnterprise: true },
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
{ attr: 'private_key', group: 'pluginConfig', subgroup: 'TLS options' },
{ attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' },
{ attr: 'tls_certificate', group: 'pluginConfig', subgroup: 'TLS options' },

View file

@ -187,17 +187,37 @@
@value={{or (get @model this.valuePath) @attr.options.defaultValue}}
@onChange={{this.onChangeWithEvent}}
/>
{{else if (eq @attr.options.editType "toggleButton")}}
{{! Togglable Input }}
<Toggle
@name="toggle-{{@attr.name}}"
@onChange={{this.toggleButton}}
@checked={{this.toggleInputEnabled}}
data-test-toggle={{@attr.name}}
disabled={{this.disabled}}
>
<span class="has-text-weight-bold is-large">{{this.labelString}}</span><br />
<div class="description has-text-grey" data-test-toggle-subtext>
<span>
{{#if this.toggleInputEnabled}}
{{@attr.options.helperTextEnabled}}
{{else}}
{{@attr.options.helperTextDisabled}}
{{/if}}
</span>
</div>
</Toggle>
{{else if (eq @attr.options.editType "optionalText")}}
{{! Togglable Text Input }}
<Toggle
@name="show-{{@attr.name}}"
@onChange={{this.toggleShow}}
@checked={{this.showInput}}
@onChange={{this.toggleTextShow}}
@checked={{this.showToggleTextInput}}
data-test-toggle={{@attr.name}}
>
<span class="ttl-picker-label is-large">{{this.labelString}}</span><br />
<div class="description has-text-grey">
{{#if this.showInput}}
{{#if this.showToggleTextInput}}
<span>
{{@attr.options.subText}}
{{#if @attr.options.docLink}}
@ -219,7 +239,7 @@
</span>
{{/if}}
</div>
{{#if this.showInput}}
{{#if this.showToggleTextInput}}
<input
data-test-input={{@attr.name}}
id={{@attr.name}}

View file

@ -63,8 +63,10 @@ export default class FormFieldComponent extends Component {
'searchSelect',
'stringArray',
'ttl',
'toggleButton',
];
@tracked showInput = false;
@tracked showToggleTextInput = false;
@tracked toggleInputEnabled = false;
constructor() {
super(...arguments);
@ -75,7 +77,8 @@ export default class FormFieldComponent extends Component {
valuePath.toLowerCase() !== 'id'
);
const modelValue = model[valuePath];
this.showInput = !!modelValue;
this.showToggleTextInput = !!modelValue;
this.toggleInputEnabled = !!modelValue;
}
get hasRadioSubText() {
@ -177,14 +180,19 @@ export default class FormFieldComponent extends Component {
}
}
@action
toggleShow() {
const value = !this.showInput;
this.showInput = value;
toggleTextShow() {
const value = !this.showToggleTextInput;
this.showToggleTextInput = value;
if (!value) {
this.setAndBroadcast(null);
}
}
@action
toggleButton() {
this.toggleInputEnabled = !this.toggleInputEnabled;
this.setAndBroadcast(this.toggleInputEnabled);
}
@action
handleKeyUp(maybeEvent) {
const value = typeof maybeEvent === 'object' ? maybeEvent.target.value : maybeEvent;
if (!this.args.onKeyUp) {

View file

@ -574,13 +574,13 @@ module('Acceptance | secrets/database/*', function (hooks) {
await rolePage.name('bar');
assert
.dom('[data-test-component="empty-state"]')
.exists({ count: 2 }, 'Two empty states exist before selections made');
.exists({ count: 1 }, 'One empty state exists before database selection is made');
await clickTrigger('#database');
assert.strictEqual(searchSelectComponent.options.length, 1, 'list shows existing connections so far');
await selectChoose('#database', '.ember-power-select-option', 0);
assert
.dom('[data-test-component="empty-state"]')
.exists({ count: 2 }, 'Two empty states exist before selections made');
.exists({ count: 2 }, 'Two empty states exist after a database is selected');
await rolePage.roleType('static');
assert.dom('[data-test-component="empty-state"]').doesNotExist('Empty states go away');
assert.dom('[data-test-input="username"]').exists('Username field appears for static role');

View file

@ -88,10 +88,11 @@ module('Acceptance | database workflow', function (hooks) {
label: 'Root rotation statements',
value: `Default`,
},
{ label: 'Skip initial rotation on static roles', value: 'No' },
];
});
test('create with rotate', async function (assert) {
assert.expect(24);
assert.expect(26);
this.server.post('/:backend/rotate-root/:name', () => {
assert.ok(true, 'rotate root called');
new Response(204);
@ -119,12 +120,16 @@ module('Acceptance | database workflow', function (hooks) {
);
assert.dom(PAGE.infoRow).exists({ count: this.expectedRows.length }, 'correct number of rows');
this.expectedRows.forEach(({ label, value }) => {
const valueSelector =
label === 'Skip initial rotation on static roles'
? PAGE.infoRowValueDiv(label)
: PAGE.infoRowValue(label);
assert.dom(PAGE.infoRowLabel(label)).hasText(label, `Label for ${label} is correct`);
assert.dom(PAGE.infoRowValue(label)).hasText(value, `Value for ${label} is correct`);
assert.dom(valueSelector).hasText(value, `Value for ${label} is correct`);
});
});
test('create without rotate', async function (assert) {
assert.expect(23);
assert.expect(25);
this.server.post('/:backend/rotate-root/:name', () => {
assert.notOk(true, 'rotate root called when it should not have been');
new Response(204);
@ -152,12 +157,16 @@ module('Acceptance | database workflow', function (hooks) {
);
assert.dom(PAGE.infoRow).exists({ count: this.expectedRows.length }, 'correct number of rows');
this.expectedRows.forEach(({ label, value }) => {
const valueSelector =
label === 'Skip initial rotation on static roles'
? PAGE.infoRowValueDiv(label)
: PAGE.infoRowValue(label);
assert.dom(PAGE.infoRowLabel(label)).hasText(label, `Label for ${label} is correct`);
assert.dom(PAGE.infoRowValue(label)).hasText(value, `Value for ${label} is correct`);
assert.dom(valueSelector).hasText(value, `Value for ${label} is correct`);
});
});
test('create failure', async function (assert) {
assert.expect(25);
assert.expect(27);
this.server.post('/:backend/rotate-root/:name', (schema, req) => {
const okay = req.params.name !== 'bad-connection';
assert.ok(okay, 'rotate root called but not for bad-connection');
@ -192,8 +201,12 @@ module('Acceptance | database workflow', function (hooks) {
);
assert.dom(PAGE.infoRow).exists({ count: this.expectedRows.length }, 'correct number of rows');
this.expectedRows.forEach(({ label, value }) => {
const valueSelector =
label === 'Skip initial rotation on static roles'
? PAGE.infoRowValueDiv(label)
: PAGE.infoRowValue(label);
assert.dom(PAGE.infoRowLabel(label)).hasText(label, `Label for ${label} is correct`);
assert.dom(PAGE.infoRowValue(label)).hasText(value, `Value for ${label} is correct`);
assert.dom(valueSelector).hasText(value, `Value for ${label} is correct`);
});
});

View file

@ -94,7 +94,8 @@ module('Integration | Component | database-role-edit', function (hooks) {
await render(hbs`<DatabaseRoleEdit @model={{this.modelStatic}} @mode="create"/>`);
await fillIn('[data-test-ttl-value="Rotation period"]', '2');
await click('[data-test-input="skip_import_rotation"]');
await click('[data-test-toggle-input="toggle-skip_import_rotation"]');
await click('[data-test-secret-save]');
await render(hbs`<DatabaseRoleEdit @model={{this.modelStatic}} @mode="show"/>`);

View file

@ -11,59 +11,45 @@ import hbs from 'htmlbars-inline-precompile';
import { setRunOptions } from 'ember-a11y-testing/test-support';
const testCases = [
{
// default case should show all possible fields for each type
pluginType: '',
staticRoleFields: ['name', 'username', 'rotation_period', 'rotation_statements'],
dynamicRoleFields: [
'name',
'default_ttl',
'max_ttl',
'creation_statements',
'revocation_statements',
'rollback_statements',
'renew_statements',
],
},
{
pluginType: 'elasticsearch-database-plugin',
staticRoleFields: ['username', 'rotation_period'],
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
dynamicRoleFields: ['creation_statement', 'default_ttl', 'max_ttl'],
},
{
pluginType: 'mongodb-database-plugin',
staticRoleFields: ['username', 'rotation_period'],
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
dynamicRoleFields: ['creation_statement', 'revocation_statement', 'default_ttl', 'max_ttl'],
statementsHidden: true,
},
{
pluginType: 'mssql-database-plugin',
staticRoleFields: ['username', 'rotation_period'],
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
dynamicRoleFields: ['creation_statements', 'revocation_statements', 'default_ttl', 'max_ttl'],
},
{
pluginType: 'mysql-database-plugin',
staticRoleFields: ['username', 'rotation_period'],
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
dynamicRoleFields: ['creation_statements', 'revocation_statements', 'default_ttl', 'max_ttl'],
},
{
pluginType: 'mysql-aurora-database-plugin',
staticRoleFields: ['username', 'rotation_period'],
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
dynamicRoleFields: ['creation_statements', 'revocation_statements', 'default_ttl', 'max_ttl'],
},
{
pluginType: 'mysql-rds-database-plugin',
staticRoleFields: ['username', 'rotation_period'],
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
dynamicRoleFields: ['creation_statements', 'revocation_statements', 'default_ttl', 'max_ttl'],
},
{
pluginType: 'mysql-legacy-database-plugin',
staticRoleFields: ['username', 'rotation_period'],
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
dynamicRoleFields: ['creation_statements', 'revocation_statements', 'default_ttl', 'max_ttl'],
},
{
pluginType: 'vault-plugin-database-oracle',
staticRoleFields: ['username', 'rotation_period'],
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
dynamicRoleFields: ['creation_statements', 'revocation_statements', 'default_ttl', 'max_ttl'],
},
];
@ -73,6 +59,7 @@ const ALL_ATTRS = [
{ name: 'default_ttl', type: 'string', options: {} },
{ name: 'max_ttl', type: 'string', options: {} },
{ name: 'username', type: 'string', options: {} },
{ name: 'skip_import_rotation', type: 'boolean', options: {} },
{ name: 'rotation_period', type: 'string', options: {} },
{ name: 'creation_statements', type: 'string', options: {} },
{ name: 'creation_statement', type: 'string', options: {} },
@ -114,20 +101,19 @@ module('Integration | Component | database-role-setting-form', function (hooks)
test('it shows appropriate fields based on roleType and db plugin', async function (assert) {
this.set('roleType', 'static');
this.set('dbType', '');
this.set('dbParams', { plugin_name: '', skip_static_role_rotation_import: false });
await render(hbs`
<DatabaseRoleSettingForm
@attrs={{this.model.attrs}}
@model={{this.model}}
@roleType={{this.roleType}}
@dbType={{this.dbType}}
@dbParams={{this.dbParams}}
/>
`);
assert.dom('[data-test-component="empty-state"]').doesNotExist('Does not show empty states');
for (const testCase of testCases) {
const staticFields = getFields(testCase.staticRoleFields);
const dynamicFields = getFields(testCase.dynamicRoleFields);
this.set('dbType', testCase.pluginType);
this.set('dbParams', { plugin_name: testCase.pluginType, skip_static_role_rotation_import: false });
this.set('roleType', 'static');
staticFields.show.forEach((attr) => {
assert

View file

@ -106,6 +106,26 @@ module('Integration | Component | form field', function (hooks) {
assert.ok(spy.calledWith('foo', 'hello'), 'onChange called with correct args');
});
test('it renders: toggleButton', async function (assert) {
const [model, spy] = await setup.call(
this,
createAttr('foobar', 'toggleButton', {
defaultValue: false,
editType: 'toggleButton',
helperTextEnabled: 'Toggled on',
helperTextDisabled: 'Toggled off',
})
);
assert.ok(component.hasToggleButton, 'renders a toggle button');
assert.dom('[data-test-toggle-input]').isNotChecked();
assert.dom('[data-test-toggle-subtext]').hasText('Toggled off');
await component.fields.objectAt(0).toggleButton();
assert.true(model.get('foobar'));
assert.ok(spy.calledWith('foobar', true), 'onChange called with correct args');
});
test('it renders: editType file', async function (assert) {
const subText = 'My subtext.';
await setup.call(this, createAttr('foo', 'string', { editType: 'file', subText, docLink: '/docs' }));

View file

@ -19,6 +19,7 @@ export default {
hasStringList: isPresent('[data-test-component=string-list]'),
hasTextFile: isPresent('[data-test-component=text-file]'),
hasTTLPicker: isPresent('[data-test-toggle-input="Foo"]'),
hasToggleButton: isPresent('[data-test-toggle-input="toggle-foobar"]'),
hasJSONEditor: isPresent('[data-test-component="code-mirror-modifier"]'),
hasJSONClearButton: isPresent('[data-test-json-clear-button]'),
hasInput: isPresent('input'),
@ -36,6 +37,7 @@ export default {
fields: collection('[data-test-field]', {
clickLabel: clickable('label'),
toggleTtl: clickable('[data-test-toggle-input="Foo"]'),
toggleButton: clickable('[data-test-toggle-input="toggle-foobar"]'),
labelValue: text('[data-test-form-field-label]'),
input: fillable('input'),
ttlTime: fillable('[data-test-ttl-value]'),

View file

@ -26,6 +26,7 @@ module('Unit | Serializer | database/connection', function (hooks) {
url: 'http://localhost:9200',
username: 'elastic',
password: 'changeme',
skip_static_role_rotation_import: false,
tls_ca: 'some-value',
ca_cert: undefined, // does not send undefined values
});
@ -38,6 +39,7 @@ module('Unit | Serializer | database/connection', function (hooks) {
url: 'http://localhost:9200',
username: 'elastic',
password: 'changeme',
skip_static_role_rotation_import: false,
insecure: false,
};