Skip to content

Commit

Permalink
[Secrets Sync] split activation modal into separate component (#26822)
Browse files Browse the repository at this point in the history
* add new SyncActivationModal component

* secrets overview: use SyncActivationModal

* tests: add SyncActivationModal tests

* int/sync/secrets/overview-test: remove redundant tests now handled by modal tests

* cleanup: reorganize sync selectors

* chore: will i ever remember copyright headers? nah
  • Loading branch information
Noelle Daley committed May 22, 2024
1 parent 1884267 commit c5a0040
Show file tree
Hide file tree
Showing 8 changed files with 295 additions and 123 deletions.
47 changes: 11 additions & 36 deletions ui/lib/sync/addon/components/secrets/page/overview.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,13 @@
{{/if}}
{{/unless}}

{{! error message if post to activated endpoint fails }}
{{#if this.error}}
{{#if this.activationError}}
{{#unless this.hideError}}
<MessageError @errorMessage={{this.error}} @onDismiss={{fn (mut this.hideError) true}} data-test-opt-in-error />
<MessageError
@errorMessage={{this.activationError}}
@onDismiss={{fn (mut this.hideError) true}}
data-test-opt-in-error
/>
{{/unless}}
{{/if}}

Expand Down Expand Up @@ -198,37 +201,9 @@
{{/if}}

{{#if this.showActivateSecretsSyncModal}}
<Hds::Modal @onClose={{fn (mut this.showActivateSecretsSyncModal) false}} data-test-secrets-sync-opt-in-modal as |M|>
<M.Header @icon="alert-triangle">
Enable Secrets Sync feature
</M.Header>
<M.Body>
<p class="has-bottom-margin-m">
By enabling the Secrets Sync feature you may incur additional costs. Please review our
<Hds::Link::Inline
@isHrefExternal={{true}}
@href={{doc-link "/hcp/docs/vault/what-is-hcp-vault/client#secrets-sync"}}
>documentation</Hds::Link::Inline>
to learn more.
</p>
<Hds::Form::Checkbox::Field
{{on "change" (fn (mut this.hasConfirmedDocs) (not this.hasConfirmedDocs))}}
data-test-opt-in-check
as |F|
>
<F.Label>I've read the above linked document</F.Label>
</Hds::Form::Checkbox::Field>
</M.Body>
<M.Footer>
<Hds::ButtonSet>
<Hds::Button
data-test-opt-in-confirm
@text="Confirm"
disabled={{(not this.hasConfirmedDocs)}}
{{on "click" (perform this.onFeatureConfirm)}}
/>
<Hds::Button data-test-opt-in-cancel @text="Cancel" @color="secondary" {{on "click" this.resetOptInModal}} />
</Hds::ButtonSet>
</M.Footer>
</Hds::Modal>
<Secrets::SyncActivationModal
@onClose={{fn (mut this.showActivateSecretsSyncModal) false}}
@onError={{this.onModalError}}
@isHvdManaged={{@isHvdManaged}}
/>
{{/if}}
32 changes: 3 additions & 29 deletions ui/lib/sync/addon/components/secrets/page/overview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,11 @@ import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { service } from '@ember/service';
import { task } from 'ember-concurrency';
import { waitFor } from '@ember/test-waiters';
import { action } from '@ember/object';
import errorMessage from 'vault/utils/error-message';
import Ember from 'ember';

import type FlashMessageService from 'vault/services/flash-messages';
import type StoreService from 'vault/services/store';
import type RouterService from '@ember/routing/router-service';
import type VersionService from 'vault/services/version';
import type FlagsService from 'vault/services/flags';
import type { SyncDestinationAssociationMetrics } from 'vault/vault/adapters/sync/association';
Expand All @@ -31,15 +28,13 @@ interface Args {
export default class SyncSecretsDestinationsPageComponent extends Component<Args> {
@service declare readonly flashMessages: FlashMessageService;
@service declare readonly store: StoreService;
@service declare readonly router: RouterService;
@service declare readonly version: VersionService;
@service declare readonly flags: FlagsService;

@tracked destinationMetrics: SyncDestinationAssociationMetrics[] = [];
@tracked page = 1;
@tracked showActivateSecretsSyncModal = false;
@tracked hasConfirmedDocs = false;
@tracked error = null;
@tracked activationError: null | string = null;
// eventually remove when we deal with permissions on activation-features
@tracked hideOptIn = false;
@tracked hideError = false;
Expand Down Expand Up @@ -67,28 +62,7 @@ export default class SyncSecretsDestinationsPageComponent extends Component<Args
});

@action
resetOptInModal() {
this.showActivateSecretsSyncModal = false;
this.hasConfirmedDocs = false;
}

@task
@waitFor
*onFeatureConfirm() {
// must return null instead of root for non managed cluster.
// child namespaces are not sent.
const namespace = this.args.isHvdManaged ? 'admin' : null;
try {
yield this.store
.adapterFor('application')
.ajax('/v1/sys/activation-flags/secrets-sync/activate', 'POST', { namespace });
// must refresh and not transition because transition does not refresh the model from within a namespace
yield this.router.refresh();
} catch (error) {
this.error = errorMessage(error);
this.flashMessages.danger(`Error enabling feature \n ${errorMessage(error)}`);
} finally {
this.resetOptInModal();
}
onModalError(errorMsg: string) {
this.activationError = errorMsg;
}
}
38 changes: 38 additions & 0 deletions ui/lib/sync/addon/components/secrets/sync-activation-modal.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
~}}

<Hds::Modal @onClose={{@onClose}} data-test-secrets-sync-opt-in-modal as |M|>
<M.Header @icon="alert-triangle">
Enable Secrets Sync feature
</M.Header>
<M.Body>
<p class="has-bottom-margin-m">
By enabling the Secrets Sync feature you may incur additional costs. Please review our
<Hds::Link::Inline
@isHrefExternal={{true}}
@href={{doc-link "/hcp/docs/vault/what-is-hcp-vault/client#secrets-sync"}}
>documentation</Hds::Link::Inline>
to learn more.
</p>
<Hds::Form::Checkbox::Field
{{on "change" (fn (mut this.hasConfirmedDocs) (not this.hasConfirmedDocs))}}
data-test-opt-in-check
as |F|
>
<F.Label>I've read the above linked document</F.Label>
</Hds::Form::Checkbox::Field>
</M.Body>
<M.Footer>
<Hds::ButtonSet>
<Hds::Button
data-test-opt-in-confirm
@text="Confirm"
disabled={{not this.hasConfirmedDocs}}
{{on "click" (perform this.onFeatureConfirm)}}
/>
<Hds::Button data-test-opt-in-cancel @text="Cancel" @color="secondary" {{on "click" @onClose}} />
</Hds::ButtonSet>
</M.Footer>
</Hds::Modal>
50 changes: 50 additions & 0 deletions ui/lib/sync/addon/components/secrets/sync-activation-modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { service } from '@ember/service';
import { task } from 'ember-concurrency';
import { waitFor } from '@ember/test-waiters';
import errorMessage from 'vault/utils/error-message';

import type FlashMessageService from 'vault/services/flash-messages';
import type StoreService from 'vault/services/store';
import type RouterService from '@ember/routing/router-service';

interface Args {
onClose: () => void;
onError: (errorMessage: string) => void;
isHvdManaged: boolean;
}

export default class SyncActivationModal extends Component<Args> {
@service declare readonly flashMessages: FlashMessageService;
@service declare readonly store: StoreService;
@service declare readonly router: RouterService;

@tracked hasConfirmedDocs = false;

@task
@waitFor
*onFeatureConfirm() {
// must return null instead of root for non managed cluster.
// child namespaces are not sent.
const namespace = this.args.isHvdManaged ? 'admin' : null;

try {
yield this.store
.adapterFor('application')
.ajax('/v1/sys/activation-flags/secrets-sync/activate', 'POST', { namespace });
// must refresh and not transition because transition does not refresh the model from within a namespace
yield this.router.refresh();
} catch (error) {
this.args.onError(errorMessage(error));
this.flashMessages.danger(`Error enabling feature \n ${errorMessage(error)}`);
} finally {
this.args.onClose();
}
}
}
30 changes: 17 additions & 13 deletions ui/tests/acceptance/sync/secrets/overview-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,20 @@ module('Acceptance | sync | overview', function (hooks) {
.dom(ts.cta.button)
.doesNotExist('create first destination is not available until feature has been activated');

assert.dom(ts.overview.optInBanner).exists();
await click(ts.overview.optInBannerEnable);
assert.dom(ts.overview.optInBanner.container).exists();
await click(ts.overview.optInBanner.enable);

assert.dom(ts.overview.optInModal).exists('modal to opt-in and activate feature is shown');
await click(ts.overview.optInCheck);
await click(ts.overview.optInConfirm);
assert
.dom(ts.overview.activationModal.container)
.exists('modal to opt-in and activate feature is shown');
await click(ts.overview.activationModal.checkbox);
await click(ts.overview.activationModal.confirm);

assert.dom(ts.overview.optInModal).doesNotExist('modal is gone once activation has been submitted');
assert
.dom(ts.overview.optInBanner)
.dom(ts.overview.activationModal.container)
.doesNotExist('modal is gone once activation has been submitted');
assert
.dom(ts.overview.optInBanner.container)
.doesNotExist('opt-in banner is gone once activation has been submitted');

await click(ts.cta.button);
Expand Down Expand Up @@ -172,9 +176,9 @@ module('Acceptance | sync | overview', function (hooks) {
assert.dom('[data-test-badge-namespace]').hasText('foo');

await click(ts.navLink('Secrets Sync'));
await click(ts.overview.optInBannerEnable);
await click(ts.overview.optInCheck);
await click(ts.overview.optInConfirm);
await click(ts.overview.optInBanner.enable);
await click(ts.overview.activationModal.checkbox);
await click(ts.overview.activationModal.confirm);
});

test('it should make activation-flag requests to correct namespace when managed', async function (assert) {
Expand Down Expand Up @@ -204,9 +208,9 @@ module('Acceptance | sync | overview', function (hooks) {
assert.dom('[data-test-badge-namespace]').hasText('foo');

await click(ts.navLink('Secrets Sync'));
await click(ts.overview.optInBannerEnable);
await click(ts.overview.optInCheck);
await click(ts.overview.optInConfirm);
await click(ts.overview.optInBanner.enable);
await click(ts.overview.activationModal.checkbox);
await click(ts.overview.activationModal.confirm);
});
});
});
20 changes: 12 additions & 8 deletions ui/tests/helpers/sync/sync-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,18 @@ export const PAGE = {
},
},
overview: {
optInBanner: '[data-test-secrets-sync-opt-in-banner]',
optInBannerEnable: '[data-test-secrets-sync-opt-in-banner-enable]',
optInBannerDescription: '[data-test-secrets-sync-opt-in-banner-description]',
optInDismiss: '[data-test-secrets-sync-opt-in-banner] [data-test-icon="x"]',
optInModal: '[data-test-secrets-sync-opt-in-modal]',
optInCheck: '[data-test-opt-in-check]',
optInConfirm: '[data-test-opt-in-confirm]',
optInCancel: '[data-test-opt-in-cancel]',
optInBanner: {
container: '[data-test-secrets-sync-opt-in-banner]',
enable: '[data-test-secrets-sync-opt-in-banner-enable]',
description: '[data-test-secrets-sync-opt-in-banner-description]',
dismiss: '[data-test-secrets-sync-opt-in-banner] [data-test-icon="x"]',
},
activationModal: {
container: '[data-test-secrets-sync-opt-in-modal]',
checkbox: '[data-test-opt-in-check]',
confirm: '[data-test-opt-in-confirm]',
cancel: '[data-test-opt-in-cancel]',
},
optInError: '[data-test-opt-in-error]',
createDestination: '[data-test-create-destination]',
table: {
Expand Down
Loading

0 comments on commit c5a0040

Please sign in to comment.