From 94c302739202f20dbc98a81cc8b8df3b2fec4013 Mon Sep 17 00:00:00 2001 From: lisbet-alvarez <107949262+lisbet-alvarez@users.noreply.github.com> Date: Wed, 8 Mar 2023 08:42:20 -0800 Subject: [PATCH 1/8] =?UTF-8?q?test:=20=F0=9F=92=8D=20acceptance=20tests?= =?UTF-8?q?=20for=20navigation=20link=20(#102)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: 💍 acceptance tests for navigation link ✅ Closes: ICU-8224 --- ui/admin/app/templates/scopes/scope.hbs | 16 +- .../acceptance/storage-buckets/list-test.js | 139 ++++++++++++++++++ .../acceptance/storage-buckets/read-test.js | 57 +++++++ 3 files changed, 205 insertions(+), 7 deletions(-) create mode 100644 ui/admin/tests/acceptance/storage-buckets/list-test.js create mode 100644 ui/admin/tests/acceptance/storage-buckets/read-test.js diff --git a/ui/admin/app/templates/scopes/scope.hbs b/ui/admin/app/templates/scopes/scope.hbs index 7d9a2836d..22be84374 100644 --- a/ui/admin/app/templates/scopes/scope.hbs +++ b/ui/admin/app/templates/scopes/scope.hbs @@ -32,13 +32,15 @@ {{t 'resources.session-recording.title_plural'}} - - - {{t 'resources.storage-bucket.title_plural'}} - + {{#if (can 'navigate model' @model collection='storage-buckets')}} + + + {{t 'resources.storage-bucket.title_plural'}} + + {{/if}} {{/if}} {{#if (and diff --git a/ui/admin/tests/acceptance/storage-buckets/list-test.js b/ui/admin/tests/acceptance/storage-buckets/list-test.js new file mode 100644 index 000000000..3821a4631 --- /dev/null +++ b/ui/admin/tests/acceptance/storage-buckets/list-test.js @@ -0,0 +1,139 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { module, test } from 'qunit'; +import { visit } from '@ember/test-helpers'; +import { setupApplicationTest } from 'admin/tests/helpers'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { authenticateSession } from 'ember-simple-auth/test-support'; + +module('Acceptance | storage-buckets | list', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + let features; + const STORAGE_BUCKET_LINK = 'Storage Buckets'; + + const instances = { + scopes: { + global: null, + org: null, + }, + }; + + const urls = { + globalScope: null, + storageBuckets: null, + }; + + hooks.beforeEach(function () { + instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + urls.globalScope = `/scopes/global`; + urls.storageBuckets = `${urls.globalScope}/storage-buckets`; + + features = this.owner.lookup('service:features'); + features.enable('session-recording'); + authenticateSession({}); + }); + + test('users can navigate to storage-buckets with proper authorization', async function (assert) { + assert.expect(3); + + await visit(urls.globalScope); + + assert.true( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('list') + ); + assert.true( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('create') + ); + assert.dom(`[href="${urls.storageBuckets}"]`).exists(); + }); + + test('user cannot navigate to index without either list or create actions', async function (assert) { + assert.expect(3); + instances.scopes.global.authorized_collection_actions['storage-buckets'] = + []; + + await visit(urls.globalScope); + + assert.false( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('list') + ); + assert.false( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('create') + ); + assert + .dom('[title="General"] a:nth-of-type(3)') + .doesNotIncludeText(STORAGE_BUCKET_LINK); + }); + + test('user can navigate to index with only create action', async function (assert) { + assert.expect(3); + instances.scopes.global.authorized_collection_actions['storage-buckets'] = + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].filter((item) => item !== 'list'); + + await visit(urls.globalScope); + + assert.false( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('list') + ); + assert.true( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('create') + ); + assert.dom(`[href="${urls.storageBuckets}"]`).exists(); + }); + + test('user can navigate to index with only list action', async function (assert) { + assert.expect(3); + instances.scopes.global.authorized_collection_actions['storage-buckets'] = + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].filter((item) => item !== 'create'); + + await visit(urls.globalScope); + + assert.true( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('list') + ); + assert.false( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('create') + ); + assert.dom(`[href="${urls.storageBuckets}"]`).exists(); + }); + + test('user cannot navigate to index when feature is disabled', async function (assert) { + assert.expect(1); + features.disable('session-recording'); + + await visit(urls.globalScope); + + assert + .dom('[title="General"] a:nth-of-type(2)') + .doesNotIncludeText(STORAGE_BUCKET_LINK); + }); +}); diff --git a/ui/admin/tests/acceptance/storage-buckets/read-test.js b/ui/admin/tests/acceptance/storage-buckets/read-test.js new file mode 100644 index 000000000..e734e79f7 --- /dev/null +++ b/ui/admin/tests/acceptance/storage-buckets/read-test.js @@ -0,0 +1,57 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { module, test } from 'qunit'; +import { visit, currentURL, click } 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 { authenticateSession } from 'ember-simple-auth/test-support'; + +module('Acceptance | storage-buckets | read', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + let features; + + const instances = { + scopes: { + global: null, + org: null, + }, + }; + + const urls = { + globalScope: null, + storageBuckets: null, + }; + + hooks.beforeEach(function () { + instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + instances.storageBucket = this.server.create('storage-bucket', { + scope: instances.scopes.global, + }); + urls.globalScope = `/scopes/global`; + urls.storageBuckets = `${urls.globalScope}/storage-buckets`; + + features = this.owner.lookup('service:features'); + features.enable('session-recording'); + authenticateSession({}); + }); + + test('visiting storage buckets', async function (assert) { + assert.expect(1); + await visit(urls.globalScope); + await a11yAudit(); + + await click(`[href="${urls.storageBuckets}"]`); + + assert.strictEqual(currentURL(), urls.storageBuckets); + }); +}); From 78392ff9b46288ae3f2a036e12e72a62ccdd3cd2 Mon Sep 17 00:00:00 2001 From: Carlos Alcaide Date: Wed, 8 Mar 2023 10:02:42 -0800 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Add=20component=20si?= =?UTF-8?q?debar=20in=20Admin=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add layout component sidebar in Admin UI and a view (Session recordings) already using it ✅ Closes: ICU-8145 --- .../app/components/layout/sidebar/index.hbs | 8 +++++ ui/admin/app/styles/app.scss | 15 +++++++++ .../session-recording/index.hbs | 33 +++++++++++++------ .../components/layout/sidebar-test.js | 23 +++++++++++++ 4 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 ui/admin/app/components/layout/sidebar/index.hbs create mode 100644 ui/admin/tests/integration/components/layout/sidebar-test.js diff --git a/ui/admin/app/components/layout/sidebar/index.hbs b/ui/admin/app/components/layout/sidebar/index.hbs new file mode 100644 index 000000000..4c5845023 --- /dev/null +++ b/ui/admin/app/components/layout/sidebar/index.hbs @@ -0,0 +1,8 @@ +
+ {{yield + (hash + body=(component 'rose/anonymous' class='layout-sidebar-body') + sidebar=(component 'rose/anonymous' class='layout-sidebar-sidebar') + ) + }} +
\ No newline at end of file diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index 1f305e001..fe37bd3ba 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -686,3 +686,18 @@ } /* stylelint-enable selector-class-pattern */ } + +.layout-sidebar { + display: grid; + grid-template-areas: 'body body sidebar'; + + .layout-sidebar-body { + grid-area: body; + } + + .layout-sidebar-sidebar { + display: flex; + flex-direction: column; + grid-area: sidebar; + } +} diff --git a/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/index.hbs b/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/index.hbs index 981e709ba..ad5da6ffd 100644 --- a/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/index.hbs +++ b/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/index.hbs @@ -4,16 +4,29 @@ -

