Merge remote-tracking branch 'origin/session-recording' into feat-use-mirage-for-recordings-navigation

This commit is contained in:
Carlos Alcaide committed 2023-03-16 09:12:20 -07:00
commit f2cd8ec742
26 files changed
+620 -69

No files matched your search

+34
View File
@@ -0,0 +1,34 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import ModelAbility from './model';
/**
* Provides abilities for session recordings.
*/
export default class RecordingAbility extends ModelAbility {
// =services
// =permissions
/**
* Only "known" session recording types may be read.
* @type {boolean}
*/
get canRead() {
return !this.model.isUnknown && this.hasAuthorizedAction('read');
}
get canList() {
return (
this.hasAuthorizedCollectionAction('list') &&
(this.model.isGlobal || this.model.isOrg)
);
}
get canDownloadAsciiCast() {
return this.model.isSSH && this.hasAuthorizedAction('download');
}
}
@@ -0,0 +1,23 @@
import ApplicationAdapter from './application';
export default class StorageBucketAdapter extends ApplicationAdapter {
/**
* Overrides the default `buildURL` such that on _create_, URLs for records of
* type `plugin` get an extra query parameter `?plugin_name`. All other
* request types behave as normal.
*
* @override
* @param {string} modelName
* @param {string} id
* @return {string} url
* @return {string} requestType
*/
buildURL(modelName, id, snapshot, requestType) {
let url = super.buildURL(...arguments);
const pluginName = snapshot?.attr('plugin')?.name;
if (requestType === 'createRecord' && pluginName) {
url = `${url}?plugin_name=${pluginName}`;
}
return url;
}
}
@@ -72,7 +72,7 @@ export default class GeneratedStorageBucketModel extends BaseModel {
readOnly: true,
isNestedAttribute: true,
})
client_secret_hmac;
secrets_hmac;
@attr('string', {
isNestedSecret: true,
@@ -0,0 +1,13 @@
import ApplicationSerializer from './application';
export default class StorageBucketSerializer extends ApplicationSerializer {
normalize(typeClass, hash, ...rest) {
const normalizedHash = structuredClone(hash);
const normalized = super.normalize(typeClass, normalizedHash, ...rest);
// Remove secret fields
delete normalized.data.attributes.access_key_id;
delete normalized.data.attributes.secret_access_key;
return normalized;
}
}
+3
View File
@@ -0,0 +1,3 @@
import { Ability } from 'ember-can';
export default class extends Ability {}
@@ -0,0 +1 @@
export { default } from 'api/adapters/storage-bucket';
@@ -0,0 +1 @@
export { default } from 'api/serializers/storage-bucket';
+7 -1
View File
@@ -14,6 +14,7 @@ import { targetHandler } from './route-handlers/target';
import { pickRandomStatusString } from './factories/session';
import initializeMockIPC from './scenarios/ipc';
import makeBooleanFilter from './helpers/bexpr-filter';
import { faker } from '@faker-js/faker';
const isTesting = environmentConfig.environment === 'test';
@@ -668,7 +669,7 @@ function routes() {
this.patch('/storage-buckets/:id');
this.post(
'/storage-buckets',
({ storageBuckets }, { queryParams: { plugin_name } }) => {
function ({ storageBuckets }, { queryParams: { plugin_name } }) {
const attrs = this.normalizedRequestAttrs();
if (plugin_name) {
attrs.type = 'plugin';
@@ -676,6 +677,11 @@ function routes() {
name: plugin_name,
};
}
// Remove the secrets and add the hmac in the response
delete attrs.secrets;
attrs.secrets_hmac = faker.git.commitSha();
return storageBuckets.create(attrs);
}
);
+5 -1
View File
@@ -18,7 +18,11 @@ export default factory.extend({
type: (i) => types[i % types.length],
authorized_actions: () =>
permissions.authorizedActionsFor('recording') || ['no-op', 'read', 'list'],
permissions.authorizedActionsFor('recording') || [
'no-op',
'read',
'download',
],
withNonExistingUserAndTarget: trait({
afterCreate(recording, server) {
+2
View File
@@ -37,8 +37,10 @@ export default factory.extend({
collectionActions.workers = ['create:worker-led', 'list'];
}
// Session recording resources are only available on global or org scope
if (this.type === 'global' || this.type === 'org') {
collectionActions['storage-buckets'] = ['create', 'list'];
collectionActions.recordings = ['list'];
}
return collectionActions;
@@ -0,0 +1,30 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Adapter | storage bucket', function (hooks) {
setupTest(hooks);
test('it generates correct createRecord URLs for storage buckets with extra query parameter ?plugin_name', function (assert) {
assert.expect(1);
const scopeID = 'o_1';
const mockSnapshot = {
adapterOptions: {
scopeID,
},
attr() {
return { name: 'aws_s3' };
},
};
const adapter = this.owner.lookup('adapter:storage-bucket');
const createRecordURL = adapter.buildURL(
'storage-bucket',
null,
mockSnapshot,
'createRecord'
);
assert.strictEqual(
createRecordURL,
'/v1/storage-buckets?plugin_name=aws_s3'
);
});
});
@@ -0,0 +1,99 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import {
TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3,
TYPE_STORAGE_BUCKET_PLUGIN,
} from 'api/models/storage-bucket';
module('Unit | Serializer | storage bucket', function (hooks) {
setupTest(hooks);
test('it serializes records', function (assert) {
let store = this.owner.lookup('service:store');
let record = store.createRecord('storage-bucket', {});
let serializedRecord = record.serialize();
assert.ok(serializedRecord);
});
test('it serializes a new aws plugin as expected', async function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const record = store.createRecord('storage-bucket', {
compositeType: TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3,
name: 'AWS',
description: 'this has an aws plugin',
bucket_name: 'bucketname',
bucket_prefix: 'bucketprefix',
worker_filter: 'workerfilter',
region: 'eu-west-1',
access_key_id: 'foobars',
secret_access_key: 'testing',
secrets_hmac: 'hmac',
});
const expectedResult = {
name: 'AWS',
description: 'this has an aws plugin',
type: TYPE_STORAGE_BUCKET_PLUGIN,
bucket_name: 'bucketname',
bucket_prefix: 'bucketprefix',
worker_filter: 'workerfilter',
attributes: {
region: 'eu-west-1',
},
secrets: {
access_key_id: 'foobars',
secret_access_key: 'testing',
},
};
assert.deepEqual(record.serialize(), expectedResult);
});
test('it normalizes correctly', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('storage-bucket');
const hostSetModelClass = store.createRecord('storage-bucket').constructor;
const payload = {
id: '1',
version: 1,
name: 'AWS',
description: 'this has an aws plugin',
type: TYPE_STORAGE_BUCKET_PLUGIN,
bucket_name: 'bucketname',
bucket_prefix: 'bucketprefix',
worker_filter: 'workerfilter',
attributes: {
region: 'eu-west-1',
},
secrets_hmac: 'this is secret',
};
const normalized = serializer.normalizeSingleResponse(
store,
hostSetModelClass,
payload
);
assert.deepEqual(normalized, {
included: [],
data: {
id: '1',
attributes: {
type: TYPE_STORAGE_BUCKET_PLUGIN,
version: 1,
authorized_actions: [],
name: 'AWS',
description: 'this has an aws plugin',
bucket_name: 'bucketname',
bucket_prefix: 'bucketprefix',
worker_filter: 'workerfilter',
secrets_hmac: 'this is secret',
region: 'eu-west-1',
},
type: 'storage-bucket',
relationships: {},
},
});
});
});
@@ -0,0 +1,25 @@
import Helper from '@ember/component/helper';
import { inject as service } from '@ember/service';
import DurationUnitFormat from 'intl-unofficial-duration-unit-format';
export default class extends Helper {
// =services
@service intl;
// =methods
/**
*
* @param {number} durationInMs Duration in milliseconds
* @returns localized human-readable duration string. For example, in English "1 hr 23 min 20 sec"
*/
compute([durationInMs]) {
const durationInSeconds = durationInMs / 1000;
const durationFormat = new DurationUnitFormat(this.intl.primaryLocale, {
style: 'narrow',
format: '{days} {hours} {minutes} {seconds}',
round: true,
});
return durationFormat.format(durationInSeconds);
}
}
@@ -0,0 +1 @@
export { default } from 'core/helpers/format-time-duration';
+2 -1
View File
@@ -40,7 +40,8 @@
"ember-feature-flags": "^6.0.0",
"ember-intl": "^6.0.0-beta.4",
"ember-loading": "^2.0.0",
"ember-truth-helpers": "^3.0.0"
"ember-truth-helpers": "^3.0.0",
"intl-unofficial-duration-unit-format": "^3.1.0"
},
"devDependencies": {
"@babel/eslint-parser": "^7.18.2",
@@ -0,0 +1,30 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'dummy/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupIntl } from 'ember-intl/test-support';
module('Integration | Helper | format-time-duration', function (hooks) {
setupRenderingTest(hooks);
setupIntl(hooks);
test('It renders in human-readable duration strings given different durations in milliseconds', async function (assert) {
// Set mock input (in milliseconds) durations
this.set('inputDurationSeconds', 3000);
this.set('inputDurationMinutes', 2700000);
this.set('inputDurationHours', 5000000);
this.set('inputDurationDays', 177800000);
await render(hbs`{{format-time-duration this.inputDurationSeconds}}`);
assert.dom(this.element).hasText('3s');
await render(hbs`{{format-time-duration this.inputDurationMinutes}}`);
assert.dom(this.element).hasText('45m');
await render(hbs`{{format-time-duration this.inputDurationHours}}`);
assert.dom(this.element).hasText('1h 23m 20s');
await render(hbs`{{format-time-duration this.inputDurationDays}}`);
assert.dom(this.element).hasText('2d 1h 23m 20s');
});
});
@@ -16,3 +16,13 @@
}
}
}
.hds-table {
border-collapse: revert;
}
.hds-dropdown {
.hds-dropdown-toggle-icon {
color: inherit;
}
}
@@ -5,6 +5,23 @@
@import 'color-tokens-tier-2';
// HDS Light Mode
// This is the default HDS color theme. Only Boundary custom tokens
// are included here for completeness.
:root {
// Tier-1 (Boundary custom)
// Boundary custom first-tier color tokens with alpha.
// In HDS, these correspond to the named color token,
// with the alpha identifier. However, they do not have stand-alone tokens
// associated with them and appear hard-coded alongside other styles.
// They are pulled out here for easier theming.
--token-color-palette-neutral-600-alpha-200: #3b3d4559;
--token-color-palette-neutral-600-alpha-100: #3b3d4540;
--token-color-palette-neutral-500-alpha-200: #656a7640;
--token-color-palette-neutral-500-alpha-100: #656a7626;
--token-color-palette-neutral-500-alpha-50: #656a760d;
}
// HDS Dark Mode
// This theme reverses the lightness order of shades per color for tier-1
// color tokens. It also provides custom tier-2 mapping overrides that
@@ -87,23 +104,6 @@
--token-form-indicator-optional-color: var(--token-color-palette-neutral-200);
}
// HDS Light Mode
// This is the default HDS color theme. Only Boundary custom tokens
// are included here for completeness.
@mixin light-mode {
// Tier-1 (Boundary custom)
// Boundary custom first-tier color tokens with alpha.
// In HDS, these correspond to the named color token,
// with the alpha identifier. However, they do not have stand-alone tokens
// associated with them and appear hard-coded alongside other styles.
// They are pulled out here for easier theming.
--token-color-palette-neutral-600-alpha-200: #3b3d4559;
--token-color-palette-neutral-600-alpha-100: #3b3d4540;
--token-color-palette-neutral-500-alpha-200: #656a7640;
--token-color-palette-neutral-500-alpha-100: #656a7626;
--token-color-palette-neutral-500-alpha-50: #656a760d;
}
@media (prefers-color-scheme: dark) {
.ember-application:not(.rose-theme-light) {
@include dark-mode;
+33
View File
@@ -0,0 +1,33 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import RecordingAbility from 'api/abilities/recording';
import { inject as service } from '@ember/service';
export default class OverrideRecordingAbility extends RecordingAbility {
// =service
@service features;
/**
* This override ensures that session recordings 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 session recordings can only be presented in the global scope
* and if the session-recording feature flag is enabled. */
get canList() {
return this.features.isEnabled('session-recording')
? this.hasAuthorizedCollectionAction('list') && this.model.isGlobal
: false;
}
get canNavigate() {
return this.canList;
}
}
+5 -2
View File
@@ -19,10 +19,13 @@ export default class OverrideStorageBucketAbility extends StorageBucketAbility {
}
/**
* This override ensures that storage buckets can only be presented in the global scope.
* This override ensures that storage buckets can only be presented in the global scope
* and if the session-recording feature flag is enabled.
*/
get canList() {
return this.hasAuthorizedCollectionAction('list') && this.model.isGlobal;
return this.features.isEnabled('session-recording')
? this.hasAuthorizedCollectionAction('list') && this.model.isGlobal
: false;
}
get canNavigate() {
-4
View File
@@ -776,7 +776,3 @@
'sidebar';
}
}
.storage-bucket-list {
border-collapse: revert;
}
@@ -4,15 +4,16 @@
</page.header>
<page.body>
{{#each @model as |recording|}}
<p>
<LinkTo
@route='scopes.scope.session-recordings.session-recording'
@model={{recording.id}}
>
{{recording.id}}
</LinkTo>
</p>
{{/each}}
<LinkTo
@route='scopes.scope.session-recordings.session-recording'
@model={{@model.session_recording_id}}
>
{{@model.session_recording_id}}
</LinkTo>
<br /><br />
<p>{{format-time-duration 3000}}</p>
<p>{{format-time-duration 2700000}}</p>
<p>{{format-time-duration 5000000}}</p>
<p>{{format-time-duration 177800000}}</p>
</page.body>
</Rose::Layout::Page>
@@ -32,7 +32,6 @@
<page.body>
{{#if @model}}
<Hds::Table
class='storage-bucket-list'
@valign='middle'
@model={{@model}}
@columns={{array
@@ -93,7 +92,20 @@
{{/if}}
</B.Td>
<B.Td>
{{!TODO: add dropdown for delete and edit actions}}
{{!TODO: add dropdown for delete actions}}
<Hds::Dropdown as |dd|>
<dd.ToggleIcon
@icon='more-horizontal'
@text='Overflow Options'
@hasChevron={{false}}
@size='small'
/>
<dd.Interactive
@route='scopes.scope.storage-buckets.storage-bucket'
@model={{B.data.id}}
@text={{t 'actions.edit'}}
/>
</Hds::Dropdown>
</B.Td>
</B.Tr>
</:body>
@@ -0,0 +1,221 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { TYPE_RECORDING_SSH } from 'api/models/recording';
module('Unit | Abilities | session-recording', function (hooks) {
setupTest(hooks);
let features;
let canService;
let store;
hooks.beforeEach(function () {
features = this.owner.lookup('service:features');
features.enable('session-recording');
canService = this.owner.lookup('service:can');
store = this.owner.lookup('service:store');
});
test('can read session recording feature is enabled', function (assert) {
assert.expect(2);
const recordingWithAuthorizedAction = store.createRecord('recording', {
authorized_actions: ['read'],
type: TYPE_RECORDING_SSH,
});
const recordingWithoutAuthorizedAction = store.createRecord('recording', {
authorized_actions: [],
type: TYPE_RECORDING_SSH,
});
assert.true(
canService.can('read recording', recordingWithAuthorizedAction)
);
assert.false(
canService.can('read recording', recordingWithoutAuthorizedAction)
);
});
test('cannot read session recording when feature is disabled', function (assert) {
assert.expect(2);
features.disable('session-recording');
const recordingWithAuthorizedAction = store.createRecord('recording', {
authorized_actions: ['read'],
type: TYPE_RECORDING_SSH,
});
const recordingWithoutAuthorizedAction = store.createRecord('recording', {
authorized_actions: [],
type: TYPE_RECORDING_SSH,
});
assert.false(
canService.can('read recording', recordingWithAuthorizedAction)
);
assert.false(
canService.can('read recording', recordingWithoutAuthorizedAction)
);
});
test('can list session recording when in global scope', function (assert) {
assert.expect(2);
const scopeModelWithAuthorizedAction = store.createRecord('scope', {
authorized_collection_actions: { recordings: ['list'] },
type: 'global',
id: 'first',
});
const scopeModelWithoutAuthorizedAction = store.createRecord('scope', {
authorized_collection_actions: { recordings: [] },
type: 'global',
id: 'second',
});
assert.true(
canService.can('list recording', scopeModelWithAuthorizedAction, {
collection: 'recordings',
})
);
assert.false(
canService.can('list recording', scopeModelWithoutAuthorizedAction, {
collection: 'recordings',
})
);
});
test('cannot list session recording when in org scope', function (assert) {
assert.expect(2);
const scopeModelWithAuthorizedAction = store.createRecord('scope', {
authorized_collection_actions: { recordings: ['list'] },
type: 'org',
id: 'first',
});
const scopeModelWithoutAuthorizedAction = store.createRecord('scope', {
authorized_collection_actions: { recordings: [] },
type: 'org',
id: 'second',
});
assert.false(
canService.can('list recording', scopeModelWithAuthorizedAction, {
collection: 'recordings',
})
);
assert.false(
canService.can('list recording', scopeModelWithoutAuthorizedAction, {
collection: 'recordings',
})
);
});
test('can navigate to session recording when feature is enabled', function (assert) {
assert.expect(2);
const scopeModelWithAuthorizedAction = store.createRecord('scope', {
authorized_collection_actions: { recordings: ['list'] },
type: 'global',
id: 'first',
});
const scopeModelWithoutAuthorizedAction = store.createRecord('scope', {
authorized_collection_actions: { recordings: [] },
type: 'global',
id: 'second',
});
assert.true(
canService.can('navigate recording', scopeModelWithAuthorizedAction, {
collection: 'recordings',
})
);
assert.false(
canService.can('navigate recording', scopeModelWithoutAuthorizedAction, {
collection: 'recordings',
})
);
});
test('cannot navigate to session recording when feature is disabled', function (assert) {
assert.expect(2);
features.disable('session-recording');
const scopeModelWithAuthorizedAction = store.createRecord('scope', {
authorized_collection_actions: { recordings: ['list'] },
type: 'global',
id: 'first',
});
const scopeModelWithoutAuthorizedAction = store.createRecord('scope', {
authorized_collection_actions: { recordings: [] },
type: 'global',
id: 'second',
});
assert.false(
canService.can('navigate recording', scopeModelWithAuthorizedAction, {
collection: 'recordings',
})
);
assert.false(
canService.can('navigate recording', scopeModelWithoutAuthorizedAction, {
collection: 'recordings',
})
);
});
test('can download asciicast when an ssh recording has download authorization', function (assert) {
assert.expect(2);
const recordingWithAuthorizedAction = store.createRecord('recording', {
authorized_actions: ['download'],
type: TYPE_RECORDING_SSH,
});
const recordingWithoutAuthorizedAction = store.createRecord('recording', {
authorized_actions: [],
type: TYPE_RECORDING_SSH,
});
assert.true(
canService.can(
'downloadAsciiCast recording',
recordingWithAuthorizedAction
)
);
assert.false(
canService.can(
'downloadAsciiCast recording',
recordingWithoutAuthorizedAction
)
);
});
test('cannot download asciicast when a recording is not ssh type', function (assert) {
assert.expect(2);
const recordingWithAuthorizedAction = store.createRecord('recording', {
authorized_actions: ['download'],
type: 'unknown',
});
const recordingWithoutAuthorizedAction = store.createRecord('recording', {
authorized_actions: [],
type: 'unknown',
});
assert.false(
canService.can(
'downloadAsciiCast recording',
recordingWithAuthorizedAction
)
);
assert.false(
canService.can(
'downloadAsciiCast recording',
recordingWithoutAuthorizedAction
)
);
});
});
@@ -14,16 +14,18 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
setupTest(hooks);
let features;
let canService;
let store;
hooks.beforeEach(function () {
features = this.owner.lookup('service:features');
features.enable('session-recording');
canService = this.owner.lookup('service:can');
store = this.owner.lookup('service:store');
});
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'],
@@ -36,8 +38,6 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
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: [],
@@ -50,8 +50,6 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
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', {
@@ -65,8 +63,6 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
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', {
@@ -80,8 +76,6 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
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'] },
@@ -98,8 +92,6 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
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': [] },
@@ -116,8 +108,6 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
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'] },
@@ -134,8 +124,6 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
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'] },
@@ -152,8 +140,6 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
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'] },
@@ -170,8 +156,6 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
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'] },
@@ -188,8 +172,6 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
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': [] },
@@ -206,8 +188,23 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
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',
})
);
});
test('cannot navigate to storage bucket when authorized and feature is disabled', function (assert) {
assert.expect(1);
features.disable('session-recording');
const scopeModel = store.createRecord('scope', {
authorized_collection_actions: { 'storage-buckets': ['create'] },
+11 -6
View File
@@ -10551,9 +10551,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001157, caniuse-lite@^1.0.30001449:
version "1.0.30001458"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz#871e35866b4654a7d25eccca86864f411825540c"
integrity sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w==
version "1.0.30001466"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001466.tgz#c1e6197c540392e09709ecaa9e3e403428c53375"
integrity sha512-ewtFBSfWjEmxUgNBSZItFSmVtvk9zkwkl1OfRZlKA8slltRN+/C/tuGVrF9styXkN36Yu3+SeJ1qkXxDEyNZ5w==
capture-exit@^2.0.0:
version "2.0.0"
@@ -17269,6 +17269,11 @@ intl-messageformat@10.2.1, intl-messageformat@^10.1.0:
"@formatjs/icu-messageformat-parser" "2.1.10"
tslib "2.4.0"
intl-unofficial-duration-unit-format@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/intl-unofficial-duration-unit-format/-/intl-unofficial-duration-unit-format-3.1.0.tgz#994feefc8d903d9e5044dc7e4e1d38ebad59394b"
integrity sha512-yjvUDwV63zSGkbeUqpDRJmOgMQoJA8OFhNeo+1KY20q+lFCtcvRWszJNsq/ssrUJRy8g+Mt3BYNfwFvRRSAkow==
invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@@ -25706,9 +25711,9 @@ terser-webpack-plugin@^5.1.3:
terser "^5.7.2"
terser@^4.1.2, terser@^4.6.3, terser@^5.14.2, terser@^5.3.0, terser@^5.3.4, terser@^5.7.2:
version "5.16.5"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.5.tgz#1c285ca0655f467f92af1bbab46ab72d1cb08e5a"
integrity sha512-qcwfg4+RZa3YvlFh0qjifnzBHjKGNbtDo9yivMqMFDy9Q6FSaQWSB/j1xKhsoUFJIqDOM3TsN6D5xbrMrFcHbg==
version "5.16.6"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.6.tgz#f6c7a14a378ee0630fbe3ac8d1f41b4681109533"
integrity sha512-IBZ+ZQIA9sMaXmRZCUMDjNH0D5AQQfdn4WUjHL0+1lF4TP1IHRJbrhb6fNaXWikrYQTSkb7SLxkeXAiy1p7mbg==
dependencies:
"@jridgewell/source-map" "^0.3.2"
acorn "^8.5.0"