From 2869020e65cd400a277b64e742eed8a38ded4ac0 Mon Sep 17 00:00:00 2001 From: Cameron Perera Date: Wed, 11 Dec 2024 14:00:27 -0600 Subject: [PATCH] Llb/worker filter improvements (#2614) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 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 * 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 * 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 * 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 Co-authored-by: Lauren Co-authored-by: laurenolivia Co-authored-by: lisbet-alvarez --- addons/core/translations/en-us.yaml | 24 +++ addons/core/translations/form/en-us.yaml | 2 + addons/core/translations/resources/en-us.yaml | 15 +- .../credential-store/actions/index.hbs | 6 + .../credential-store/nav/index.hbs | 7 + .../form/credential-store/vault/index.hbs | 29 --- .../form/storage-bucket/aws/index.hbs | 59 +++--- .../form/storage-bucket/aws/index.js | 5 +- .../form/storage-bucket/minio/index.hbs | 60 +++--- .../components/form/worker-filter/index.hbs | 21 ++ .../worker-filter-generator/index.hbs | 198 ++++++++++++++++++ .../worker-filter-generator/index.js | 70 +++++++ .../app/components/worker-filter/index.hbs | 41 ---- .../app/components/worker-filter/index.js | 22 -- .../credential-store/edit-worker-filter.js | 10 + .../credential-store/worker-filter.js | 10 + .../scopes/scope/credential-stores/index.js | 27 +++ ui/admin/app/router.js | 2 + .../credential-store/edit-worker-filter.js | 8 + .../credential-store/worker-filter.js | 8 + ui/admin/app/styles/app.scss | 27 +++ .../credential-store/edit-worker-filter.hbs | 37 ++++ .../credential-store/worker-filter.hbs | 72 +++++++ .../target/edit-egress-worker-filter.hbs | 4 +- .../target/edit-ingress-worker-filter.hbs | 4 +- .../scopes/scope/targets/target/workers.hbs | 20 +- ui/admin/config/environment.js | 4 +- .../acceptance/credential-store/selectors.js | 13 ++ .../credential-store/update-test.js | 67 ++++++ .../acceptance/storage-buckets/create-test.js | 36 +++- .../acceptance/storage-buckets/selectors.js | 5 + .../acceptance/storage-buckets/update-test.js | 36 +++- .../create-storage-bucket-test.js | 8 +- .../tests/acceptance/targets/workers-test.js | 34 +++ .../worker-filter-generator/index-test.js | 135 ++++++++++++ .../components/worker-filter/index-test.js | 45 ---- .../edit-worker-filter-test.js | 22 ++ .../credential-store/worker-filter-test.js | 22 ++ .../edit-worker-filter-test.js | 21 ++ .../credential-store/worker-filter-test.js | 21 ++ 40 files changed, 1045 insertions(+), 212 deletions(-) create mode 100644 ui/admin/app/components/form/worker-filter/index.hbs create mode 100644 ui/admin/app/components/worker-filter-generator/index.hbs create mode 100644 ui/admin/app/components/worker-filter-generator/index.js delete mode 100644 ui/admin/app/components/worker-filter/index.hbs delete mode 100644 ui/admin/app/components/worker-filter/index.js create mode 100644 ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/edit-worker-filter.js create mode 100644 ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/worker-filter.js create mode 100644 ui/admin/app/routes/scopes/scope/credential-stores/credential-store/edit-worker-filter.js create mode 100644 ui/admin/app/routes/scopes/scope/credential-stores/credential-store/worker-filter.js create mode 100644 ui/admin/app/templates/scopes/scope/credential-stores/credential-store/edit-worker-filter.hbs create mode 100644 ui/admin/app/templates/scopes/scope/credential-stores/credential-store/worker-filter.hbs create mode 100644 ui/admin/tests/acceptance/credential-store/selectors.js create mode 100644 ui/admin/tests/integration/components/worker-filter-generator/index-test.js delete mode 100644 ui/admin/tests/integration/components/worker-filter/index-test.js create mode 100644 ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/edit-worker-filter-test.js create mode 100644 ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/worker-filter-test.js create mode 100644 ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/edit-worker-filter-test.js create mode 100644 ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/worker-filter-test.js diff --git a/addons/core/translations/en-us.yaml b/addons/core/translations/en-us.yaml index d398d2854..3cff6634e 100644 --- a/addons/core/translations/en-us.yaml +++ b/addons/core/translations/en-us.yaml @@ -133,3 +133,27 @@ settings: alerts: cache-daemon: There may be a problem with the cache daemon client-agent: There may be a problem with the client agent +worker-filter-generator: + toggle: + title: Show filter generator + description: The generator will automatically generate a pre-formatted filter for you. You can copy the result to the editor above. + filter-generator: + title: Filter generator + description: Choose what you want to format into a filter. + link: Learn more about formatting a worker filter. + input-values: + title: Input values + description: Next, input what you want to format. + formatted-result: + title: Formatted result + description: Copy and paste this into the editor above. + label: Generated result + tag: + label: Tag + helper: Format a worker tag + name: + label: Name + helper: Format a worker name + operator: + matches: matches + contains: contains diff --git a/addons/core/translations/form/en-us.yaml b/addons/core/translations/form/en-us.yaml index d3cfc20d0..8be1621eb 100644 --- a/addons/core/translations/form/en-us.yaml +++ b/addons/core/translations/form/en-us.yaml @@ -136,6 +136,8 @@ key: label: Key value: label: Value +operator: + label: Operator callback_url: label: Callback URL help: The URL to which the third-party provider sends the authentication response. Paste this into the provider configuration. diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index c09d23d66..9098e9a7c 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -502,7 +502,7 @@ storage-bucket: help: The secret access key generated by your storage provider for the IAM user to use with this storage bucket. We will not show this data after it is saved, but you can replace it. worker_filter: label: Worker filter - help: Filter for Boundary workers that have network access to your storage. Boolean expression. + help: Filters to the worker(s) that can handle requests for this storage bucket. disable_credential_rotation: label: Disable credential rotation help: Disabling credential rotation could allow entities outside of Boundary to know the client secret in use. @@ -819,6 +819,7 @@ target: ingress-workers: Ingress workers actions: edit-worker-filter: Edit worker filter + add-worker-filter: Add worker filter credential-store: title: Credential Store title_plural: Credential Stores @@ -828,8 +829,20 @@ credential-store: title: Welcome to Credential Stores titles: new: New Credential Store + worker-filter: + title: Worker Filter + description: The worker filter format for this credential store. + messages: + none: + title: No worker filter added + description: You haven't added a worker filter yet. + edit: + title: Edit Worker Filter + description: Specify workers that have access to the credential store. actions: + add: Add Worker Filter delete: Delete Credential Store + edit: Edit Worker Filter types: vault: Vault static: Static diff --git a/ui/admin/app/components/credential-stores/credential-store/actions/index.hbs b/ui/admin/app/components/credential-stores/credential-store/actions/index.hbs index f205e1110..d7b116d16 100644 --- a/ui/admin/app/components/credential-stores/credential-store/actions/index.hbs +++ b/ui/admin/app/components/credential-stores/credential-store/actions/index.hbs @@ -18,6 +18,12 @@ {{t 'resources.credential.actions.create'}} {{/if}} + {{#if (and (feature-flag 'vault-worker-filter') @model.isVault)}} + + {{/if}} {{#if (can 'delete model' @model)}} diff --git a/ui/admin/app/components/credential-stores/credential-store/nav/index.hbs b/ui/admin/app/components/credential-stores/credential-store/nav/index.hbs index 828895fab..307868cc0 100644 --- a/ui/admin/app/components/credential-stores/credential-store/nav/index.hbs +++ b/ui/admin/app/components/credential-stores/credential-store/nav/index.hbs @@ -8,6 +8,13 @@ {{t 'titles.details'}} {{#if @model.isVault}} + {{#if (feature-flag 'vault-worker-filter')}} + + {{t 'resources.credential-store.worker-filter.title'}} + + {{/if}} {{#if (can 'navigate model' @model collection='credential-libraries')}} - {{#if (feature-flag 'vault-worker-filter')}} - - - {{t 'resources.credential-store.form.worker_filter.label'}} - - - {{t 'resources.credential-store.form.worker_filter.help'}} - - {{t 'actions.learn-more'}} - - - {{#if @model.errors.worker_filter}} - - {{#each @model.errors.worker_filter as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - - {{/if}} - {{#if (or @model.isNew form.isEditable)}} - {{t 'resources.storage-bucket.form.worker_filter.label'}} - - {{t 'resources.storage-bucket.form.worker_filter.help'}} - - {{t 'actions.learn-more'}} - - - {{#if @model.errors.worker_filter}} - - {{#each @model.errors.worker_filter as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - + {{#let (unique-id) (unique-id) as |labelId helpId|}} + + {{t + 'resources.storage-bucket.form.worker_filter.label' + }} + + {{t 'resources.storage-bucket.form.worker_filter.help'}} + + {{t 'actions.learn-more'}} + + + + {{#if form.disabled}} + + {{else}} + + {{/if}} + + + {{/let}} {{! JUST for static credentials }} {{#unless this.showDynamicCredentials}} diff --git a/ui/admin/app/components/form/storage-bucket/aws/index.js b/ui/admin/app/components/form/storage-bucket/aws/index.js index 9671c701c..3d8d43bd3 100644 --- a/ui/admin/app/components/form/storage-bucket/aws/index.js +++ b/ui/admin/app/components/form/storage-bucket/aws/index.js @@ -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) { diff --git a/ui/admin/app/components/form/storage-bucket/minio/index.hbs b/ui/admin/app/components/form/storage-bucket/minio/index.hbs index d9011a5aa..1761c2701 100644 --- a/ui/admin/app/components/form/storage-bucket/minio/index.hbs +++ b/ui/admin/app/components/form/storage-bucket/minio/index.hbs @@ -315,30 +315,42 @@ {{/if}} {{! worker_filter }} - - {{t 'resources.storage-bucket.form.worker_filter.label'}} - - {{t 'resources.storage-bucket.form.worker_filter.help'}} - - {{t 'actions.learn-more'}} - - - {{#if @model.errors.worker_filter}} - - {{#each @model.errors.worker_filter as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - + {{#let (unique-id) (unique-id) as |labelId helpId|}} + + {{t + 'resources.storage-bucket.form.worker_filter.label' + }} + + {{t 'resources.storage-bucket.form.worker_filter.help'}} + + {{t 'actions.learn-more'}} + + + + {{#if form.disabled}} + + {{else}} + + {{/if}} + + + {{/let}} {{! disable_credential_rotation }} + + + {{#if (can 'save model' @model)}} + + {{/if}} + \ No newline at end of file diff --git a/ui/admin/app/components/worker-filter-generator/index.hbs b/ui/admin/app/components/worker-filter-generator/index.hbs new file mode 100644 index 000000000..54306a42f --- /dev/null +++ b/ui/admin/app/components/worker-filter-generator/index.hbs @@ -0,0 +1,198 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + + + + + {{#unless @hideToolbar}} + + {{/unless}} + + + + {{#if (get @model.errors @name)}} + + {{#each (get @model.errors @name) as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + + + {{t 'worker-filter-generator.toggle.title'}} + {{t + 'worker-filter-generator.toggle.description' + }} + + + + +{{#if this.showFilterGenerator}} + + {{t 'worker-filter-generator.filter-generator.title'}} + + {{t 'worker-filter-generator.filter-generator.description'}} +
+ + {{t 'worker-filter-generator.filter-generator.link'}} + +
+ + {{t + 'worker-filter-generator.filter-generator.tag.label' + }} + {{t + 'worker-filter-generator.filter-generator.tag.helper' + }} + + + {{t + 'worker-filter-generator.filter-generator.name.label' + }} + {{t + 'worker-filter-generator.filter-generator.name.helper' + }} + +
+ + {{#let (unique-id) (unique-id) as |labelId helpId|}} + + {{t + 'worker-filter-generator.filter-generator.input-values.title' + }} + {{t + 'worker-filter-generator.filter-generator.input-values.description' + }} + + {{#if (eq this.selectedGeneratorType this.generatorTagType)}} + + {{t 'form.key.label'}} + + {{else}} + + {{t 'form.operator.label'}} + + + {{#each this.operatorOptions as |operator|}} + + {{/each}} + + + {{/if}} + + + + {{t 'form.value.label'}} + + + + {{/let}} + + {{#let (unique-id) (unique-id) as |labelId helpId|}} + + {{t + 'worker-filter-generator.filter-generator.formatted-result.title' + }} + {{t + 'worker-filter-generator.filter-generator.formatted-result.description' + }} + + {{t + 'worker-filter-generator.filter-generator.formatted-result.label' + }} +
+ + {{#if this.generatedResult}} + + {{/if}} +
+
+
+ {{/let}} +{{/if}} \ No newline at end of file diff --git a/ui/admin/app/components/worker-filter-generator/index.js b/ui/admin/app/components/worker-filter-generator/index.js new file mode 100644 index 000000000..bd3afe7e7 --- /dev/null +++ b/ui/admin/app/components/worker-filter-generator/index.js @@ -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; + } +} diff --git a/ui/admin/app/components/worker-filter/index.hbs b/ui/admin/app/components/worker-filter/index.hbs deleted file mode 100644 index ed65622f2..000000000 --- a/ui/admin/app/components/worker-filter/index.hbs +++ /dev/null @@ -1,41 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -}} - - - - - - - - - - {{#if (get @model.errors @name)}} - - {{#each (get @model.errors @name) as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - - - - \ No newline at end of file diff --git a/ui/admin/app/components/worker-filter/index.js b/ui/admin/app/components/worker-filter/index.js deleted file mode 100644 index 4da609eb1..000000000 --- a/ui/admin/app/components/worker-filter/index.js +++ /dev/null @@ -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; - } -} diff --git a/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/edit-worker-filter.js b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/edit-worker-filter.js new file mode 100644 index 000000000..136049535 --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/edit-worker-filter.js @@ -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; +} diff --git a/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/worker-filter.js b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/worker-filter.js new file mode 100644 index 000000000..82616dedc --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/worker-filter.js @@ -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; +} diff --git a/ui/admin/app/controllers/scopes/scope/credential-stores/index.js b/ui/admin/app/controllers/scopes/scope/credential-stores/index.js index fd60e8f17..ad9b2b874 100644 --- a/ui/admin/app/controllers/scopes/scope/credential-stores/index.js +++ b/ui/admin/app/controllers/scopes/scope/credential-stores/index.js @@ -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', + ); + } } diff --git a/ui/admin/app/router.js b/ui/admin/app/router.js index 9d4a9af3a..1656c63a6 100644 --- a/ui/admin/app/router.js +++ b/ui/admin/app/router.js @@ -150,6 +150,8 @@ Router.map(function () { function () {}, ); }); + this.route('worker-filter'); + this.route('edit-worker-filter'); }, ); this.route('new'); diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/edit-worker-filter.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/edit-worker-filter.js new file mode 100644 index 000000000..751a6d0b5 --- /dev/null +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/edit-worker-filter.js @@ -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 {} diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/worker-filter.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/worker-filter.js new file mode 100644 index 000000000..0f32202e5 --- /dev/null +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/worker-filter.js @@ -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 {} diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index 2c98efe9d..3b0997be6 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -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; + } +} diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/edit-worker-filter.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/edit-worker-filter.hbs new file mode 100644 index 000000000..e45c15585 --- /dev/null +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/edit-worker-filter.hbs @@ -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') +}} + + + + + + + + + + {{t 'resources.credential-store.worker-filter.messages.edit.title'}} + + + + {{t 'resources.credential-store.worker-filter.messages.edit.description'}} + + + + + + + \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/worker-filter.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/worker-filter.hbs new file mode 100644 index 000000000..f3d1d3a3e --- /dev/null +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/worker-filter.hbs @@ -0,0 +1,72 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + +{{page-title (t 'resources.credential-store.worker-filter.title')}} + + + + + + + + + + + + + + + + + + + + + {{#if @model.worker_filter}} + + + {{t 'resources.credential-store.worker-filter.title'}} + + + {{t 'resources.credential-store.worker-filter.description'}} + + + {{else}} + + + + + + + + + + {{/if}} + + \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/targets/target/edit-egress-worker-filter.hbs b/ui/admin/app/templates/scopes/scope/targets/target/edit-egress-worker-filter.hbs index 21a17a612..3a5778062 100644 --- a/ui/admin/app/templates/scopes/scope/targets/target/edit-egress-worker-filter.hbs +++ b/ui/admin/app/templates/scopes/scope/targets/target/edit-egress-worker-filter.hbs @@ -17,7 +17,7 @@

{{t 'resources.target.workers.edit-egress-worker-filter.title'}} - +

{{t 'resources.target.workers.edit-egress-worker-filter.description' @@ -25,7 +25,7 @@ -

{{t 'resources.target.workers.edit-ingress-worker-filter.title'}} - +

{{t 'resources.target.workers.edit-ingress-worker-filter.description' @@ -26,7 +26,7 @@ - @@ -62,6 +66,7 @@ <:content> {{#if @model.ingress_worker_filter}} @@ -110,6 +121,7 @@ <:content> {{#if @model.egress_worker_filter}} + `[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); diff --git a/ui/admin/tests/integration/components/worker-filter-generator/index-test.js b/ui/admin/tests/integration/components/worker-filter-generator/index-test.js new file mode 100644 index 000000000..285bde339 --- /dev/null +++ b/ui/admin/tests/integration/components/worker-filter-generator/index-test.js @@ -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``, + ); + + 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``, + ); + + 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``, + ); + + 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``, + ); + 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``, + ); + 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``, + ); + + 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``, + ); + + 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``, + ); + + 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(); + }); + }, +); diff --git a/ui/admin/tests/integration/components/worker-filter/index-test.js b/ui/admin/tests/integration/components/worker-filter/index-test.js deleted file mode 100644 index be095b213..000000000 --- a/ui/admin/tests/integration/components/worker-filter/index-test.js +++ /dev/null @@ -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``, - ); - - 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``, - ); - - 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); - }); -}); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/edit-worker-filter-test.js b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/edit-worker-filter-test.js new file mode 100644 index 000000000..2a2981eb8 --- /dev/null +++ b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/edit-worker-filter-test.js @@ -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); + }); + }, +); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/worker-filter-test.js b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/worker-filter-test.js new file mode 100644 index 000000000..14ee81ec3 --- /dev/null +++ b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/worker-filter-test.js @@ -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); + }); + }, +); diff --git a/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/edit-worker-filter-test.js b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/edit-worker-filter-test.js new file mode 100644 index 000000000..8de8cb806 --- /dev/null +++ b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/edit-worker-filter-test.js @@ -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); + }); + }, +); diff --git a/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/worker-filter-test.js b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/worker-filter-test.js new file mode 100644 index 000000000..23f4e1eb3 --- /dev/null +++ b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/worker-filter-test.js @@ -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); + }); + }, +);