{{t 'resources.session-recording.title'}} - {{@model.session_recording_id}}

-

{{t 'resources.session-recording.channel.title'}} - {{@model.channel_connection_id}}

+ + +

+ {{t 'resources.session-recording.title'}} + {{@model.session_recording_id}} +

- - {{@model.channel_connection_id}} - +

+ {{t 'resources.session-recording.channel.title'}} + {{@model.channel_connection_id}} +

+ + + {{@model.channel_connection_id}} + +
+ + +

{{t 'actions.help'}}

+
+
\ No newline at end of file diff --git a/ui/admin/tests/integration/components/layout/sidebar-test.js b/ui/admin/tests/integration/components/layout/sidebar-test.js new file mode 100644 index 000000000..c90c9a9c4 --- /dev/null +++ b/ui/admin/tests/integration/components/layout/sidebar-test.js @@ -0,0 +1,23 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'admin/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | layout/sidebar', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + await render(hbs` + + Body content + Sidebar content + + `); + + assert.dom('.layout-sidebar').isVisible(); + assert.dom('.layout-sidebar-body').isVisible(); + assert.dom('.layout-sidebar-body').hasText('Body content'); + assert.dom('.layout-sidebar-sidebar').isVisible(); + assert.dom('.layout-sidebar-sidebar').hasText('Sidebar content'); + }); +}); From 634ec086f2fdd3a25a747a9097ee10b98d4ee780 Mon Sep 17 00:00:00 2001 From: Carlos Alcaide Date: Wed, 8 Mar 2023 11:05:35 -0800 Subject: [PATCH 3/8] Delete sidebar inner styles --- ui/admin/app/styles/app.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index fe37bd3ba..9bbe06c90 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -696,8 +696,6 @@ } .layout-sidebar-sidebar { - display: flex; - flex-direction: column; grid-area: sidebar; } } From bd3cfab5fdc16b5811f0d134d4e68f826a675c53 Mon Sep 17 00:00:00 2001 From: Carlos Alcaide Date: Wed, 8 Mar 2023 11:39:47 -0800 Subject: [PATCH 4/8] Add media query to layour-sidebar, add grid gap and areas --- ui/admin/app/styles/app.scss | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index 9bbe06c90..f57df4d86 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -690,6 +690,9 @@ .layout-sidebar { display: grid; grid-template-areas: 'body body sidebar'; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: auto 1fr; + gap: 1em; .layout-sidebar-body { grid-area: body; @@ -699,3 +702,12 @@ grid-area: sidebar; } } + +@media (max-width: media.width(large)) { + .layout-sidebar { + grid-template-columns: 1fr; + grid-template-areas: + 'body' + 'sidebar'; + } +} From 6a5a27348b140b011fee4de3784418a1022d7fdf Mon Sep 17 00:00:00 2001 From: Carlos Alcaide Date: Wed, 8 Mar 2023 11:42:38 -0800 Subject: [PATCH 5/8] Change unit of measurement --- ui/admin/app/styles/app.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index f57df4d86..d7b5449d1 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -692,7 +692,7 @@ grid-template-areas: 'body body sidebar'; grid-template-columns: repeat(3, 1fr); grid-template-rows: auto 1fr; - gap: 1em; + gap: 2rem; .layout-sidebar-body { grid-area: body; From 4f0b3f71fd8d4ac50e7f3a7e886cf3183219096b Mon Sep 17 00:00:00 2001 From: lisbet-alvarez <107949262+lisbet-alvarez@users.noreply.github.com> Date: Thu, 9 Mar 2023 08:57:50 -0800 Subject: [PATCH 6/8] =?UTF-8?q?chore:=20=F0=9F=A4=96=20can=20abilities=20f?= =?UTF-8?q?or=20storage=20bucket=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 🤖 add can list, navigate, and create ✅ Closes: ICU-8244 --- addons/api/addon/abilities/storage-bucket.js | 41 ++++ addons/api/app/abilities/storage-bucket.js | 6 + ui/admin/app/abilities/storage-bucket.js | 31 +++ .../unit/abilities/storage-bucket-test.js | 224 ++++++++++++++++++ 4 files changed, 302 insertions(+) create mode 100644 addons/api/addon/abilities/storage-bucket.js create mode 100644 addons/api/app/abilities/storage-bucket.js create mode 100644 ui/admin/app/abilities/storage-bucket.js create mode 100644 ui/admin/tests/unit/abilities/storage-bucket-test.js diff --git a/addons/api/addon/abilities/storage-bucket.js b/addons/api/addon/abilities/storage-bucket.js new file mode 100644 index 000000000..d521b8ad4 --- /dev/null +++ b/addons/api/addon/abilities/storage-bucket.js @@ -0,0 +1,41 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import ModelAbility from './model'; + +/** + * Provides abilities for storage buckets. + */ +export default class StorageBucketAbility extends ModelAbility { + // =services + + // =permissions + + /** + * Only "known" storage bucket types may be read. + * @type {boolean} + */ + get canRead() { + return !this.model.isUnknown && this.hasAuthorizedAction('read'); + } + + /** + * Storage buckets can only exist within global and org scopes. + * @type {boolean} + */ + get canCreate() { + return ( + this.hasAuthorizedCollectionAction('create') && + (this.model.isGlobal || this.model.isOrg) + ); + } + + get canList() { + return ( + this.hasAuthorizedCollectionAction('list') && + (this.model.isGlobal || this.model.isOrg) + ); + } +} diff --git a/addons/api/app/abilities/storage-bucket.js b/addons/api/app/abilities/storage-bucket.js new file mode 100644 index 000000000..ed343c8d4 --- /dev/null +++ b/addons/api/app/abilities/storage-bucket.js @@ -0,0 +1,6 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +export { default } from 'api/abilities/storage-bucket'; diff --git a/ui/admin/app/abilities/storage-bucket.js b/ui/admin/app/abilities/storage-bucket.js new file mode 100644 index 000000000..a34d0b9d3 --- /dev/null +++ b/ui/admin/app/abilities/storage-bucket.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import StorageBucketAbility from 'api/abilities/storage-bucket'; +import { inject as service } from '@ember/service'; + +export default class OverrideStorageBucketAbility extends StorageBucketAbility { + // =service + @service features; + + /** + * This override ensures that storage buckets may be read only if the + * session-recording feature flag is enabled. + */ + get canRead() { + return this.features.isEnabled('session-recording') ? super.canRead : false; + } + + /** + * This override ensures that storage buckets can only be presented in the global scope. + */ + get canList() { + return this.hasAuthorizedCollectionAction('list') && this.model.isGlobal; + } + + get canNavigate() { + return this.canCreate || this.canList; + } +} diff --git a/ui/admin/tests/unit/abilities/storage-bucket-test.js b/ui/admin/tests/unit/abilities/storage-bucket-test.js new file mode 100644 index 000000000..3961c60dd --- /dev/null +++ b/ui/admin/tests/unit/abilities/storage-bucket-test.js @@ -0,0 +1,224 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import { + TYPE_STORAGE_BUCKET_PLUGIN, + TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3, +} from 'api/models/storage-bucket'; + +module('Unit | Abilities | storage-bucket', function (hooks) { + setupTest(hooks); + + let features; + + hooks.beforeEach(function () { + features = this.owner.lookup('service:features'); + features.enable('session-recording'); + }); + + test('can read storage bucket when authorized and feature is enabled', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const storageBucket = store.createRecord('storage-bucket', { + authorized_actions: ['read'], + type: TYPE_STORAGE_BUCKET_PLUGIN, + compositeType: TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3, + }); + + assert.true(canService.can('read storage-bucket', storageBucket)); + }); + + test('cannot read storage bucket when unauthorized and feature is enabled', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const storageBucket = store.createRecord('storage-bucket', { + authorized_actions: [], + type: TYPE_STORAGE_BUCKET_PLUGIN, + compositeType: TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3, + }); + + assert.false(canService.can('read storage-bucket', storageBucket)); + }); + + test('cannot read storage bucket when authorized and feature is disabled', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + features.disable('session-recording'); + + const storageBucket = store.createRecord('storage-bucket', { + authorized_actions: ['read'], + type: TYPE_STORAGE_BUCKET_PLUGIN, + compositeType: TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3, + }); + + assert.false(canService.can('read storage-bucket', storageBucket)); + }); + + test('cannot read storage bucket when unauthorized and feature is disabled', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + features.disable('session-recording'); + + const storageBucket = store.createRecord('storage-bucket', { + authorized_actions: [], + type: TYPE_STORAGE_BUCKET_PLUGIN, + compositeType: TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3, + }); + + assert.false(canService.can('read storage-bucket', storageBucket)); + }); + + test('can list storage bucket when authorized and in global scope', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': ['list'] }, + type: 'global', + id: 'global', + }); + + assert.true( + canService.can('list storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); + + test('cannot list storage bucket when unauthorized and in global scope', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': [] }, + type: 'global', + id: 'global', + }); + + assert.false( + canService.can('list storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); + + test('cannot list storage bucket when authorized and in org scope', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': ['list'] }, + type: 'org', + id: 'o_123', + }); + + assert.false( + canService.can('list storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); + + test('can create storage bucket when authorized and in global scope', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': ['create'] }, + type: 'global', + id: 'global', + }); + + assert.true( + canService.can('create storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); + + test('can create storage bucket when authorized and in org scope', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': ['create'] }, + type: 'org', + id: 'o_123', + }); + + assert.true( + canService.can('create storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); + + test('cannot create storage bucket when authorized and in project scope', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': ['create'] }, + type: 'project', + id: 'p_123', + }); + + assert.false( + canService.can('create storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); + + test('cannot create storage bucket when unauthorized', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': [] }, + type: 'global', + id: 'global', + }); + + assert.false( + canService.can('create storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); + + test('can navigate to storage bucket when authorized', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': ['create'] }, + type: 'global', + id: 'global', + }); + + assert.true( + canService.can('navigate storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); +}); From 0aedfc1147d2e04a07d62b9398493245ad3513cf Mon Sep 17 00:00:00 2001 From: lisbet-alvarez <107949262+lisbet-alvarez@users.noreply.github.com> Date: Thu, 9 Mar 2023 08:58:29 -0800 Subject: [PATCH 7/8] Icu 8225 create a new link to create storage bucket (#106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 add new link to create storage bucket ✅ Closes: ICU-8225 --- .../scopes/scope/storage-buckets.js | 22 ++++++++++++++ .../routes/scopes/scope/storage-buckets.js | 7 ++++- .../scopes/scope/storage-buckets/index.js | 10 ++++++- .../scopes/scope/storage-buckets/index.hbs | 30 ++++++++++++++++++- ui/admin/config/environment.js | 1 + .../scopes/scope/storage-buckets-test.js | 18 +++++++++++ 6 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 ui/admin/app/controllers/scopes/scope/storage-buckets.js create mode 100644 ui/admin/tests/unit/controllers/scopes/scope/storage-buckets-test.js diff --git a/ui/admin/app/controllers/scopes/scope/storage-buckets.js b/ui/admin/app/controllers/scopes/scope/storage-buckets.js new file mode 100644 index 000000000..249913c40 --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/storage-buckets.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class ScopesScopeStorageBucketsController extends Controller { + // =services + + @service intl; + + // = attributes + /** + * Translated breadcrumb + * @type {string} + */ + get breadCrumb() { + return this.intl.t('resources.storage-bucket.title_plural'); + } +} diff --git a/ui/admin/app/routes/scopes/scope/storage-buckets.js b/ui/admin/app/routes/scopes/scope/storage-buckets.js index 6c75be9b8..6c01670d9 100644 --- a/ui/admin/app/routes/scopes/scope/storage-buckets.js +++ b/ui/admin/app/routes/scopes/scope/storage-buckets.js @@ -5,4 +5,9 @@ import Route from '@ember/routing/route'; -export default class ScopesScopeStorageBucketsRoute extends Route {} +export default class ScopesScopeStorageBucketsRoute extends Route { + model() { + // Temporary placeholder + return [{ scope_id: 'sb_123456' }]; + } +} diff --git a/ui/admin/app/routes/scopes/scope/storage-buckets/index.js b/ui/admin/app/routes/scopes/scope/storage-buckets/index.js index 57bbf4b1f..1796e2de3 100644 --- a/ui/admin/app/routes/scopes/scope/storage-buckets/index.js +++ b/ui/admin/app/routes/scopes/scope/storage-buckets/index.js @@ -5,4 +5,12 @@ import Route from '@ember/routing/route'; -export default class ScopesScopeStorageBucketsIndexRoute extends Route {} +export default class ScopesScopeStorageBucketsIndexRoute extends Route { + // =methods + + setupController(controller) { + const scope = this.modelFor('scopes.scope'); + super.setupController(...arguments); + controller.setProperties({ scope }); + } +} diff --git a/ui/admin/app/templates/scopes/scope/storage-buckets/index.hbs b/ui/admin/app/templates/scopes/scope/storage-buckets/index.hbs index 3faf92ead..ead6a3c53 100644 --- a/ui/admin/app/templates/scopes/scope/storage-buckets/index.hbs +++ b/ui/admin/app/templates/scopes/scope/storage-buckets/index.hbs @@ -1,4 +1,32 @@ {{! Copyright (c) HashiCorp, Inc. SPDX-License-Identifier: MPL-2.0 -}} \ No newline at end of file +}} + + + + + + + + {{! TODO: Add valid doc link and description}} +

+ {{t 'resources.storage-bucket.title_plural'}} + +

+
+ + + {{! TODO: Check can create ability using correct file }} + {{#if (can 'create model' this.scope collection='storage-buckets')}} + {{#if @model}} + + {{/if}} + {{/if}} + + + +
\ No newline at end of file diff --git a/ui/admin/config/environment.js b/ui/admin/config/environment.js index 24ebf82ec..7cb123ec6 100644 --- a/ui/admin/config/environment.js +++ b/ui/admin/config/environment.js @@ -117,6 +117,7 @@ module.exports = function (environment) { worker: '/workers', 'worker.manage-workers': '/workers/manage-workers-on-hcp', 'worker-filters': '/worker-filters', + 'storage-bucket': '/storage-buckets', }, }, diff --git a/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets-test.js b/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets-test.js new file mode 100644 index 000000000..fa69f7929 --- /dev/null +++ b/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets-test.js @@ -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 | Controller | scopes/scope/storage-buckets', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let controller = this.owner.lookup( + 'controller:scopes/scope/storage-buckets' + ); + assert.ok(controller); + }); +}); From 9f4e52153427ef599d0f3311632e2fd5de178e4f Mon Sep 17 00:00:00 2001 From: Carlos Alcaide Date: Thu, 9 Mar 2023 09:34:16 -0800 Subject: [PATCH 8/8] Apply changes requested on PR --- .../components/layout/sidebar/body/index.hbs | 3 +++ .../app/components/layout/sidebar/index.hbs | 4 ++-- .../layout/sidebar/sidebar/index.hbs | 3 +++ .../session-recording/index.hbs | 8 ++++---- .../components/layout/sidebar-test.js | 4 ++-- .../components/layout/sidebar/body-test.js | 18 ++++++++++++++++++ .../components/layout/sidebar/sidebar-test.js | 18 ++++++++++++++++++ 7 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 ui/admin/app/components/layout/sidebar/body/index.hbs create mode 100644 ui/admin/app/components/layout/sidebar/sidebar/index.hbs create mode 100644 ui/admin/tests/integration/components/layout/sidebar/body-test.js create mode 100644 ui/admin/tests/integration/components/layout/sidebar/sidebar-test.js diff --git a/ui/admin/app/components/layout/sidebar/body/index.hbs b/ui/admin/app/components/layout/sidebar/body/index.hbs new file mode 100644 index 000000000..d905ec2e1 --- /dev/null +++ b/ui/admin/app/components/layout/sidebar/body/index.hbs @@ -0,0 +1,3 @@ +
+ {{yield}} +
\ No newline at end of file diff --git a/ui/admin/app/components/layout/sidebar/index.hbs b/ui/admin/app/components/layout/sidebar/index.hbs index 4c5845023..8e66b15a7 100644 --- a/ui/admin/app/components/layout/sidebar/index.hbs +++ b/ui/admin/app/components/layout/sidebar/index.hbs @@ -1,8 +1,8 @@
{{yield (hash - body=(component 'rose/anonymous' class='layout-sidebar-body') - sidebar=(component 'rose/anonymous' class='layout-sidebar-sidebar') + Body=(component 'layout/sidebar/body') + Sidebar=(component 'layout/sidebar/sidebar') ) }}
\ No newline at end of file diff --git a/ui/admin/app/components/layout/sidebar/sidebar/index.hbs b/ui/admin/app/components/layout/sidebar/sidebar/index.hbs new file mode 100644 index 000000000..4c986569d --- /dev/null +++ b/ui/admin/app/components/layout/sidebar/sidebar/index.hbs @@ -0,0 +1,3 @@ +
+ {{yield}} +
\ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/index.hbs b/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/index.hbs index ad5da6ffd..4bdc99323 100644 --- a/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/index.hbs +++ b/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/index.hbs @@ -5,7 +5,7 @@ - +

{{t 'resources.session-recording.title'}} {{@model.session_recording_id}} @@ -22,11 +22,11 @@ > {{@model.channel_connection_id}} - + - +

{{t 'actions.help'}}

- +
\ No newline at end of file diff --git a/ui/admin/tests/integration/components/layout/sidebar-test.js b/ui/admin/tests/integration/components/layout/sidebar-test.js index c90c9a9c4..95a93d6d5 100644 --- a/ui/admin/tests/integration/components/layout/sidebar-test.js +++ b/ui/admin/tests/integration/components/layout/sidebar-test.js @@ -9,8 +9,8 @@ module('Integration | Component | layout/sidebar', function (hooks) { test('it renders', async function (assert) { await render(hbs` - Body content - Sidebar content + Body content + Sidebar content `); diff --git a/ui/admin/tests/integration/components/layout/sidebar/body-test.js b/ui/admin/tests/integration/components/layout/sidebar/body-test.js new file mode 100644 index 000000000..837a29e59 --- /dev/null +++ b/ui/admin/tests/integration/components/layout/sidebar/body-test.js @@ -0,0 +1,18 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'admin/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | layout/sidebar/body', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + await render(hbs` + + template block text + + `); + + assert.dom(this.element).hasText('template block text'); + }); +}); diff --git a/ui/admin/tests/integration/components/layout/sidebar/sidebar-test.js b/ui/admin/tests/integration/components/layout/sidebar/sidebar-test.js new file mode 100644 index 000000000..ec42c3488 --- /dev/null +++ b/ui/admin/tests/integration/components/layout/sidebar/sidebar-test.js @@ -0,0 +1,18 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'admin/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | layout/sidebar/sidebar', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + await render(hbs` + + template block text + + `); + + assert.dom(this.element).hasText('template block text'); + }); +});