Llb/worker filter improvements (#2614)

* test: 💍 clean up worker filter component tests (#2559)

* Icu 15741 add show filter generator toggle to worker filter component (#2562)

* feat: 🎸 add filter generator to worker filter component

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15741

* feat: 🎸 show add worker filter when none set for targets (#2564)

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15760

* Icu 15743 tag workflow (#2565)

* feat: 🎸 add tag generator workflow

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15743

* test: 💍 add test cases

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15743

* refactor: 💡 update selected type name to selectedGeneratorType

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15743

* Apply suggestions from code review

minor translation change

Co-authored-by: Lisbet Alvarez <107949262+lisbet-alvarez@users.noreply.github.com>

* refactor: 💡 update id for generated-value

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15743

---------

Co-authored-by: Lisbet Alvarez <lisbet.alvarez@hashicorp.com>
Co-authored-by: Lisbet Alvarez <107949262+lisbet-alvarez@users.noreply.github.com>

* Icu 15744 name workflow (#2566)

* feat: 🎸 add name workflow

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15744

* test: 💍 add "name" workflow tests for filter generator

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15744

* refactor: 💡 use const test selectors for filter generator

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15744

* refactor: 💡 add default option

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15744

* docs: ✏️ fix comment

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15744

---------

Co-authored-by: cameronperera <cameron.perera@hashicorp.com>

* refactor: 💡 move worker filter generator outside of rose::form (#2574)

* refactor: 💡 move worker filter generator outside of rose::form

* [ICU-15718] Add worker filter route, template for empty state (#2576)

* feat: 🎸 Vault Credential Store Worker Filter

The Worker Filters tab will display the associated worker filter and
have an empty state for when there is no worker filter assigned.

* add template

* Add missing copyright headers

* update styling, remove Rose component

* color primary is already default, remove

* add rose component, remove custom css

* fix test

* singularize lang

---------

Co-authored-by: laurenolivia <laurenolivia@users.noreply.github.com>

* feat: 🎸 update worker filter field to use code editor (#2578)

* feat: 🎸 update worker filter field to use code editor

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15737

* test: 💍 fix failing storage bucket tests

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15737

* test: 💍 update tests for new worker filter field (#2580)

* test: 💍 update tests for new worker filter field

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15739

* Update ui/admin/tests/acceptance/storage-buckets/update-test.js

Co-authored-by: Lisbet Alvarez <107949262+lisbet-alvarez@users.noreply.github.com>

---------

Co-authored-by: Lisbet Alvarez <107949262+lisbet-alvarez@users.noreply.github.com>

* [ICU-15719] Create edit worker filter route and template (#2582)

* add codeblock to template

* add cancel btn fn

* add controller test

* add save fn

* display formatted worker filter text inside codeblock

* move codeblock component outside rose::layout::centered

* remove console.log

* remove router.refresh() and refactor translations yaml

* refactor translations, add breadcrumb::item

* replace html elements with hds

* move component, delete file

* [ICU-15720] Add edit worker filter action to manage dropdown (#2593)

* add acceptance test file

* delete test file

* add edit option to manage dropdown

* update route in dropdown

* add check for model isVault

* add acceptance test file

* delete test file

* remove data test selectors, fix syntax

* [ICU-15721] Remove worker filter input field (#2596)

* remove worker filter input field from vault credential store form

* revert yaml

* [ICU-15838] Add vault credential store worker filter tests (#2595)

* add data test elements

* add subtests to update-test.js

* add subtext to check for .hasText()

* add subtest when worker filter exists

* update subtest name

* remove console.log

* clean up

* add new selectors

* refactor: 💡 worker filter clean-up (#2592)

* refactor: 💡 worker filter clean-up

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15816

* refactor: 💡 add aria labels & missing can save check

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15816

* refactor: 💡 pr feedback of using uuid

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15816

* Add missing copyright headers

* refactor: 💡 use unique-id helper for aria labels (pr feedback)

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15816

---------

Co-authored-by: lisbet-alvarez <lisbet-alvarez@users.noreply.github.com>

* refactor: 💡 update icon logic for egress worker filter (#2612)

* refactor: 💡 update worker_filter help translation

* refactor: 💡 add maxHeight to worker filter code blocks

* refactor: 💡 use existing default option for dropdowns

* refactor: 💡 hide copy button when no result to copy

and remove unused styles

---------

Co-authored-by: Lisbet Alvarez <107949262+lisbet-alvarez@users.noreply.github.com>
Co-authored-by: Lisbet Alvarez <lisbet.alvarez@hashicorp.com>
Co-authored-by: Lauren <lauren.burwell@hashicorp.com>
Co-authored-by: laurenolivia <laurenolivia@users.noreply.github.com>
Co-authored-by: lisbet-alvarez <lisbet-alvarez@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2024-12-11 14:00:27 -06:00
1 parent 40c8ceeca6
commit 2869020e65
40 files changed
+1045 -212

No files matched your search

@@ -18,6 +18,12 @@
{{t 'resources.credential.actions.create'}}
</dd.Interactive>
{{/if}}
{{#if (and (feature-flag 'vault-worker-filter') @model.isVault)}}
<dd.Interactive
@route='scopes.scope.credential-stores.credential-store.edit-worker-filter'
@text={{t 'resources.credential-store.actions.edit'}}
/>
{{/if}}
{{#if (can 'delete model' @model)}}
<dd.Separator />
<dd.Interactive @color='critical' {{on 'click' @delete}}>
@@ -8,6 +8,13 @@
{{t 'titles.details'}}
</nav.link>
{{#if @model.isVault}}
{{#if (feature-flag 'vault-worker-filter')}}
<nav.link
@route='scopes.scope.credential-stores.credential-store.worker-filter'
>
{{t 'resources.credential-store.worker-filter.title'}}
</nav.link>
{{/if}}
{{#if (can 'navigate model' @model collection='credential-libraries')}}
<nav.link
@route='scopes.scope.credential-stores.credential-store.credential-libraries'
@@ -115,35 +115,6 @@
{{/if}}
</Hds::Form::TextInput::Field>
{{#if (feature-flag 'vault-worker-filter')}}
<Hds::Form::TextInput::Field
name='worker_filter'
@value={{@model.worker_filter}}
@isOptional={{true}}
@isInvalid={{@model.errors.worker_filter}}
disabled={{form.disabled}}
{{on 'input' (set-from-event @model 'worker_filter')}}
as |F|
>
<F.Label>
{{t 'resources.credential-store.form.worker_filter.label'}}
</F.Label>
<F.HelperText>
{{t 'resources.credential-store.form.worker_filter.help'}}
<Hds::Link::Inline @href={{doc-url 'worker-filters'}}>
{{t 'actions.learn-more'}}
</Hds::Link::Inline>
</F.HelperText>
{{#if @model.errors.worker_filter}}
<F.Error as |E|>
{{#each @model.errors.worker_filter as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
{{/if}}
{{#if (or @model.isNew form.isEditable)}}
<Hds::Form::TextInput::Field
name='token'
@@ -456,30 +456,41 @@
{{/if}}
{{! worker_filter }}
<Hds::Form::TextInput::Field
name='worker_filter'
@isRequired={{true}}
@value={{@model.worker_filter}}
@isInvalid={{@model.errors.worker_filter}}
disabled={{form.disabled}}
{{on 'input' (set-from-event @model 'worker_filter')}}
as |F|
>
<F.Label>{{t 'resources.storage-bucket.form.worker_filter.label'}}</F.Label>
<F.HelperText>
{{t 'resources.storage-bucket.form.worker_filter.help'}}
<Hds::Link::Inline @href={{doc-url 'storage-bucket.worker-filter'}}>
{{t 'actions.learn-more'}}
</Hds::Link::Inline>
</F.HelperText>
{{#if @model.errors.worker_filter}}
<F.Error data-test-worker-filter-error as |E|>
{{#each @model.errors.worker_filter as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
{{#let (unique-id) (unique-id) as |labelId helpId|}}
<Hds::Form::Fieldset
aria-labelledby={{labelId}}
aria-describedby={{helpId}}
class='storage-bucket-worker-filter'
@isRequired={{true}}
as |F|
>
<F.Legend id={{labelId}}>{{t
'resources.storage-bucket.form.worker_filter.label'
}}</F.Legend>
<F.HelperText id={{helpId}}>
{{t 'resources.storage-bucket.form.worker_filter.help'}}
<Hds::Link::Inline @href={{doc-url 'storage-bucket.worker-filter'}}>
{{t 'actions.learn-more'}}
</Hds::Link::Inline>
</F.HelperText>
<F.Control>
{{#if form.disabled}}
<Hds::CodeBlock
@language='bash'
@value={{@model.worker_filter}}
@hasCopyButton={{true}}
data-test-worker-filter
/>
{{else}}
<WorkerFilterGenerator
@model={{@model}}
@name='worker_filter'
@hideToolbar={{true}}
/>
{{/if}}
</F.Control>
</Hds::Form::Fieldset>
{{/let}}
{{! JUST for static credentials }}
{{#unless this.showDynamicCredentials}}
@@ -13,10 +13,11 @@ import { tracked } from '@glimmer/tracking';
export default class FormStorageBucketAwsIndexComponent extends Component {
// =attributes
@tracked selectedCredentialType = this.args.model.credentialType;
@tracked selectedCredentialType = this.args.model.credentialType;
@tracked showDynamicCredentials =
this.args.model.credentialType === TYPE_CREDENTIAL_DYNAMIC;
/**
* returns an array of available credential types
* @type {array}
@@ -28,7 +29,7 @@ export default class FormStorageBucketAwsIndexComponent extends Component {
// =actions
/**
* Allows to update the credential type
* @param type {string}
* @param {string} type
*/
@action
updateCredentialTypeSelection(type) {
@@ -315,30 +315,42 @@
{{/if}}
{{! worker_filter }}
<Hds::Form::TextInput::Field
name='worker_filter'
@isRequired={{true}}
@value={{@model.worker_filter}}
@isInvalid={{@model.errors.worker_filter}}
disabled={{form.disabled}}
{{on 'input' (set-from-event @model 'worker_filter')}}
as |F|
>
<F.Label>{{t 'resources.storage-bucket.form.worker_filter.label'}}</F.Label>
<F.HelperText>
{{t 'resources.storage-bucket.form.worker_filter.help'}}
<Hds::Link::Inline @href={{doc-url 'storage-bucket.worker-filter'}}>
{{t 'actions.learn-more'}}
</Hds::Link::Inline>
</F.HelperText>
{{#if @model.errors.worker_filter}}
<F.Error data-test-worker-filter-error as |E|>
{{#each @model.errors.worker_filter as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
{{#let (unique-id) (unique-id) as |labelId helpId|}}
<Hds::Form::Fieldset
aria-labelledby={{labelId}}
aria-describedby={{helpId}}
class='storage-bucket-worker-filter'
@isRequired={{true}}
as |F|
>
<F.Legend id={{labelId}}>{{t
'resources.storage-bucket.form.worker_filter.label'
}}</F.Legend>
<F.HelperText {{helpId}}>
{{t 'resources.storage-bucket.form.worker_filter.help'}}
<Hds::Link::Inline @href={{doc-url 'storage-bucket.worker-filter'}}>
{{t 'actions.learn-more'}}
</Hds::Link::Inline>
</F.HelperText>
<F.Control>
{{#if form.disabled}}
<Hds::CodeBlock
@maxHeight='300px'
@language='bash'
@value={{@model.worker_filter}}
@hasCopyButton={{true}}
data-test-worker-filter
/>
{{else}}
<WorkerFilterGenerator
@model={{@model}}
@name='worker_filter'
@hideToolbar={{true}}
/>
{{/if}}
</F.Control>
</Hds::Form::Fieldset>
{{/let}}
{{! disable_credential_rotation }}
<Hds::Form::Checkbox::Field
@@ -0,0 +1,21 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
}}
<Rose::Form
class='full-width'
@onSubmit={{@submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
as |form|
>
<WorkerFilterGenerator @model={{@model}} @name={{@name}} />
{{#if (can 'save model' @model)}}
<form.actions
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
{{/if}}
</Rose::Form>
@@ -0,0 +1,198 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
}}
<Hds::Form::Field as |F|>
<F.Control>
<Rose::CodeEditor as |c|>
{{#unless @hideToolbar}}
<c.toolbar @copyText={{get @model @name}} />
{{/unless}}
<c.fieldEditor
@onInput={{fn this.setWorkerFilter @model @name}}
@value={{or (get @model @name) ''}}
@options={{hash
mode='text/x-sh'
gutters=(array 'CodeMirror-lint-markers')
lint='true'
}}
/>
</Rose::CodeEditor>
</F.Control>
{{#if (get @model.errors @name)}}
<F.Error data-test-worker-filter-error as |E|>
{{#each (get @model.errors @name) as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::Field>
<Hds::Separator />
<Hds::Form::Toggle::Field
name='show_filter_generator'
{{on 'change' this.toggleFilterGenerator}}
as |F|
>
<F.Label>{{t 'worker-filter-generator.toggle.title'}}</F.Label>
<F.HelperText>{{t
'worker-filter-generator.toggle.description'
}}</F.HelperText>
</Hds::Form::Toggle::Field>
<Hds::Separator />
{{#if this.showFilterGenerator}}
<Hds::Form::Radio::Group
class='filter-generator-selection'
@name='filter_generator'
{{on 'change' this.setGeneratorType}}
as |G|
>
<G.Legend>{{t 'worker-filter-generator.filter-generator.title'}}</G.Legend>
<G.HelperText>
{{t 'worker-filter-generator.filter-generator.description'}}
<br />
<Hds::Link::Inline
@href={{doc-url 'worker-filters-format'}}
@color='secondary'
>
{{t 'worker-filter-generator.filter-generator.link'}}
</Hds::Link::Inline>
</G.HelperText>
<G.RadioField
@value={{this.generatorTagType}}
checked={{eq this.selectedGeneratorType this.generatorTagType}}
as |F|
>
<F.Label>{{t
'worker-filter-generator.filter-generator.tag.label'
}}</F.Label>
<F.HelperText>{{t
'worker-filter-generator.filter-generator.tag.helper'
}}</F.HelperText>
</G.RadioField>
<G.RadioField
@value={{this.generatorNameType}}
checked={{eq this.selectedGeneratorType this.generatorNameType}}
as |F|
>
<F.Label>{{t
'worker-filter-generator.filter-generator.name.label'
}}</F.Label>
<F.HelperText>{{t
'worker-filter-generator.filter-generator.name.helper'
}}</F.HelperText>
</G.RadioField>
</Hds::Form::Radio::Group>
{{#let (unique-id) (unique-id) as |labelId helpId|}}
<Hds::Form::Fieldset
aria-labelledby={{labelId}}
aria-describedby={{helpId}}
class='input-values'
as |F|
>
<F.Legend id={{labelId}}>{{t
'worker-filter-generator.filter-generator.input-values.title'
}}</F.Legend>
<F.HelperText id={{helpId}}>{{t
'worker-filter-generator.filter-generator.input-values.description'
}}</F.HelperText>
<F.Control>
{{#if (eq this.selectedGeneratorType this.generatorTagType)}}
<Hds::Form::TextInput::Field
@value={{this.key}}
name='tag_key'
@width='320px'
{{on 'input' (set-from-event this 'key')}}
as |F|
>
<F.Label>{{t 'form.key.label'}}</F.Label>
</Hds::Form::TextInput::Field>
{{else}}
<Hds::Form::Select::Field
name='name_operator'
@value={{this.operator}}
@width='320px'
{{on 'change' (set-from-event this 'operator')}}
as |F|
>
<F.Label>{{t 'form.operator.label'}}</F.Label>
<F.Options>
<option value=''>
{{t 'titles.choose-an-option'}}
</option>
{{#each this.operatorOptions as |operator|}}
<option
value={{operator}}
selected={{eq operator this.operator}}
>
{{#if (eq operator '==')}}
{{operator}}
{{else}}
{{t
(concat
'worker-filter-generator.filter-generator.operator.'
operator
)
}}
{{/if}}
</option>
{{/each}}
</F.Options>
</Hds::Form::Select::Field>
{{/if}}
</F.Control>
<F.Control>
<Hds::Form::TextInput::Field
@value={{this.value}}
name='tag_value'
@width='320px'
{{on 'input' (set-from-event this 'value')}}
as |F|
>
<F.Label>{{t 'form.value.label'}}</F.Label>
</Hds::Form::TextInput::Field>
</F.Control>
</Hds::Form::Fieldset>
{{/let}}
{{#let (unique-id) (unique-id) as |labelId helpId|}}
<Hds::Form::Fieldset
aria-labelledby={{labelId}}
aria-describedby={{helpId}}
class='formatted-results'
@layout='vertical'
as |F|
>
<F.Legend id={{labelId}}>{{t
'worker-filter-generator.filter-generator.formatted-result.title'
}}</F.Legend>
<F.HelperText id={{helpId}}>{{t
'worker-filter-generator.filter-generator.formatted-result.description'
}}</F.HelperText>
<F.Control>
<Hds::Text::Body @tag='p' @weight='semibold'>{{t
'worker-filter-generator.filter-generator.formatted-result.label'
}}</Hds::Text::Body>
<div class='generated-results-container'>
<Hds::Form::TextInput::Base
readonly
name='generated_value'
@value={{this.generatedResult}}
@width='320px'
/>
{{#if this.generatedResult}}
<Hds::Copy::Button
@text='Copy'
@textToCopy={{this.generatedResult}}
/>
{{/if}}
</div>
</F.Control>
</Hds::Form::Fieldset>
{{/let}}
{{/if}}
@@ -0,0 +1,70 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
export default class WorkerFilterGeneratorIndexComponent extends Component {
// =attributes
generatorTagType = 'tag';
generatorNameType = 'name';
operatorOptions = ['==', 'matches', 'contains'];
@tracked showFilterGenerator;
@tracked selectedGeneratorType = this.generatorTagType;
@tracked key = '';
@tracked value = '';
@tracked operator = '';
/**
* @return {string}
*/
get generatedResult() {
let valueResult = this.value ? `"${this.value}"` : '';
if (this.selectedGeneratorType === 'tag') {
let keyResult = this.key ? `"/tags/${this.key}"` : '';
return keyResult || valueResult ? `${valueResult} in ${keyResult}` : '';
} else {
return valueResult || this.operator
? `"/name" ${this.operator} ${valueResult}`
: '';
}
}
// =actions
/**
* Sets the model filter to the passed in value.
* @param {Model} model
* @param {string} filter
* @param {string} value
*/
@action
setWorkerFilter(model, filter, value) {
model[filter] = value;
}
/**
* Toggles showFilterGenerator attribute to true or false.
*/
@action
toggleFilterGenerator() {
this.showFilterGenerator = !this.showFilterGenerator;
}
/**
* Clears out key, value, and operator.
* Sets selectedGeneratorType to a value.
* @param {object} event
*/
@action
setGeneratorType(event) {
this.key = '';
this.value = '';
this.operator = '';
this.selectedGeneratorType = event.target.value;
}
}
@@ -1,41 +0,0 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
}}
<Rose::Form
class='full-width'
@onSubmit={{@submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
as |form|
>
<Hds::Form::Field as |F|>
<F.Control>
<Rose::CodeEditor as |c|>
<c.toolbar @copyText={{get @model @name}} />
<c.fieldEditor
@onInput={{fn this.setWorkerFilter @model @name}}
@value={{or (get @model @name) ''}}
@options={{hash
mode='text/x-sh'
gutters=(array 'CodeMirror-lint-markers')
lint='true'
}}
/>
</Rose::CodeEditor>
</F.Control>
{{#if (get @model.errors @name)}}
<F.Error as |E|>
{{#each (get @model.errors @name) as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::Field>
<form.actions
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
</Rose::Form>
@@ -1,22 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class WorkerFilterComponent extends Component {
// =actions
/**
* Sets the model filter to the passed in value.
* @param {Model} model
* @param {string} filter
* @param {string} value
*/
@action
setWorkerFilter(model, filter, value) {
model[filter] = value;
}
}
@@ -0,0 +1,10 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Controller, { inject as controller } from '@ember/controller';
export default class ScopesScopeCredentialStoresCredentialStoreEditWorkerFilterController extends Controller {
@controller('scopes/scope/credential-stores/index') credentialStores;
}
@@ -0,0 +1,10 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Controller, { inject as controller } from '@ember/controller';
export default class ScopesScopeCredentialStoresCredentialStoreWorkerFilterController extends Controller {
@controller('scopes/scope/credential-stores/index') credentialStores;
}
@@ -126,4 +126,31 @@ export default class ScopesScopeCredentialStoresIndexController extends Controll
this[paramKey] = [...selectedItems];
this.page = 1;
}
/**
* Save worker filter
* @param {CredentialStoreModel} credentialStore
*/
@action
@loading
@notifyError(({ message }) => message, { catch: true })
@notifySuccess('notifications.add-success')
async saveWorkerFilter(credentialStore) {
await credentialStore.save();
await this.router.replaceWith(
'scopes.scope.credential-stores.credential-store.worker-filter',
);
}
/**
* Cancel adding or editing a worker filter
* @param {CredentialStoreModel} credentialStore
*/
@action
async cancelWorkerFilter(credentialStore) {
credentialStore.rollbackAttributes();
await this.router.replaceWith(
'scopes.scope.credential-stores.credential-store.worker-filter',
);
}
}
+2
View File
@@ -150,6 +150,8 @@ Router.map(function () {
function () {},
);
});
this.route('worker-filter');
this.route('edit-worker-filter');
},
);
this.route('new');
@@ -0,0 +1,8 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Route from '@ember/routing/route';
export default class ScopesScopeCredentialStoresCredentialStoreEditWorkerFilterRoute extends Route {}
@@ -0,0 +1,8 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Route from '@ember/routing/route';
export default class ScopesScopeCredentialStoresCredentialStoreWorkerFilterRoute extends Route {}
+27
View File
@@ -1056,3 +1056,30 @@
padding: 0.25rem 0.75rem;
}
}
// worker filter generator
.filter-generator-selection {
.hds-form-group__control-field {
margin-bottom: 0;
}
}
.formatted-results {
p {
margin-bottom: 0.5rem;
}
}
.generated-results-container {
display: flex;
flex-direction: row;
gap: 0.5rem;
align-items: flex-start;
}
// storage buckets
.storage-bucket-worker-filter {
.hds-form-field--layout-flag {
margin-bottom: 0;
}
}
@@ -0,0 +1,37 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
}}
{{page-title
(t 'resources.credential-store.worker-filter.messages.edit.title')
}}
<Breadcrumbs::Item
@text={{t 'resources.credential-store.worker-filter.messages.edit.title'}}
@route='scopes.scope.credential-stores.credential-store.edit-worker-filter'
/>
<Rose::Layout::Page as |page|>
<page.breadcrumbs>
<Breadcrumbs::Container />
</page.breadcrumbs>
<page.header>
<Hds::Text::Display @tag='h2' @size='300' class='h2'>
{{t 'resources.credential-store.worker-filter.messages.edit.title'}}
<DocLink @doc='worker-filters-format' @iconSize='large' />
</Hds::Text::Display>
<Hds::Text::Body @tag='p' @size='300'>
{{t 'resources.credential-store.worker-filter.messages.edit.description'}}
</Hds::Text::Body>
</page.header>
<page.body>
<Form::WorkerFilter
@name='worker_filter'
@model={{@model}}
@submit={{fn this.credentialStores.saveWorkerFilter @model}}
@cancel={{fn this.credentialStores.cancelWorkerFilter @model}}
/>
</page.body>
</Rose::Layout::Page>
@@ -0,0 +1,72 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
}}
{{page-title (t 'resources.credential-store.worker-filter.title')}}
<Breadcrumbs::Item
@text={{t 'resources.credential-store.worker-filter.title'}}
@route='scopes.scope.credential-stores.credential-store.worker-filter'
/>
<Rose::Layout::Page as |page|>
<page.breadcrumbs>
<Breadcrumbs::Container />
</page.breadcrumbs>
<page.header>
<CredentialStores::CredentialStore::Header @model={{@model}} />
</page.header>
<page.navigation>
<CredentialStores::CredentialStore::Nav @model={{@model}} />
</page.navigation>
<page.actions>
<CredentialStores::CredentialStore::Actions
@model={{@model}}
@delete={{fn this.credentialStores.delete @model}}
/>
</page.actions>
<page.body>
{{#if @model.worker_filter}}
<Hds::CodeBlock
@maxHeight='300px'
@language='bash'
@value={{@model.worker_filter}}
@hasCopyButton={{true}}
as |CB|
>
<CB.Title>
{{t 'resources.credential-store.worker-filter.title'}}
</CB.Title>
<CB.Description>
{{t 'resources.credential-store.worker-filter.description'}}
</CB.Description>
</Hds::CodeBlock>
{{else}}
<Rose::Layout::Centered>
<Hds::ApplicationState as |A|>
<A.Header
@title={{t
'resources.credential-store.worker-filter.messages.none.title'
}}
/>
<A.Body
@text={{t
'resources.credential-store.worker-filter.messages.none.description'
}}
/>
<A.Footer as |F|>
<F.LinkStandalone
@icon='plus-circle'
@text={{t 'resources.credential-store.actions.add'}}
@route='scopes.scope.credential-stores.credential-store.edit-worker-filter'
/>
</A.Footer>
</Hds::ApplicationState>
</Rose::Layout::Centered>
{{/if}}
</page.body>
</Rose::Layout::Page>
@@ -17,7 +17,7 @@
<page.header>
<h2>
{{t 'resources.target.workers.edit-egress-worker-filter.title'}}
<DocLink @doc='target.worker-filters' @iconSize='large' />
<DocLink @doc='worker-filters-format' @iconSize='large' />
</h2>
<p>{{t
'resources.target.workers.edit-egress-worker-filter.description'
@@ -25,7 +25,7 @@
</page.header>
<page.body class='target-workers'>
<WorkerFilter
<Form::WorkerFilter
@name='egress_worker_filter'
@model={{@model}}
@submit={{fn this.targets.saveWorkerFilter @model}}
@@ -18,7 +18,7 @@
<page.header>
<h2>
{{t 'resources.target.workers.edit-ingress-worker-filter.title'}}
<DocLink @doc='target.worker-filters' @iconSize='large' />
<DocLink @doc='worker-filters-format' @iconSize='large' />
</h2>
<p>{{t
'resources.target.workers.edit-ingress-worker-filter.description'
@@ -26,7 +26,7 @@
</page.header>
<page.body class='target-workers'>
<WorkerFilter
<Form::WorkerFilter
@name='ingress_worker_filter'
@model={{@model}}
@submit={{fn this.targets.saveWorkerFilter @model}}
@@ -51,9 +51,13 @@
'resources.target.workers.accordion-label.ingress-workers'
}}</span>
<Hds::Link::Standalone
@icon='edit'
@icon={{if @model.ingress_worker_filter 'edit' 'plus-circle'}}
@text={{t
'resources.target.workers.actions.edit-worker-filter'
(if
@model.ingress_worker_filter
'resources.target.workers.actions.edit-worker-filter'
'resources.target.workers.actions.add-worker-filter'
)
}}
@route='scopes.scope.targets.target.edit-ingress-worker-filter'
/>
@@ -62,6 +66,7 @@
<:content>
{{#if @model.ingress_worker_filter}}
<Hds::CodeBlock
@maxHeight='300px'
@language='bash'
@hasCopyButton={{true}}
@value={{@model.ingress_worker_filter}}
@@ -101,8 +106,14 @@
'resources.target.workers.accordion-label.egress-workers'
}}</span>
<Hds::Link::Standalone
@icon='edit'
@text={{t 'resources.target.workers.actions.edit-worker-filter'}}
@icon={{if @model.egress_worker_filter 'edit' 'plus-circle'}}
@text={{t
(if
@model.egress_worker_filter
'resources.target.workers.actions.edit-worker-filter'
'resources.target.workers.actions.add-worker-filter'
)
}}
@route='scopes.scope.targets.target.edit-egress-worker-filter'
/>
</div>
@@ -110,6 +121,7 @@
<:content>
{{#if @model.egress_worker_filter}}
<Hds::CodeBlock
@maxHeight='300px'
@language='bash'
@hasCopyButton={{true}}
@value={{@model.egress_worker_filter}}
+2 -2
View File
@@ -101,8 +101,8 @@ module.exports = function (environment) {
'target.add-host-sources': '/docs/concepts/domain-model/host-sets',
'target.enable-session-recording':
'/docs/configuration/session-recording/enable-session-recording',
'target.worker-filters':
'/docs/concepts/filtering/worker-tags#worker-filtering',
'worker-filters-format':
'/docs/concepts/filtering/worker-tags#filter-examples',
user: '/docs/concepts/domain-model/users',
downloads: '/install',
'getting-started.desktop':
@@ -0,0 +1,13 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
// Manage actions dropdown selectors
export const MANAGE_DROPDOWN =
'[data-test-manage-credential-stores-dropdown] button:first-child';
export const EDIT_ACTION =
'[data-test-manage-credential-stores-dropdown] ul li:nth-child(2) a';
export const CODE_EDITOR_BODY = '[data-test-code-editor-field-editor] textarea';
export const CODE_BLOCK_BODY = '.hds-code-block__code';
@@ -10,6 +10,8 @@ import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
import { setupIndexedDb } from 'api/test-support/helpers/indexed-db';
import { authenticateSession } from 'ember-simple-auth/test-support';
import { Response } from 'miragejs';
import * as selectors from './selectors';
import * as commonSelectors from 'admin/tests/helpers/selectors';
module('Acceptance | credential-stores | update', function (hooks) {
setupApplicationTest(hooks);
@@ -59,7 +61,13 @@ module('Acceptance | credential-stores | update', function (hooks) {
urls.credentialStores = `${urls.projectScope}/credential-stores`;
urls.vaultCredentialStore = `${urls.credentialStores}/${instances.vaultCredentialStore.id}`;
urls.staticCredentialStore = `${urls.credentialStores}/${instances.staticCredentialStore.id}`;
urls.workerFilter = `${urls.credentialStores}/${instances.vaultCredentialStore.id}/worker-filter`;
urls.editWorkerFilter = `${urls.credentialStores}/${instances.vaultCredentialStore.id}/edit-worker-filter`;
await authenticateSession({});
// Enable feature flag
const featuresService = this.owner.lookup('service:features');
featuresService.enable('vault-worker-filter');
});
test('can save changes to existing static credential store', async function (assert) {
@@ -295,4 +303,63 @@ module('Acceptance | credential-stores | update', function (hooks) {
);
}
});
test('visiting static credential store does not show worker filter tab', async function (assert) {
await visit(urls.staticCredentialStore);
assert.dom(commonSelectors.HREF(urls.workerFilter)).doesNotExist();
});
test('user can click vault credential store worker filter tab and be rerouted to correct url', async function (assert) {
await visit(urls.vaultCredentialStore);
assert.dom(commonSelectors.HREF(urls.workerFilter)).exists();
await click(commonSelectors.HREF(urls.workerFilter));
assert.strictEqual(currentURL(), urls.workerFilter);
});
test('manage actions dropdown displays edit option and routes to correct url', async function (assert) {
await visit(urls.vaultCredentialStore);
await click(commonSelectors.HREF(urls.workerFilter));
await click(selectors.MANAGE_DROPDOWN);
await click(selectors.EDIT_ACTION);
assert.strictEqual(currentURL(), urls.editWorkerFilter);
});
test('when work filters code editor is empty, save btn reroutes to empty state template', async function (assert) {
instances.vaultCredentialStore.update({
attributes: { worker_filter: null },
});
await visit(urls.vaultCredentialStore);
await click(commonSelectors.HREF(urls.workerFilter));
assert.dom('.hds-application-state').exists();
assert
.dom('.hds-application-state')
.hasText(
`No worker filter added You haven't added a worker filter yet. Add Worker Filter`,
);
});
test('when worker filter exists, readonly code block displays the filter text', async function (assert) {
instances.vaultCredentialStore.update({
attributes: { worker_filter: null },
});
await visit(urls.vaultCredentialStore);
await click(commonSelectors.HREF(urls.workerFilter));
await click(selectors.MANAGE_DROPDOWN);
await click(selectors.EDIT_ACTION);
await fillIn(selectors.CODE_EDITOR_BODY, '"bar" in "/tags/foo"');
await click(commonSelectors.SAVE_BTN);
assert.dom(selectors.CODE_BLOCK_BODY).exists();
assert.dom(selectors.CODE_BLOCK_BODY).includesText('"bar" in "/tags/foo"');
});
});
@@ -7,7 +7,6 @@ import { module, test } from 'qunit';
import { visit, currentURL, click, fillIn, select } from '@ember/test-helpers';
import { setupApplicationTest } from 'admin/tests/helpers';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
import a11yAudit from 'ember-a11y-testing/test-support/audit';
import { Response } from 'miragejs';
import { authenticateSession } from 'ember-simple-auth/test-support';
import * as selectors from './selectors';
@@ -58,6 +57,11 @@ module('Acceptance | storage-buckets | create', function (hooks) {
await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE);
await select(selectors.FIELD_SCOPE, 'global');
await fillIn(
selectors.EDITOR_WORKER_FILTER,
selectors.EDITOR_WORKER_FILTER_VALUE,
);
assert.dom(selectors.FIELD_BUCKET_NAME).isNotDisabled();
assert.dom(selectors.FIELD_BUCKET_PREFIX).isNotDisabled();
assert.dom(selectors.FIELD_BUCKET_NAME).doesNotHaveAttribute('readOnly');
@@ -70,6 +74,9 @@ module('Acceptance | storage-buckets | create', function (hooks) {
assert.strictEqual(storageBucket.name, commonSelectors.FIELD_NAME_VALUE);
assert.strictEqual(storageBucket.scopeId, 'global');
assert
.dom(selectors.READONLY_WORKER_FILTER)
.hasText(selectors.EDITOR_WORKER_FILTER_VALUE);
assert.strictEqual(getStorageBucketCount(), storageBucketCount + 1);
});
@@ -81,6 +88,11 @@ module('Acceptance | storage-buckets | create', function (hooks) {
await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE);
await select(selectors.FIELD_SCOPE, instances.scopes.org.id);
await fillIn(
selectors.EDITOR_WORKER_FILTER,
selectors.EDITOR_WORKER_FILTER_VALUE,
);
assert.dom(selectors.FIELD_BUCKET_NAME).isNotDisabled();
assert.dom(selectors.FIELD_BUCKET_PREFIX).isNotDisabled();
assert.dom(selectors.FIELD_BUCKET_NAME).doesNotHaveAttribute('readOnly');
@@ -120,6 +132,11 @@ module('Acceptance | storage-buckets | create', function (hooks) {
);
await fillIn(selectors.FIELD_ACCESS_KEY, selectors.FIELD_ACCESS_KEY_VALUE);
await fillIn(selectors.FIELD_SECRET_KEY, selectors.FIELD_SECRET_KEY_VALUE);
await fillIn(
selectors.EDITOR_WORKER_FILTER,
selectors.EDITOR_WORKER_FILTER_VALUE,
);
await click(commonSelectors.SAVE_BTN);
// Assertions
@@ -152,6 +169,10 @@ module('Acceptance | storage-buckets | create', function (hooks) {
await click(selectors.FIELD_DYNAMIC_CREDENTIAL);
await fillIn(selectors.FIELD_ROLE_ARN, selectors.FIELD_ROLE_ARN_VALUE);
await fillIn(
selectors.EDITOR_WORKER_FILTER,
selectors.EDITOR_WORKER_FILTER_VALUE,
);
await click(commonSelectors.SAVE_BTN);
const storageBucket = this.server.schema.storageBuckets.findBy({
@@ -175,6 +196,10 @@ module('Acceptance | storage-buckets | create', function (hooks) {
await click(selectors.FIELD_STATIC_CREDENTIAL);
await fillIn(selectors.FIELD_ACCESS_KEY, selectors.FIELD_ACCESS_KEY_VALUE);
await fillIn(selectors.FIELD_SECRET_KEY, selectors.FIELD_SECRET_KEY_VALUE);
await fillIn(
selectors.EDITOR_WORKER_FILTER,
selectors.EDITOR_WORKER_FILTER_VALUE,
);
await click(commonSelectors.SAVE_BTN);
@@ -208,6 +233,10 @@ module('Acceptance | storage-buckets | create', function (hooks) {
);
await fillIn(selectors.FIELD_ACCESS_KEY, selectors.FIELD_ACCESS_KEY_VALUE);
await fillIn(selectors.FIELD_SECRET_KEY, selectors.FIELD_SECRET_KEY_VALUE);
await fillIn(
selectors.EDITOR_WORKER_FILTER,
selectors.EDITOR_WORKER_FILTER_VALUE,
);
await click(commonSelectors.SAVE_BTN);
@@ -264,8 +293,11 @@ module('Acceptance | storage-buckets | create', function (hooks) {
await visit(urls.storageBuckets);
await click(`[href="${urls.newStorageBucket}"]`);
await fillIn(
selectors.EDITOR_WORKER_FILTER,
selectors.EDITOR_WORKER_FILTER_VALUE,
);
await click(commonSelectors.SAVE_BTN);
await a11yAudit();
assert
.dom(commonSelectors.ALERT_TOAST_BODY)
@@ -29,6 +29,11 @@ export const FIELD_SECRET_KEY_EDIT_BTN =
'[data-test-secret-access-key] [type=button]';
export const FIELD_WORKER_FILTER = '[name=worker_filter]';
export const FIELD_WORKER_FILTER_ERROR = '[data-test-worker-filter-error]';
export const READONLY_WORKER_FILTER = '[data-test-worker-filter]';
export const EDITOR_WORKER_FILTER =
'[data-test-code-editor-field-editor] textarea';
export const EDITOR_WORKER_FILTER_VALUE = '"dev" in "/tags/env"';
export const TOAST = '[role=alert] div';
export const TOAST_SUCCESSFUL_VALUE = 'Saved successfully.';
export const TABLE_FIRST_ROW_ACTION_DROPDOWN =
@@ -22,12 +22,13 @@ module('Acceptance | storage-buckets | update', function (hooks) {
global: null,
},
storageBucket: null,
storageBucketMinio: null,
};
const urls = {
globalScope: null,
storageBuckets: null,
storageBucket: null,
storageBucketMinio: null,
};
hooks.beforeEach(async function () {
@@ -39,8 +40,7 @@ module('Acceptance | storage-buckets | update', function (hooks) {
scope: instances.scopes.global,
plugin: { name: 'minio' },
});
urls.globalScope = `/scopes/global`;
urls.storageBuckets = `${urls.globalScope}/storage-buckets`;
urls.storageBuckets = `/scopes/global/storage-buckets`;
urls.storageBucket = `${urls.storageBuckets}/${instances.storageBucket.id}`;
urls.storageBucketMinio = `${urls.storageBuckets}/${instances.storageBucketMinio.id}`;
@@ -51,7 +51,7 @@ module('Acceptance | storage-buckets | update', function (hooks) {
test('can save changes to an existing storage-bucket', async function (assert) {
await visit(urls.storageBuckets);
await click(`[href="${urls.storageBucket}"]`);
await click(commonSelectors.HREF(urls.storageBucket));
await click(commonSelectors.EDIT_BTN, 'Click edit mode');
await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE);
await click(commonSelectors.SAVE_BTN, 'Click save');
@@ -70,7 +70,7 @@ module('Acceptance | storage-buckets | update', function (hooks) {
const name = instances.storageBucket.name;
await visit(urls.storageBuckets);
await click(`[href="${urls.storageBucket}"]`);
await click(commonSelectors.HREF(urls.storageBucket));
await click(commonSelectors.EDIT_BTN, 'Click edit mode');
await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE);
await click(commonSelectors.CANCEL_BTN, 'Click cancel');
@@ -82,7 +82,7 @@ module('Acceptance | storage-buckets | update', function (hooks) {
test('can save changes to access key fields', async function (assert) {
await visit(urls.storageBuckets);
await click(`[href="${urls.storageBucket}"]`);
await click(commonSelectors.HREF(urls.storageBucket));
assert.dom(selectors.FIELD_ACCESS_KEY_EDIT_BTN).exists();
assert.dom(selectors.FIELD_SECRET_KEY_EDIT_BTN).exists();
@@ -117,7 +117,7 @@ module('Acceptance | storage-buckets | update', function (hooks) {
test('can cancel changes to access key fields', async function (assert) {
await visit(urls.storageBuckets);
await click(`[href="${urls.storageBucket}"]`);
await click(commonSelectors.HREF(urls.storageBucket));
await click(commonSelectors.EDIT_BTN, 'Click edit mode');
await click(selectors.FIELD_ACCESS_KEY_EDIT_BTN);
await click(selectors.FIELD_SECRET_KEY_EDIT_BTN);
@@ -165,7 +165,10 @@ module('Acceptance | storage-buckets | update', function (hooks) {
await click(`[href="${urls.storageBucket}"]`);
await click(commonSelectors.EDIT_BTN, 'Activate edit mode');
await fillIn(selectors.FIELD_WORKER_FILTER, 'random string');
await fillIn(
selectors.EDITOR_WORKER_FILTER,
selectors.EDITOR_WORKER_FILTER_VALUE,
);
await click(commonSelectors.SAVE_BTN);
assert
@@ -182,7 +185,7 @@ module('Acceptance | storage-buckets | update', function (hooks) {
test('user cannot edit scope, provider, bucket name, bucket prefix and region fields in a Amazon S3 storage bucket form', async function (assert) {
await visit(urls.storageBuckets);
await click(`[href="${urls.storageBucket}"]`);
await click(commonSelectors.HREF(urls.storageBucket));
assert.dom(selectors.FIELD_BUCKET_NAME).isDisabled();
assert.dom(selectors.FIELD_BUCKET_PREFIX).isDisabled();
@@ -202,7 +205,7 @@ module('Acceptance | storage-buckets | update', function (hooks) {
test('user cannot edit scope, provider, endpoint_url, bucket name or region fields in a MinIO storage bucket', async function (assert) {
await visit(urls.storageBuckets);
await click(`[href="${urls.storageBucketMinio}"]`);
await click(commonSelectors.HREF(urls.storageBucketMinio));
assert.dom(selectors.FIELD_BUCKET_NAME).isDisabled();
@@ -214,4 +217,17 @@ module('Acceptance | storage-buckets | update', function (hooks) {
assert.dom(selectors.FIELD_BUCKET_NAME).hasAttribute('readOnly');
assert.dom(selectors.FIELD_REGION).hasAttribute('readOnly');
});
test('user sees an editable code editor while updating and readonly code block before/after', async function (assert) {
await visit(urls.storageBuckets);
await click(commonSelectors.HREF(urls.storageBucketMinio));
assert.dom(selectors.READONLY_WORKER_FILTER).isVisible();
assert.dom(selectors.EDITOR_WORKER_FILTER).doesNotExist();
await click(commonSelectors.EDIT_BTN, 'Click edit mode');
assert.dom(selectors.EDITOR_WORKER_FILTER).isVisible();
assert.dom(selectors.READONLY_WORKER_FILTER).doesNotExist();
});
});
@@ -7,7 +7,6 @@ import { module, test } from 'qunit';
import { visit, currentURL, click, fillIn } from '@ember/test-helpers';
import { setupApplicationTest } from 'admin/tests/helpers';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
import a11yAudit from 'ember-a11y-testing/test-support/audit';
import { Response } from 'miragejs';
import { authenticateSession } from 'ember-simple-auth/test-support';
import { TYPE_TARGET_SSH } from 'api/models/target';
@@ -30,6 +29,9 @@ module(
const NAME_FIELD_TEXT = 'random string';
const BUCKET_NAME_FIELD_SELECTOR = '[name="bucket_name"]';
const BUCKET_PREFIX_FIELD_SELECTOR = '[name="bucket_prefix"]';
const EDITOR_WORKER_FILTER =
'[data-test-code-editor-field-editor] textarea';
const EDITOR_WORKER_FILTER_VALUE = '"dev" in "/tags/env"';
const instances = {
scopes: {
@@ -84,6 +86,7 @@ module(
await click(`[href="${urls.newStorageBucket}"]`);
await fillIn(NAME_FIELD_SELECTOR, NAME_FIELD_TEXT);
await click('[value="global"]');
await fillIn(EDITOR_WORKER_FILTER, EDITOR_WORKER_FILTER_VALUE);
assert.dom(BUCKET_NAME_FIELD_SELECTOR).isNotDisabled();
assert.dom(BUCKET_PREFIX_FIELD_SELECTOR).isNotDisabled();
@@ -107,6 +110,7 @@ module(
await click(`[href="${urls.newStorageBucket}"]`);
await fillIn(NAME_FIELD_SELECTOR, NAME_FIELD_TEXT);
await click(`[value="${instances.scopes.org.scope.id}"]`);
await fillIn(EDITOR_WORKER_FILTER, EDITOR_WORKER_FILTER_VALUE);
assert.dom(BUCKET_NAME_FIELD_SELECTOR).isNotDisabled();
assert.dom(BUCKET_PREFIX_FIELD_SELECTOR).isNotDisabled();
@@ -158,8 +162,8 @@ module(
await visit(urls.enableSessionRecording);
await click(`[href="${urls.newStorageBucket}"]`);
await fillIn(EDITOR_WORKER_FILTER, EDITOR_WORKER_FILTER_VALUE);
await click(SAVE_BTN_SELECTOR);
await a11yAudit();
assert.dom(ALERT_TEXT_SELECTOR).hasText('The request was invalid.');
assert.dom(FIELD_ERROR_TEXT_SELECTOR).hasText('Name is required.');
@@ -17,6 +17,8 @@ module('Acceptance | targets | workers', function (hooks) {
let featuresService;
let featureEdition;
const ACCORDION_DROPDOWN_TEXT_SELECTOR = (name) =>
`[data-test-target-${name}-workers-accordion-item] a`;
const ACCORDION_DROPDOWN_SELECTOR = (name) =>
`[data-test-target-${name}-workers-accordion-item] .hds-accordion-item__button`;
const CODE_BLOCK_SELECTOR = (name) =>
@@ -244,6 +246,38 @@ module('Acceptance | targets | workers', function (hooks) {
assert.dom(CODE_BLOCK_SELECTOR('egress')).hasText(egressWorkerFilter);
});
test('user will see "Add worker filter" if no filter set', async function (assert) {
featuresService.enable('target-worker-filters-v2-ingress');
instances.target.update({
egress_worker_filter: '',
ingress_worker_filter: '',
});
await visit(urls.target);
await click(`[href="${urls.targetWorkers}"]`);
assert
.dom(ACCORDION_DROPDOWN_TEXT_SELECTOR('egress'))
.hasText('Add worker filter');
assert
.dom(ACCORDION_DROPDOWN_TEXT_SELECTOR('ingress'))
.hasText('Add worker filter');
});
test('user will see "Edit worker filter" if filter is set', async function (assert) {
featuresService.enable('target-worker-filters-v2-ingress');
await visit(urls.target);
await click(`[href="${urls.targetWorkers}"]`);
assert
.dom(ACCORDION_DROPDOWN_TEXT_SELECTOR('egress'))
.hasText('Edit worker filter');
assert
.dom(ACCORDION_DROPDOWN_TEXT_SELECTOR('ingress'))
.hasText('Edit worker filter');
});
test('user can cancel changes to egress worker filter in a target', async function (assert) {
const egressWorkerFilter = '"random" in "/worker/filters"';
await visit(urls.target);
@@ -0,0 +1,135 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, click, fillIn, select } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupIntl } from 'ember-intl/test-support';
module(
'Integration | Component | worker-filter-generator/index',
function (hooks) {
setupRenderingTest(hooks);
setupIntl(hooks, 'en-us');
const CODE_EDITOR = '[data-test-code-editor-field-editor]';
const CODE_EDITOR_LINE =
'[data-test-code-editor-field-editor] .CodeMirror-line';
const FILTER_GENERATOR = '[name="filter_generator"]';
const SHOW_FILTER_GENERATOR = '[name="show_filter_generator"]';
const TAG_TYPE_OPTION = '[value="tag"]';
const NAME_TYPE_OPTION = '[value="name"]';
const TAG_KEY = '[name="tag_key"]';
const TAG_VALUE = '[name="tag_value"]';
const NAME_OPERATOR = '[name="name_operator"]';
const GENERATED_VALUE = '[name="generated_value"]';
test('it renders correct content when egress_worker_filter is passed in', async function (assert) {
this.model = { egress_worker_filter: 'egress filter' };
await render(
hbs`<WorkerFilterGenerator @name='egress_worker_filter' @model={{this.model}} />`,
);
assert.dom(CODE_EDITOR).isVisible();
assert.dom(CODE_EDITOR_LINE).hasText(this.model.egress_worker_filter);
});
test('it renders correct content when ingress_worker_filter is passed in', async function (assert) {
this.model = { ingress_worker_filter: 'ingress filter' };
await render(
hbs`<WorkerFilterGenerator @name='ingress_worker_filter' @model={{this.model}} />`,
);
assert.dom(CODE_EDITOR).isVisible();
assert.dom(CODE_EDITOR_LINE).hasText(this.model.ingress_worker_filter);
});
test('toggleFilterGenerator shows filter generator when toggled on', async function (assert) {
this.model = { ingress_worker_filter: 'ingress filter' };
await render(
hbs`<WorkerFilterGenerator @name='ingress_worker_filter' @model={{this.model}} />`,
);
assert.dom(FILTER_GENERATOR).isNotVisible();
await click(SHOW_FILTER_GENERATOR);
assert.dom(FILTER_GENERATOR).isVisible();
});
test('filter generator tag type shows key and value input boxes', async function (assert) {
this.model = { ingress_worker_filter: 'ingress filter' };
await render(
hbs`<WorkerFilterGenerator @name='ingress_worker_filter' @model={{this.model}} />`,
);
await click(SHOW_FILTER_GENERATOR);
await click(TAG_TYPE_OPTION);
assert.dom(TAG_KEY).isVisible();
assert.dom(TAG_VALUE).isVisible();
assert.dom(NAME_OPERATOR).isNotVisible();
});
test('filter generator tag type generates correctly formatted filter', async function (assert) {
this.model = { ingress_worker_filter: 'ingress filter' };
await render(
hbs`<WorkerFilterGenerator @name='ingress_worker_filter' @model={{this.model}} />`,
);
await click(SHOW_FILTER_GENERATOR);
await click(TAG_TYPE_OPTION);
await fillIn(TAG_KEY, 'key1');
await fillIn(TAG_VALUE, 'val1');
assert.dom(GENERATED_VALUE).hasValue('"val1" in "/tags/key1"');
});
test('filter generator name type shows operator and value fields', async function (assert) {
this.model = { ingress_worker_filter: 'ingress filter' };
await render(
hbs`<WorkerFilterGenerator @name='ingress_worker_filter' @model={{this.model}} />`,
);
await click(SHOW_FILTER_GENERATOR);
await click(NAME_TYPE_OPTION);
assert.dom(NAME_OPERATOR).isVisible();
assert.dom(TAG_VALUE).isVisible();
assert.dom(TAG_KEY).isNotVisible();
});
test('filter generator name type generates correctly formatted filter', async function (assert) {
this.model = { ingress_worker_filter: 'ingress filter' };
await render(
hbs`<WorkerFilterGenerator @name='ingress_worker_filter' @model={{this.model}} />`,
);
await click(SHOW_FILTER_GENERATOR);
await click(NAME_TYPE_OPTION);
await fillIn(TAG_VALUE, 'val1');
await select(NAME_OPERATOR, '==');
assert.dom(GENERATED_VALUE).hasValue('"/name" == "val1"');
});
test('generated result is cleared when switching filter types', async function (assert) {
this.model = { ingress_worker_filter: 'ingress filter' };
await render(
hbs`<WorkerFilterGenerator @name='ingress_worker_filter' @model={{this.model}} />`,
);
await click(SHOW_FILTER_GENERATOR);
await click(NAME_TYPE_OPTION);
await fillIn(TAG_VALUE, 'val1');
await select(NAME_OPERATOR, '==');
assert.dom(GENERATED_VALUE).hasValue('"/name" == "val1"');
await click(TAG_TYPE_OPTION);
assert.dom(GENERATED_VALUE).hasNoValue();
});
},
);
@@ -1,45 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupIntl } from 'ember-intl/test-support';
module('Integration | Component | worker-filter/index', function (hooks) {
setupRenderingTest(hooks);
setupIntl(hooks, 'en-us');
test('it renders correct content when egress_worker_filter is passed in', async function (assert) {
this.onInput = () => {};
this.submit = () => {};
this.cancel = () => {};
this.model = { egress_worker_filter: 'egress filter' };
await render(
hbs`<WorkerFilter @name='egress_worker_filter' @model={{this.model}} @onInput={{this.onInput}} @submit={{this.submit}} @cancel={{this.cancel}} />`,
);
assert.dom('[data-test-code-editor-field-editor]').isVisible();
assert
.dom('[data-test-code-editor-field-editor] .CodeMirror-line')
.hasText(this.model.egress_worker_filter);
});
test('it renders correct content when ingress_worker_filter is passed in', async function (assert) {
this.onInput = () => {};
this.submit = () => {};
this.cancel = () => {};
this.model = { ingress_worker_filter: 'ingress filter' };
await render(
hbs`<WorkerFilter @name='ingress_worker_filter' @model={{this.model}} @onInput={{this.onInput}} @submit={{this.submit}} @cancel={{this.cancel}}/>`,
);
assert.dom('[data-test-code-editor-field-editor]').isVisible();
assert
.dom('[data-test-code-editor-field-editor] .CodeMirror-line')
.hasText(this.model.ingress_worker_filter);
});
});
@@ -0,0 +1,22 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module(
'Unit | Controller | scopes/scope/credential-stores/credential-store/edit-worker-filter',
function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let controller = this.owner.lookup(
'controller:scopes/scope/credential-stores/credential-store/edit-worker-filter',
);
assert.ok(controller);
assert.ok(controller.credentialStores);
});
},
);
@@ -0,0 +1,22 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module(
'Unit | Controller | scopes/scope/credential-stores/credential-store/worker-filter',
function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let controller = this.owner.lookup(
'controller:scopes/scope/credential-stores/credential-store/worker-filter',
);
assert.ok(controller);
assert.ok(controller.credentialStores);
});
},
);
@@ -0,0 +1,21 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupTest } from 'admin/tests/helpers';
module(
'Unit | Route | scopes/scope/credential-stores/credential-store/edit-worker-filter',
function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let route = this.owner.lookup(
'route:scopes/scope/credential-stores/credential-store/edit-worker-filter',
);
assert.ok(route);
});
},
);
@@ -0,0 +1,21 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupTest } from 'admin/tests/helpers';
module(
'Unit | Route | scopes/scope/credential-stores/credential-store/worker-filter',
function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let route = this.owner.lookup(
'route:scopes/scope/credential-stores/credential-store/worker-filter',
);
assert.ok(route);
});
},
);