policy association form (#2089)
* feat: 🎸 add storage policy api and mirage
This commit is contained in:
1 parent
ee4e19ace7
commit
ba786d2172
16 files changed
+688
-17
No files matched your search
@@ -88,7 +88,29 @@ function routes() {
|
||||
this.get('/scopes/:id');
|
||||
this.patch('/scopes/:id');
|
||||
this.del('/scopes/:id');
|
||||
this.post(
|
||||
'/scopes/:idMethod',
|
||||
function ({ scopes }, { params: { idMethod } }) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
const id = idMethod.split(':')[0];
|
||||
const method = idMethod.split(':')[1];
|
||||
const scope = scopes.find(id);
|
||||
|
||||
const updatedAttrs = {
|
||||
version: attrs.version,
|
||||
storagePolicyId: attrs.storagePolicyId,
|
||||
};
|
||||
|
||||
if (method === 'attach-storage-policy') {
|
||||
updatedAttrs.storagePolicyId = attrs.storagePolicyId;
|
||||
}
|
||||
|
||||
if (method === 'detach-storage-policy') {
|
||||
updatedAttrs.storagePolicyId = '';
|
||||
}
|
||||
return scope.update(updatedAttrs);
|
||||
},
|
||||
);
|
||||
// Auth & IAM resources
|
||||
|
||||
this.get(
|
||||
|
||||
@@ -922,13 +922,21 @@ policy:
|
||||
new: New Storage Policy
|
||||
policy_name: Policy name
|
||||
retain_for: Retain for
|
||||
delete_after: Delete After
|
||||
delete_after: Delete after
|
||||
do_not_delete: Do not delete
|
||||
forever: Forever
|
||||
days: days
|
||||
policy_description: Store recorded sessions for at least this long.
|
||||
create_policy_description: Enable session recording for projects in this org.
|
||||
actions:
|
||||
delete: Delete Storage Policy
|
||||
add: Add Storage Policy
|
||||
apply: Apply the storage policy to this scope and its children
|
||||
new: Add a new storage policy
|
||||
questions:
|
||||
detach: Are you sure you want to detach this storage policy?
|
||||
messages:
|
||||
detach: Successfully detached
|
||||
none:
|
||||
title: No storage policies yet
|
||||
description: Storage policies define how long to store recordings before they are automatically deleted. We recommend checking your data retention policies for compliance requirements.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
}}
|
||||
|
||||
<Rose::Form
|
||||
class='full-width'
|
||||
@onSubmit={{@submit}}
|
||||
@cancel={{@cancel}}
|
||||
as |form|
|
||||
>
|
||||
<Hds::Form::Select::Field
|
||||
class='select-policy'
|
||||
@isRequired={{true}}
|
||||
@isInvalid={{@model.errors.storage_policy_id}}
|
||||
@value={{@model.storage_policy_id}}
|
||||
@type='text'
|
||||
@width='30%'
|
||||
name='policy_id'
|
||||
{{on 'change' this.selectPolicy}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'resources.policy.title'}}</F.Label>
|
||||
<F.HelperText>
|
||||
{{t 'resources.policy.actions.apply'}}
|
||||
</F.HelperText>
|
||||
<F.Options>
|
||||
<option disabled hidden selected value=''>{{t
|
||||
'titles.choose-an-option'
|
||||
}}</option>
|
||||
{{#each @policyList as |policy|}}
|
||||
<option
|
||||
value={{policy.id}}
|
||||
selected={{eq @model.storage_policy_id policy.id}}
|
||||
>
|
||||
{{policy.displayName}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</F.Options>
|
||||
{{#if @model.errors.storage_policy_id}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.storage_policy_id as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::Select::Field>
|
||||
|
||||
<Hds::Link::Standalone
|
||||
class='add-policy-link'
|
||||
@color='primary'
|
||||
@icon='plus'
|
||||
@text={{t 'resources.policy.actions.new'}}
|
||||
@route='scopes.scope.add-storage-policy.create'
|
||||
/>
|
||||
<form.actions
|
||||
@enableEditText={{t 'actions.edit-form'}}
|
||||
@submitText={{t 'actions.save'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
|
||||
</Rose::Form>
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class FormAddStoragePolicyIndexComponent extends Component {
|
||||
//actions
|
||||
|
||||
/**
|
||||
* select storage policy
|
||||
*/
|
||||
@action
|
||||
selectPolicy({ target: { value: policyId } }) {
|
||||
this.args.model.storage_policy_id = policyId;
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,9 @@ Router.map(function () {
|
||||
this.route('new');
|
||||
});
|
||||
this.route('edit');
|
||||
this.route('add-storage-policy', function () {
|
||||
this.route('create');
|
||||
});
|
||||
|
||||
this.route('users', function () {
|
||||
this.route('user', { path: ':user_id' }, function () {
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { A } from '@ember/array';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { loading } from 'ember-loading';
|
||||
import { confirm } from 'core/decorators/confirm';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesRoute extends Route {
|
||||
// =services
|
||||
@@ -33,4 +37,19 @@ export default class ScopesRoute extends Route {
|
||||
// expected to default to the global scope, thus returning org scopes.
|
||||
return this.store.findAll('scope').catch(() => A([]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the scope and redirects to index.
|
||||
* @param {Model} scope
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@confirm('resources.policy.questions.detach')
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess('resources.policy.messages.detach')
|
||||
async detachStoragePolicy(scope) {
|
||||
const { storage_policy_id } = scope;
|
||||
await scope.detachStoragePolicy(storage_policy_id);
|
||||
scope.storage_policy_id = '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { TYPE_POLICY } from 'api/models/policy';
|
||||
import { action } from '@ember/object';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
import { loading } from 'ember-loading';
|
||||
|
||||
export default class ScopesScopeAddStoragePolicyCreateRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service store;
|
||||
@service router;
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Creates a new unsaved policy.
|
||||
* @return {Policy}
|
||||
*/
|
||||
model() {
|
||||
const scopeModel = this.store.peekRecord('scope', 'global');
|
||||
const record = this.store.createRecord('policy', {
|
||||
type: TYPE_POLICY,
|
||||
});
|
||||
record.scopeModel = scopeModel;
|
||||
return record;
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Handle save
|
||||
* @param {PolicyModel} policy
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess('notifications.save-success')
|
||||
async save(policy) {
|
||||
await policy.save();
|
||||
await this.router.transitionTo('scopes.scope.add-storage-policy');
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback changes on policies.
|
||||
* @param {PolicyModel} policy
|
||||
*/
|
||||
@action
|
||||
cancel(policy) {
|
||||
policy.rollbackAttributes();
|
||||
this.router.transitionTo('scopes.scope.add-storage-policy');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { loading } from 'ember-loading';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesScopeAddStoragePolicyIndexRoute extends Route {
|
||||
// =services
|
||||
@service store;
|
||||
@service router;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Load polcies from global and current scope
|
||||
* @param {Model} model
|
||||
*/
|
||||
async afterModel() {
|
||||
const { id: scope_id } = this.modelFor('scopes.scope');
|
||||
|
||||
//fetch policies from global scope
|
||||
const globalScopePolicies = await this.store.query('policy', {
|
||||
scope_id: 'global',
|
||||
});
|
||||
|
||||
//fetch policies from org scope
|
||||
const orgScopePolicies = await this.store.query('policy', {
|
||||
scope_id,
|
||||
});
|
||||
|
||||
const policyList = [...globalScopePolicies, ...orgScopePolicies];
|
||||
|
||||
this.policyList = policyList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds `policyList` to the context.
|
||||
* @param {Controller} controller
|
||||
*/
|
||||
setupController(controller) {
|
||||
super.setupController(...arguments);
|
||||
controller.set('policyList', this.policyList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the scope and redirects to index.
|
||||
* @param {Model} scope
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.save-success')
|
||||
async attachStoragePolicy(scope) {
|
||||
const { storage_policy_id } = scope;
|
||||
if (storage_policy_id) {
|
||||
await scope.attachStoragePolicy(storage_policy_id);
|
||||
}
|
||||
await scope.save();
|
||||
await this.router.transitionTo('scopes.scope.edit', scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback changes on add storage policy.
|
||||
* @param {PolicyModel} policy
|
||||
*/
|
||||
@action
|
||||
cancel(scope) {
|
||||
scope.rollbackAttributes();
|
||||
this.router.transitionTo('scopes.scope.edit', scope);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export default class ScopesScopeEditRoute extends Route {
|
||||
|
||||
@service session;
|
||||
@service router;
|
||||
|
||||
@service store;
|
||||
// =methods
|
||||
|
||||
/**
|
||||
@@ -20,4 +20,18 @@ export default class ScopesScopeEditRoute extends Route {
|
||||
beforeModel() {
|
||||
if (!this.session.isAuthenticated) this.router.transitionTo('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds storage policy name to the context.
|
||||
* @param {Controller} controller
|
||||
* @param {PolicyModel} policy
|
||||
*/
|
||||
async setupController(controller, policy) {
|
||||
super.setupController(...arguments);
|
||||
if (policy.storage_policy_id) {
|
||||
const { storage_policy_id } = policy;
|
||||
const record = await this.store.findRecord('policy', storage_policy_id);
|
||||
controller.set('storage_policy', record);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -985,3 +985,37 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// policy sidebar
|
||||
|
||||
.policy-sidebar {
|
||||
background-color: var(--token-form-control-disabled-surface-color);
|
||||
padding: 1rem;
|
||||
width: 380px;
|
||||
|
||||
.hds-card__container > ul {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.hds-link-standalone__text {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
// add storage policy
|
||||
|
||||
.add-storage-policy {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.select-policy {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.add-policy-link {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
}}
|
||||
|
||||
{{page-title (t 'titles.new')}}
|
||||
<Breadcrumbs::Item
|
||||
@text={{t 'resources.policy.titles.new'}}
|
||||
@route='scopes.scope.add-storage-policy.create'
|
||||
/>
|
||||
|
||||
<Rose::Layout::Page as |page|>
|
||||
<page.breadcrumbs>
|
||||
<Breadcrumbs::Container />
|
||||
</page.breadcrumbs>
|
||||
|
||||
<page.header>
|
||||
<Hds::Text::Display @tag='h2'>
|
||||
{{t 'resources.policy.titles.new'}}
|
||||
</Hds::Text::Display>
|
||||
</page.header>
|
||||
|
||||
<page.body>
|
||||
<Form::Policy
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
/>
|
||||
</page.body>
|
||||
|
||||
</Rose::Layout::Page>
|
||||
@@ -0,0 +1,31 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
}}
|
||||
{{page-title (t 'titles.new')}}
|
||||
<Breadcrumbs::Item
|
||||
@text={{t 'resources.policy.actions.add'}}
|
||||
@route='scopes.scope.add-storage-policy'
|
||||
/>
|
||||
|
||||
<Rose::Layout::Page as |page|>
|
||||
<page.breadcrumbs>
|
||||
<Breadcrumbs::Container />
|
||||
</page.breadcrumbs>
|
||||
|
||||
<page.header>
|
||||
<Hds::Text::Display @tag='h1' class='add-storage-policy'>
|
||||
{{t 'resources.policy.actions.add'}}
|
||||
</Hds::Text::Display>
|
||||
</page.header>
|
||||
|
||||
<page.body>
|
||||
<Form::Scope::AddStoragePolicy
|
||||
@model={{@model}}
|
||||
@policyList={{this.policyList}}
|
||||
@submit={{route-action 'attachStoragePolicy' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
/>
|
||||
</page.body>
|
||||
|
||||
</Rose::Layout::Page>
|
||||
@@ -66,21 +66,120 @@
|
||||
{{/if}}
|
||||
|
||||
<page.body>
|
||||
{{#if (can 'read model' @model)}}
|
||||
<Form::Scope
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
/>
|
||||
{{else}}
|
||||
<Rose::Layout::Centered>
|
||||
<Rose::Message as |message|>
|
||||
<message.description>
|
||||
{{t 'resources.scope.messages.cannot_read'}}
|
||||
</message.description>
|
||||
</Rose::Message>
|
||||
</Rose::Layout::Centered>
|
||||
{{/if}}
|
||||
<Rose::Layout::BodyContent as |bc|>
|
||||
<bc.Body>
|
||||
{{#if (can 'read model' @model)}}
|
||||
<Form::Scope
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
/>
|
||||
{{else}}
|
||||
<Rose::Layout::Centered>
|
||||
<Rose::Message as |message|>
|
||||
<message.description>
|
||||
{{t 'resources.scope.messages.cannot_read'}}
|
||||
</message.description>
|
||||
</Rose::Message>
|
||||
</Rose::Layout::Centered>
|
||||
{{/if}}
|
||||
</bc.Body>
|
||||
{{#if (can 'attachStoragePolicy scope' @model)}}
|
||||
<bc.Sidebar>
|
||||
<div class='policy-sidebar'>
|
||||
<Hds::Text::Display @tag='h3'>
|
||||
{{t 'resources.policy.title'}}
|
||||
</Hds::Text::Display>
|
||||
|
||||
{{#if @model.storage_policy_id}}
|
||||
<Hds::Text::Body @tag='p' class='description'>
|
||||
{{t 'resources.policy.titles.policy_description'}}
|
||||
</Hds::Text::Body>
|
||||
{{else}}
|
||||
<Hds::Text::Body @tag='p' class='description'>
|
||||
{{t 'resources.policy.titles.create_policy_description'}}
|
||||
</Hds::Text::Body>
|
||||
{{/if}}
|
||||
{{#if @model.storage_policy_id}}
|
||||
<LinkListPanel as |P|>
|
||||
<P.Item
|
||||
@icon='hourglass'
|
||||
@color='boundary'
|
||||
@text={{this.storage_policy.name}}
|
||||
>
|
||||
{{#if this.storage_policy.retain_for.days}}
|
||||
<Hds::Text::Body
|
||||
@tag='p'
|
||||
@color='var(--ui-gray-subtler-1)'
|
||||
@size='100'
|
||||
>
|
||||
{{t 'resources.policy.titles.retain_for'}}
|
||||
{{#if (gte this.storage_policy.retain_for.days 1)}}
|
||||
{{format-day-year this.storage_policy.retain_for.days}}
|
||||
{{else if (eq this.storage_policy.retain_for.days -1)}}
|
||||
{{t 'resources.policy.titles.forever'}}
|
||||
{{/if}}
|
||||
</Hds::Text::Body>
|
||||
{{/if}}
|
||||
|
||||
{{#if (gt this.storage_policy.delete_after.days 0)}}
|
||||
<Hds::Text::Body
|
||||
@tag='p'
|
||||
@color='var(--ui-gray-subtler-1)'
|
||||
@size='100'
|
||||
>
|
||||
{{t 'resources.policy.titles.delete_after'}}
|
||||
{{format-day-year this.storage_policy.delete_after.days}}
|
||||
</Hds::Text::Body>
|
||||
{{/if}}
|
||||
</P.Item>
|
||||
{{#if (can 'detachStoragePolicy scope' @model)}}
|
||||
<P.Item>
|
||||
<Hds::Dropdown as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon='more-horizontal'
|
||||
@text={{t 'actions.manage'}}
|
||||
@hasChevron={{false}}
|
||||
@size='small'
|
||||
/>
|
||||
<dd.Interactive
|
||||
@route='scopes.scope.policies.policy'
|
||||
@model={{this.storage_policy.id}}
|
||||
@text={{t 'actions.view'}}
|
||||
/>
|
||||
<dd.Separator />
|
||||
<dd.Interactive
|
||||
@text={{t 'resources.policy.actions.delete'}}
|
||||
@color='critical'
|
||||
{{on
|
||||
'click'
|
||||
(route-action 'detachStoragePolicy' @model)
|
||||
}}
|
||||
/>
|
||||
</Hds::Dropdown>
|
||||
</P.Item>
|
||||
{{/if}}
|
||||
|
||||
</LinkListPanel>
|
||||
|
||||
<Hds::Link::Standalone
|
||||
@icon='arrow-right'
|
||||
@iconPosition='trailing'
|
||||
@text={{t 'resources.policy.actions.add'}}
|
||||
@route='scopes.scope.add-storage-policy'
|
||||
/>
|
||||
|
||||
{{else}}
|
||||
<Hds::Button
|
||||
@text={{t 'resources.policy.actions.add'}}
|
||||
@color='secondary'
|
||||
@route='scopes.scope.add-storage-policy'
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</bc.Sidebar>
|
||||
{{/if}}
|
||||
</Rose::Layout::BodyContent>
|
||||
</page.body>
|
||||
|
||||
</Rose::Layout::Page>
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { visit, find, click, currentURL } from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import a11yAudit from 'ember-a11y-testing/test-support/audit';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
|
||||
import select from '@ember/test-helpers/dom/select';
|
||||
|
||||
module('Acceptance | scope | add storage policy', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
let featuresService;
|
||||
let policyOne;
|
||||
let policyTwo;
|
||||
|
||||
const SETTINGS_LINK_SELECTOR = '.policy-sidebar .hds-link-standalone';
|
||||
const ENABLE_BUTTON_SELECTOR = '.policy-sidebar .hds-button';
|
||||
const DROPDOWN_SELECTOR = '[name=policy_id]';
|
||||
|
||||
const LINK_LIST_SELECTOR_ITEM_TEXT = '.link-list-item__text';
|
||||
|
||||
const LINK_TO_NEW_STORAGE_POLICY = '.hds-link-standalone';
|
||||
const SAVE_BTN_SELECTOR = '.hds-button--color-primary';
|
||||
const CANCEL_BTN_SELECTOR = '.hds-button--color-secondary';
|
||||
|
||||
// Instances
|
||||
const instances = {
|
||||
scopes: {
|
||||
global: null,
|
||||
org: null,
|
||||
},
|
||||
sessionRecording: null,
|
||||
};
|
||||
|
||||
//URLs
|
||||
const urls = {
|
||||
globalScope: null,
|
||||
orgScope: null,
|
||||
orgScopeEdit: null,
|
||||
policies: null,
|
||||
policy: null,
|
||||
newPolicy: null,
|
||||
addStoragePolicy: null,
|
||||
};
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
featuresService = this.owner.lookup('service:features');
|
||||
// Generate resources
|
||||
instances.scopes.global = this.server.create('scope', { id: 'global' });
|
||||
instances.scopes.org = this.server.create('scope', {
|
||||
type: 'org',
|
||||
scope: { id: 'global', type: 'global' },
|
||||
});
|
||||
|
||||
instances.policy = this.server.createList('policy', 2, {
|
||||
scope: instances.scopes.org,
|
||||
});
|
||||
|
||||
policyOne = instances.policy[0];
|
||||
policyTwo = instances.policy[1];
|
||||
|
||||
// Generate route URLs for resources
|
||||
urls.globalScope = `/scopes/global`;
|
||||
urls.orgScope = `/scopes/${instances.scopes.org.id}`;
|
||||
urls.orgScopeEdit = `${urls.orgScope}/edit`;
|
||||
urls.policies = `${urls.orgScope}/policies`;
|
||||
urls.policy = `${urls.policies}/${policyOne.id}`;
|
||||
urls.addStoragePolicy = `${urls.orgScope}/add-storage-policy`;
|
||||
urls.newPolicy = `${urls.addStoragePolicy}/create`;
|
||||
authenticateSession({});
|
||||
});
|
||||
|
||||
test('cannot attach policy on a scope without proper authorization', async function (assert) {
|
||||
assert.false(featuresService.isEnabled('ssh-session-recording'));
|
||||
await visit(urls.orgScopeEdit);
|
||||
await a11yAudit();
|
||||
assert.dom('.policy-sidebar a').doesNotExist();
|
||||
});
|
||||
|
||||
test('users can click on add storage policy button in the sidebar and it takes them to add a policy', async function (assert) {
|
||||
featuresService.enable('ssh-session-recording');
|
||||
await visit(urls.orgScopeEdit);
|
||||
assert.dom(SETTINGS_LINK_SELECTOR).doesNotExist();
|
||||
await click(ENABLE_BUTTON_SELECTOR);
|
||||
assert.strictEqual(currentURL(), urls.addStoragePolicy);
|
||||
});
|
||||
|
||||
test('users can click on settings link in the sidebar and it takes them to enable session recording', async function (assert) {
|
||||
featuresService.enable('ssh-session-recording');
|
||||
instances.scopes.org.update({
|
||||
storagePolicyId: policyOne.id,
|
||||
});
|
||||
await visit(urls.orgScopeEdit);
|
||||
await click(SETTINGS_LINK_SELECTOR);
|
||||
assert.strictEqual(currentURL(), urls.addStoragePolicy);
|
||||
});
|
||||
|
||||
test('link to add new storage policy should be displayed and redirect to new storage policy form', async function (assert) {
|
||||
featuresService.enable('ssh-session-recording');
|
||||
await visit(urls.orgScopeEdit);
|
||||
await click(ENABLE_BUTTON_SELECTOR);
|
||||
assert.strictEqual(currentURL(), urls.addStoragePolicy);
|
||||
assert.dom(LINK_TO_NEW_STORAGE_POLICY).isVisible();
|
||||
await click(LINK_TO_NEW_STORAGE_POLICY);
|
||||
assert.strictEqual(currentURL(), urls.newPolicy);
|
||||
});
|
||||
|
||||
test('can assign a storage policy for the scope', async function (assert) {
|
||||
featuresService.enable('ssh-session-recording');
|
||||
await visit(urls.orgScopeEdit);
|
||||
await click(ENABLE_BUTTON_SELECTOR);
|
||||
|
||||
assert.strictEqual(currentURL(), urls.addStoragePolicy);
|
||||
|
||||
assert.dom(DROPDOWN_SELECTOR).hasNoValue();
|
||||
await select(DROPDOWN_SELECTOR, policyTwo.id);
|
||||
assert.dom(DROPDOWN_SELECTOR).hasValue();
|
||||
await click(SAVE_BTN_SELECTOR);
|
||||
|
||||
assert.strictEqual(currentURL(), urls.orgScopeEdit);
|
||||
|
||||
assert.strictEqual(
|
||||
find(LINK_LIST_SELECTOR_ITEM_TEXT).textContent.trim(),
|
||||
policyTwo.name,
|
||||
);
|
||||
});
|
||||
|
||||
test('can cancel changes to an existing storage policy selection', async function (assert) {
|
||||
featuresService.enable('ssh-session-recording');
|
||||
instances.scopes.org.update({
|
||||
storagePolicyId: policyOne.id,
|
||||
});
|
||||
|
||||
await visit(urls.orgScopeEdit);
|
||||
await click(SETTINGS_LINK_SELECTOR);
|
||||
assert.strictEqual(currentURL(), urls.addStoragePolicy);
|
||||
|
||||
//update the storage policy selection
|
||||
await select(DROPDOWN_SELECTOR, policyTwo);
|
||||
|
||||
await click(CANCEL_BTN_SELECTOR);
|
||||
assert.strictEqual(currentURL(), urls.orgScopeEdit);
|
||||
|
||||
assert.strictEqual(
|
||||
find(LINK_LIST_SELECTOR_ITEM_TEXT).textContent.trim(),
|
||||
policyOne.name,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'admin/tests/helpers';
|
||||
|
||||
module(
|
||||
'Unit | Route | scopes/scope/add-storage-policy/create',
|
||||
function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let route = this.owner.lookup(
|
||||
'route:scopes/scope/add-storage-policy/create',
|
||||
);
|
||||
assert.ok(route);
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'admin/tests/helpers';
|
||||
|
||||
module('Unit | Route | scopes/scope/add-storage-policy', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let route = this.owner.lookup(
|
||||
'route:scopes/scope/add-storage-policy/index',
|
||||
);
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user