Chore migrate credential library storage bucket (#3282)

This commit is contained in:
Dharini committed 2026-06-03 16:36:29 -07:00
1 parent 0e223ded8f
commit e38d3afa58
22 files changed
+177 -102

No files matched your search

@@ -48,6 +48,9 @@ export default factory.extend({
critical_options: { critical_options: {
[faker.word.noun()]: faker.word.words(2), [faker.word.noun()]: faker.word.words(2),
}, },
extensions: {
[faker.word.noun()]: faker.word.words(2),
},
}; };
case TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP: case TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP:
return { return {
@@ -89,7 +89,6 @@
@data={{@model.certificates}} @data={{@model.certificates}}
@legend={{t 'resources.auth-method.form.certificates.label'}} @legend={{t 'resources.auth-method.form.certificates.label'}}
@helperText={{t 'resources.auth-method.form.certificates.help'}} @helperText={{t 'resources.auth-method.form.certificates.help'}}
@errors={{@model.errors.certificates}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
@isOptional={{true}} @isOptional={{true}}
> >
@@ -406,7 +405,6 @@
@data={{@model.account_attribute_maps}} @data={{@model.account_attribute_maps}}
@legend={{t 'resources.auth-method.form.account_attribute_maps.label'}} @legend={{t 'resources.auth-method.form.account_attribute_maps.label'}}
@helperText={{t 'resources.auth-method.form.account_attribute_maps.help'}} @helperText={{t 'resources.auth-method.form.account_attribute_maps.help'}}
@errors={{@model.errors.account_attribute_maps}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
@isOptional={{true}} @isOptional={{true}}
> >
@@ -154,7 +154,6 @@
@data={{@model.signing_algorithms}} @data={{@model.signing_algorithms}}
@legend={{t 'form.signing_algorithms.label'}} @legend={{t 'form.signing_algorithms.label'}}
@helperText={{t 'form.signing_algorithms.help'}} @helperText={{t 'form.signing_algorithms.help'}}
@errors={{@model.errors.signing_algorithms}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
@isOptional={{true}} @isOptional={{true}}
> >
@@ -191,7 +190,6 @@
@data={{@model.allowed_audiences}} @data={{@model.allowed_audiences}}
@legend={{t 'form.allowed_audiences.label'}} @legend={{t 'form.allowed_audiences.label'}}
@helperText={{t 'form.allowed_audiences.help'}} @helperText={{t 'form.allowed_audiences.help'}}
@errors={{@model.errors.allowed_audiences}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
@isOptional={{true}} @isOptional={{true}}
> >
@@ -217,7 +215,6 @@
@data={{@model.claims_scopes}} @data={{@model.claims_scopes}}
@legend={{t 'form.claims_scopes.label'}} @legend={{t 'form.claims_scopes.label'}}
@helperText={{t 'form.claims_scopes.help'}} @helperText={{t 'form.claims_scopes.help'}}
@errors={{@model.errors.claims_scopes}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
@isOptional={{true}} @isOptional={{true}}
> >
@@ -243,7 +240,6 @@
@data={{@model.account_claim_maps}} @data={{@model.account_claim_maps}}
@legend={{t 'form.account_claim_maps.label'}} @legend={{t 'form.account_claim_maps.label'}}
@helperText={{t 'form.account_claim_maps.help'}} @helperText={{t 'form.account_claim_maps.help'}}
@errors={{@model.errors.account_claim_maps}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
@isOptional={{true}} @isOptional={{true}}
> >
@@ -288,7 +284,6 @@
@data={{@model.idp_ca_certs}} @data={{@model.idp_ca_certs}}
@legend={{t 'form.idp_ca_certs.label'}} @legend={{t 'form.idp_ca_certs.label'}}
@helperText={{t 'form.idp_ca_certs.help'}} @helperText={{t 'form.idp_ca_certs.help'}}
@errors={{@model.errors.idp_ca_certs}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
@isOptional={{true}} @isOptional={{true}}
> >
@@ -232,7 +232,6 @@
@data={{@model.critical_options}} @data={{@model.critical_options}}
@legend={{t 'resources.credential-library.form.critical_options.label'}} @legend={{t 'resources.credential-library.form.critical_options.label'}}
@helperText={{t 'resources.credential-library.form.critical_options.help'}} @helperText={{t 'resources.credential-library.form.critical_options.help'}}
@errors={{@model.errors.critical_options}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
@isOptional={{true}} @isOptional={{true}}
> >
@@ -262,43 +261,40 @@
</Form::Field::KeyValue> </Form::Field::KeyValue>
<Form::Field::ListWrapper <Form::Field::KeyValue
@layout='horizontal' @name='extensions'
@isOptional={{true}} @model={{@model}}
@properties={{array 'key' 'value'}}
@data={{@model.extensions}}
@legend={{t 'resources.credential-library.form.extensions.label'}}
@helperText={{t 'resources.credential-library.form.extensions.help'}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
@isOptional={{true}}
> >
<:fieldset as |F|> <:row as |R|>
<F.Legend> <R.Field as |F|>
{{t 'resources.credential-library.form.extensions.label'}} <F.Label>{{t 'form.key.label'}}</F.Label>
</F.Legend> <F.TextInput
<F.HelperText> data-test-key-input
{{t 'resources.credential-library.form.extensions.help'}} @value={{R.rowData.key}}
</F.HelperText> {{on 'input' (fn R.updateRow 'key')}}
/>
</R.Field>
{{#if @model.errors.extensions}} <R.Field as |F|>
<F.Error as |E|> <F.Label>{{t 'form.value.label'}}</F.Label>
{{#each @model.errors.extensions as |error|}} <F.TextInput
<E.Message>{{error.message}}</E.Message> data-test-value-input
{{/each}} @value={{R.rowData.value}}
</F.Error> {{on 'input' (fn R.updateRow 'value')}}
/>
</R.Field>
{{#if R.canDelete}}
<R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} />
{{/if}} {{/if}}
</:fieldset> </:row>
<:field as |F|> </Form::Field::KeyValue>
<F.KeyValue
@name='extensions'
@options={{@model.extensions}}
@model={{@model}}
>
<:key as |K|>
<K.text />
</:key>
<:value as |V|>
<V.text />
</:value>
</F.KeyValue>
</:field>
</Form::Field::ListWrapper>
{{#if (can 'save model' @model)}} {{#if (can 'save model' @model)}}
<form.actions <form.actions
@@ -34,9 +34,10 @@
data-test-add-button data-test-add-button
@onClick={{this.handleAdd}} @onClick={{this.handleAdd}}
/> />
{{#if @errors}}
{{#if (get @model.errors @name)}}
<F.Error as |E|> <F.Error as |E|>
{{#each @errors as |error|}} {{#each (get @model.errors @name) as |error|}}
<E.Message data-test-error-message>{{error.message}}</E.Message> <E.Message data-test-error-message>{{error.message}}</E.Message>
{{/each}} {{/each}}
</F.Error> </F.Error>
@@ -279,7 +279,6 @@
@data={{@model.role_tags}} @data={{@model.role_tags}}
@legend={{t 'resources.storage-bucket.form.role_tags.label'}} @legend={{t 'resources.storage-bucket.form.role_tags.label'}}
@helperText={{t 'resources.storage-bucket.form.role_tags.help'}} @helperText={{t 'resources.storage-bucket.form.role_tags.help'}}
@errors={{@model.errors.role_tags}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
@isOptional={{true}} @isOptional={{true}}
> >
@@ -69,7 +69,6 @@
@data={{@model.preferred_endpoints}} @data={{@model.preferred_endpoints}}
@legend={{t 'resources.host-set.form.preferred_endpoints.label'}} @legend={{t 'resources.host-set.form.preferred_endpoints.label'}}
@helperText={{t 'resources.host-set.form.preferred_endpoints.help'}} @helperText={{t 'resources.host-set.form.preferred_endpoints.help'}}
@errors={{@model.errors.preferred_endpoints}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
@isOptional={{true}} @isOptional={{true}}
> >
@@ -94,7 +93,6 @@
@data={{@model.filters}} @data={{@model.filters}}
@legend={{t 'resources.host-set.form.filter.label'}} @legend={{t 'resources.host-set.form.filter.label'}}
@helperText={{t 'resources.host-set.form.filter.aws.help'}} @helperText={{t 'resources.host-set.form.filter.aws.help'}}
@errors={{@model.errors.filters}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
@isOptional={{true}} @isOptional={{true}}
> >
@@ -69,7 +69,6 @@
@data={{@model.preferred_endpoints}} @data={{@model.preferred_endpoints}}
@legend={{t 'resources.host-set.form.preferred_endpoints.label'}} @legend={{t 'resources.host-set.form.preferred_endpoints.label'}}
@helperText={{t 'resources.host-set.form.preferred_endpoints.help'}} @helperText={{t 'resources.host-set.form.preferred_endpoints.help'}}
@errors={{@model.errors.preferred_endpoints}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
@isOptional={{true}} @isOptional={{true}}
> >
@@ -68,7 +68,6 @@
@data={{@model.preferred_endpoints}} @data={{@model.preferred_endpoints}}
@legend={{t 'resources.host-set.form.preferred_endpoints.label'}} @legend={{t 'resources.host-set.form.preferred_endpoints.label'}}
@helperText={{t 'resources.host-set.form.preferred_endpoints.help'}} @helperText={{t 'resources.host-set.form.preferred_endpoints.help'}}
@errors={{@model.errors.preferred_endpoints}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
@isOptional={{true}} @isOptional={{true}}
> >
@@ -93,7 +92,6 @@
@data={{@model.filters}} @data={{@model.filters}}
@legend={{t 'resources.host-set.form.filter.label'}} @legend={{t 'resources.host-set.form.filter.label'}}
@helperText={{t 'resources.host-set.form.filter.gcp.help'}} @helperText={{t 'resources.host-set.form.filter.gcp.help'}}
@errors={{@model.errors.filters}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
@isOptional={{true}} @isOptional={{true}}
> >
@@ -4,6 +4,7 @@
}} }}
<Rose::Form <Rose::Form
@edit={{@edit}}
@onSubmit={{@submit}} @onSubmit={{@submit}}
@cancel={{@cancel}} @cancel={{@cancel}}
@disabled={{@model.isSaving}} @disabled={{@model.isSaving}}
@@ -417,42 +418,42 @@
</Hds::Form::TextInput::Field> </Hds::Form::TextInput::Field>
{{! role_tags }} {{! role_tags }}
<Form::Field::ListWrapper <Form::Field::KeyValue
@layout='horizontal' @model={{@model}}
@name='role_tags'
@properties={{array 'key' 'value'}}
@data={{@model.role_tags}}
@isOptional={{true}} @isOptional={{true}}
@legend={{t 'resources.storage-bucket.form.role_tags.label'}}
@helperText={{t 'resources.storage-bucket.form.role_tags.help'}}
@disabled={{form.disabled}} @disabled={{form.disabled}}
> >
<:fieldset as |F|> <:row as |R|>
<F.Legend> <R.Field as |F|>
{{t 'resources.storage-bucket.form.role_tags.label'}} <F.TextInput
</F.Legend> data-test-key-input
<F.HelperText> @value={{R.rowData.key}}
{{t 'resources.storage-bucket.form.role_tags.help'}} {{on 'input' (fn R.updateRow 'key')}}
</F.HelperText> disabled={{form.disabled}}
/>
{{#if @model.errors.role_tags}} </R.Field>
<F.Error as |E|> <R.Field as |F|>
{{#each @model.errors.role_tags as |error|}} <F.TextInput
<E.Message>{{error.message}}</E.Message> data-test-value-input
{{/each}} @value={{R.rowData.value}}
</F.Error> {{on 'input' (fn R.updateRow 'value')}}
disabled={{form.disabled}}
/>
</R.Field>
{{#if R.canDelete}}
<R.DeleteRowButton
data-test-delete-button
@onClick={{R.removeRow}}
disabled={{form.disabled}}
/>
{{/if}} {{/if}}
</:fieldset> </:row>
<:field as |F|> </Form::Field::KeyValue>
<F.KeyValue
@name='role_tags'
@options={{@model.role_tags}}
@model={{@model}}
>
<:key as |K|>
<K.text />
</:key>
<:value as |V|>
<V.text />
</:value>
</F.KeyValue>
</:field>
</Form::Field::ListWrapper>
{{/if}} {{/if}}
{{! worker_filter }} {{! worker_filter }}
@@ -5,6 +5,7 @@
{{#if @model.type}} {{#if @model.type}}
<this.storageBucketFormComponent <this.storageBucketFormComponent
@model={{@model}} @model={{@model}}
@edit={{@edit}}
@submit={{@submit}} @submit={{@submit}}
@cancel={{@cancel}} @cancel={{@cancel}}
@updateScope={{this.updateScope}} @updateScope={{this.updateScope}}
@@ -79,6 +79,13 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
credentialLibrary.critical_options = credentialLibrary.critical_options =
credentialLibrary.critical_options.filter((item) => item.key?.trim()); credentialLibrary.critical_options.filter((item) => item.key?.trim());
} }
if (credentialLibrary.extensions) {
credentialLibrary.extensions = credentialLibrary.extensions.filter(
(item) => item.key?.trim(),
);
}
await credentialLibrary.save(); await credentialLibrary.save();
if (this.abilities.can('read model', credentialLibrary)) { if (this.abilities.can('read model', credentialLibrary)) {
await this.router.transitionTo( await this.router.transitionTo(
@@ -130,6 +137,10 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
if (credentialLibrary.critical_options?.length === 0) { if (credentialLibrary.critical_options?.length === 0) {
credentialLibrary.critical_options = [{ key: '', value: '' }]; credentialLibrary.critical_options = [{ key: '', value: '' }];
} }
if (credentialLibrary.extensions?.length === 0) {
credentialLibrary.extensions = [{ key: '', value: '' }];
}
} }
/** /**
@@ -9,7 +9,10 @@ import { action } from '@ember/object';
import { loading } from 'ember-loading'; import { loading } from 'ember-loading';
import { confirm } from 'core/decorators/confirm'; import { confirm } from 'core/decorators/confirm';
import { notifySuccess, notifyError } from 'core/decorators/notify'; import { notifySuccess, notifyError } from 'core/decorators/notify';
import { TYPE_CREDENTIAL_STATIC } from 'api/models/storage-bucket'; import {
TYPE_CREDENTIAL_STATIC,
TYPE_CREDENTIAL_DYNAMIC,
} from 'api/models/storage-bucket';
export default class ScopesScopeStorageBucketsIndexController extends Controller { export default class ScopesScopeStorageBucketsIndexController extends Controller {
// =services // =services
@@ -45,6 +48,17 @@ export default class ScopesScopeStorageBucketsIndexController extends Controller
return this.intl.t(description, { resource }); return this.intl.t(description, { resource });
} }
/**
* Generates one empty role_tags row for storage bucket when the field is empty.
* @param {storageBucketModel} bucket
*/
generateAwsRoleTagsRowIfEmpty(bucket) {
// If role_tags is an empty array, add an empty row
if (!bucket.role_tags || bucket.role_tags.length === 0) {
bucket.role_tags = [{ key: '', value: '' }];
}
}
// =actions // =actions
/** /**
@@ -60,6 +74,13 @@ export default class ScopesScopeStorageBucketsIndexController extends Controller
if (!storageBucket.role_arn) { if (!storageBucket.role_arn) {
storageBucket.credentialType = TYPE_CREDENTIAL_STATIC; storageBucket.credentialType = TYPE_CREDENTIAL_STATIC;
} }
if (storageBucket.role_tags) {
storageBucket.role_tags = storageBucket.role_tags.filter((item) =>
item.key?.trim(),
);
}
await storageBucket.save(); await storageBucket.save();
await this.router.transitionTo( await this.router.transitionTo(
'scopes.scope.storage-buckets.storage-bucket', 'scopes.scope.storage-buckets.storage-bucket',
@@ -105,6 +126,10 @@ export default class ScopesScopeStorageBucketsIndexController extends Controller
@action @action
changeCredentialType(storageBucket, credentialType) { changeCredentialType(storageBucket, credentialType) {
storageBucket.credentialType = credentialType; storageBucket.credentialType = credentialType;
if (credentialType === TYPE_CREDENTIAL_DYNAMIC) {
this.generateAwsRoleTagsRowIfEmpty(storageBucket);
}
} }
/** /**
@@ -117,4 +142,15 @@ export default class ScopesScopeStorageBucketsIndexController extends Controller
queryParams: { compositeType: pluginType }, queryParams: { compositeType: pluginType },
}); });
} }
@action
edit(storageBucket) {
if (storageBucket.role_tags) {
storageBucket.role_tags = structuredClone(storageBucket.role_tags);
}
if (storageBucket.credentialType === TYPE_CREDENTIAL_DYNAMIC) {
this.generateAwsRoleTagsRowIfEmpty(storageBucket);
}
}
} }
@@ -77,9 +77,10 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
name, name,
description, description,
}; };
// if the type is vault-ssh-certificate, initialize critical_options with an empty row // if the type is vault-ssh-certificate, initialize critical_options and extensions with an empty row
if (resolvedType === TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE) { if (resolvedType === TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE) {
attrs.critical_options = [{ key: '', value: '' }]; attrs.critical_options = [{ key: '', value: '' }];
attrs.extensions = [{ key: '', value: '' }];
} }
return this.store.createRecord('credential-library', attrs); return this.store.createRecord('credential-library', attrs);
@@ -53,7 +53,8 @@ export default class ScopesScopeStorageBucketsNewRoute extends Route {
bucket_name, bucket_name,
bucket_prefix, bucket_prefix,
region, region,
worker_filter; worker_filter,
role_tags;
if (this.currentModel?.isNew) { if (this.currentModel?.isNew) {
({ ({
@@ -64,6 +65,7 @@ export default class ScopesScopeStorageBucketsNewRoute extends Route {
bucket_prefix, bucket_prefix,
region, region,
worker_filter, worker_filter,
role_tags,
} = this.currentModel); } = this.currentModel);
this.currentModel.rollbackAttributes(); this.currentModel.rollbackAttributes();
} }
@@ -78,6 +80,7 @@ export default class ScopesScopeStorageBucketsNewRoute extends Route {
bucket_prefix, bucket_prefix,
region, region,
worker_filter, worker_filter,
role_tags: role_tags || [{ key: '', value: '' }],
}); });
return record; return record;
} }
@@ -42,6 +42,7 @@
<page.body> <page.body>
<Form::StorageBucket <Form::StorageBucket
@model={{@model}} @model={{@model}}
@edit={{fn this.storageBuckets.edit @model}}
@submit={{fn this.storageBuckets.save @model}} @submit={{fn this.storageBuckets.save @model}}
@cancel={{fn this.storageBuckets.cancel @model}} @cancel={{fn this.storageBuckets.cancel @model}}
@scopes={{this.scopes}} @scopes={{this.scopes}}
@@ -193,7 +193,6 @@ module('Acceptance | credential-libraries | create', function (hooks) {
await fillIn(selectors.FIELD_CRIT_OPTS_VALUE, 'co_value'); await fillIn(selectors.FIELD_CRIT_OPTS_VALUE, 'co_value');
await fillIn(selectors.FIELD_EXT_KEY, 'ext_key'); await fillIn(selectors.FIELD_EXT_KEY, 'ext_key');
await fillIn(selectors.FIELD_EXT_VALUE, 'ext_value'); await fillIn(selectors.FIELD_EXT_VALUE, 'ext_value');
await click(selectors.FIELD_EXT_BTN);
await click(commonSelectors.SAVE_BTN); await click(commonSelectors.SAVE_BTN);
assert.strictEqual(getCredentialLibraryCount(), count + 1); assert.strictEqual(getCredentialLibraryCount(), count + 1);
@@ -37,11 +37,8 @@ export const FIELD_CRIT_OPTS_KEY =
export const FIELD_CRIT_OPTS_VALUE = export const FIELD_CRIT_OPTS_VALUE =
'[name="critical_options"] [data-test-value-input]'; '[name="critical_options"] [data-test-value-input]';
export const FIELD_EXT_KEY = export const FIELD_EXT_KEY = '[name="extensions"] [data-test-key-input]';
'[name="extensions"] tbody td:nth-of-type(1) input'; export const FIELD_EXT_VALUE = '[name="extensions"] [data-test-value-input]';
export const FIELD_EXT_VALUE =
'[name="extensions"] tbody td:nth-of-type(2) input';
export const FIELD_EXT_BTN = '[name="extensions"] button';
export const MANAGE_DROPDOWN_CRED_LIB = export const MANAGE_DROPDOWN_CRED_LIB =
'[data-test-manage-credential-library-dropdown] button:first-child'; '[data-test-manage-credential-library-dropdown] button:first-child';
@@ -357,7 +357,6 @@ module('Acceptance | credential-libraries | update', function (hooks) {
await fillIn(selectors.FIELD_CRIT_OPTS_VALUE, 'co_value'); await fillIn(selectors.FIELD_CRIT_OPTS_VALUE, 'co_value');
await fillIn(selectors.FIELD_EXT_KEY, 'ext_key'); await fillIn(selectors.FIELD_EXT_KEY, 'ext_key');
await fillIn(selectors.FIELD_EXT_VALUE, 'ext_value'); await fillIn(selectors.FIELD_EXT_VALUE, 'ext_value');
await click(selectors.FIELD_EXT_BTN);
await click(commonSelectors.SAVE_BTN); await click(commonSelectors.SAVE_BTN);
@@ -249,6 +249,14 @@ module('Acceptance | storage-buckets | create', function (hooks) {
await click(selectors.FIELD_DYNAMIC_CREDENTIAL); await click(selectors.FIELD_DYNAMIC_CREDENTIAL);
await fillIn(selectors.FIELD_ROLE_ARN, selectors.FIELD_ROLE_ARN_VALUE); await fillIn(selectors.FIELD_ROLE_ARN, selectors.FIELD_ROLE_ARN_VALUE);
await fillIn(
selectors.FIELD_ROLE_TAG_KEY,
selectors.FIELD_ROLE_TAG_KEY_VALUE,
);
await fillIn(
selectors.FIELD_ROLE_TAG_VALUE,
selectors.FIELD_ROLE_TAG_VALUE_TEXT,
);
await waitFor(commonSelectors.CODE_EDITOR_CM); await waitFor(commonSelectors.CODE_EDITOR_CM);
const editorElement = find(commonSelectors.CODE_EDITOR_CODE); const editorElement = find(commonSelectors.CODE_EDITOR_CODE);
@@ -266,6 +274,10 @@ module('Acceptance | storage-buckets | create', function (hooks) {
}); });
assert.strictEqual(storageBucket.name, commonSelectors.FIELD_NAME_VALUE); assert.strictEqual(storageBucket.name, commonSelectors.FIELD_NAME_VALUE);
assert.deepEqual(storageBucket.attributes.role_tags, {
env: selectors.FIELD_ROLE_TAG_VALUE_TEXT,
});
//for dynamic credentials, there should be no secret field //for dynamic credentials, there should be no secret field
assert.notOk(storageBucket.secret); assert.notOk(storageBucket.secret);
assert.strictEqual(getStorageBucketCount(), storageBucketCount + 1); assert.strictEqual(getStorageBucketCount(), storageBucketCount + 1);
@@ -29,6 +29,13 @@ export const FIELD_SECRET_KEY_EDIT_BTN =
'[data-test-secret-access-key] [type=button]'; '[data-test-secret-access-key] [type=button]';
export const FIELD_WORKER_FILTER = '[name=worker_filter]'; export const FIELD_WORKER_FILTER = '[name=worker_filter]';
export const FIELD_WORKER_FILTER_ERROR = '[data-test-worker-filter-error]'; export const FIELD_WORKER_FILTER_ERROR = '[data-test-worker-filter-error]';
export const FIELD_ROLE_TAG_KEY = '[name=role_tags] [data-test-key-input]';
export const FIELD_ROLE_TAG_VALUE = '[name=role_tags] [data-test-value-input]';
export const FIELD_ROLE_TAG_KEY_VALUE = 'env';
export const FIELD_ROLE_TAG_VALUE_TEXT = 'dev';
export const FIELD_ROLE_TAG_ADD_BTN = '[name=role_tags] [data-test-add-button]';
export const FIELD_ROLE_TAG_DELETE_BTN =
'[name=role_tags] [data-test-delete-button]';
export const READONLY_WORKER_FILTER = '[data-test-worker-filter]'; export const READONLY_WORKER_FILTER = '[data-test-worker-filter]';
export const TOAST = '[role=alert] div'; export const TOAST = '[role=alert] div';
@@ -203,16 +203,26 @@ module('Integration | Component | form/field/key-value', function (hooks) {
assert.dom('[data-test-delete-button]').doesNotExist(); assert.dom('[data-test-delete-button]').doesNotExist();
}); });
test('it displays error messages in footer', async function (assert) { test('it displays error messages in footer (model errors)', async function (assert) {
this.set('data', [{ key: '', value: '' }]); const model = {
this.set('errors', [ tags: [{ key: '', value: '' }],
{ message: 'Key is required' }, errors: {
{ message: 'Value must be unique' }, tags: [
]); { message: 'Key is required' },
{ message: 'Value must be unique' },
],
},
};
this.set('model', model);
this.set('properties', ['key', 'value']);
// With errors // With errors
await render(hbs` await render(hbs`
<Form::Field::KeyValue @data={{this.data}} @errors={{this.errors}}> <Form::Field::KeyValue
@model={{this.model}}
@name="tags"
@properties={{this.properties}}
@data={{this.model.tags}}>
<:row as |R|> <:row as |R|>
<R.Field as |F|> <R.Field as |F|>
<F.TextInput data-test-key-input @value={{R.rowData.key}} /> <F.TextInput data-test-key-input @value={{R.rowData.key}} />
@@ -221,7 +231,7 @@ module('Integration | Component | form/field/key-value', function (hooks) {
<F.TextInput data-test-value-input @value={{R.rowData.value}} /> <F.TextInput data-test-value-input @value={{R.rowData.value}} />
</R.Field> </R.Field>
{{#if R.canDelete}} {{#if R.canDelete}}
<R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} /> <R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} />
{{/if}} {{/if}}
</:row> </:row>
<:footer as |F|> <:footer as |F|>
@@ -235,9 +245,13 @@ module('Integration | Component | form/field/key-value', function (hooks) {
.hasText('Value must be unique'); .hasText('Value must be unique');
// Without errors // Without errors
this.set('errors', undefined); this.set('model', { ...model, errors: {} });
await render(hbs` await render(hbs`
<Form::Field::KeyValue @data={{this.data}} @errors={{this.errors}}> <Form::Field::KeyValue
@model={{this.model}}
@name="tags"
@properties={{this.properties}}
@data={{this.model.tags}}>
<:row as |R|> <:row as |R|>
<R.Field as |F|> <R.Field as |F|>
<F.TextInput data-test-key-input @value={{R.rowData.key}} /> <F.TextInput data-test-key-input @value={{R.rowData.key}} />
@@ -246,7 +260,7 @@ module('Integration | Component | form/field/key-value', function (hooks) {
<F.TextInput data-test-value-input @value={{R.rowData.value}} /> <F.TextInput data-test-value-input @value={{R.rowData.value}} />
</R.Field> </R.Field>
{{#if R.canDelete}} {{#if R.canDelete}}
<R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} /> <R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} />
{{/if}} {{/if}}
</:row> </:row>
<:footer as |F|> <:footer as |F|>
@@ -322,6 +336,12 @@ module('Integration | Component | form/field/key-value', function (hooks) {
test('custom handlers take precedence over default handlers', async function (assert) { test('custom handlers take precedence over default handlers', async function (assert) {
const model = { const model = {
tags: [{ key: '', value: '' }], tags: [{ key: '', value: '' }],
errors: {
tags: [
{ message: 'Key is required' },
{ message: 'Value must be unique' },
],
},
}; };
this.set('model', model); this.set('model', model);