fix: 🐛 fix mismtach between fe/be feature flag strings (#264)

* fix: 🐛 fix mismtach between fe/be feature flag strings

* chore: 🤖 add development can helper to recompute on flags
This commit is contained in:
Randall Morey authored and GitHub committed 2023-05-22 08:49:30 -07:00
1 parent f0550290e2
commit 8d883073e1
18 files changed
+180 -59

No files matched your search

+6 -2
View File
@@ -21,7 +21,9 @@ export default class OverrideScopeAbility extends OverrideModelAbility {
get canCreate() {
switch (this.collection) {
case 'storage-buckets':
return this.features.isEnabled('session-recording') && super.canCreate;
return (
this.features.isEnabled('ssh-session-recording') && super.canCreate
);
default:
return super.canCreate;
}
@@ -36,7 +38,9 @@ export default class OverrideScopeAbility extends OverrideModelAbility {
switch (this.collection) {
case 'session-recordings':
case 'storage-buckets':
return this.features.isEnabled('session-recording') && super.canList;
return (
this.features.isEnabled('ssh-session-recording') && super.canList
);
default:
return super.canList;
}
+3 -1
View File
@@ -15,6 +15,8 @@ export default class OverrideSessionRecordingAbility extends SessionRecordingAbi
* session-recording feature flag is enabled.
*/
get canRead() {
return this.features.isEnabled('session-recording') ? super.canRead : false;
return this.features.isEnabled('ssh-session-recording')
? super.canRead
: false;
}
}
+5 -3
View File
@@ -15,7 +15,9 @@ export default class OverrideStorageBucketAbility extends StorageBucketAbility {
* session-recording feature flag is enabled.
*/
get canRead() {
return this.features.isEnabled('session-recording') ? super.canRead : false;
return this.features.isEnabled('ssh-session-recording')
? super.canRead
: false;
}
/**
@@ -23,7 +25,7 @@ export default class OverrideStorageBucketAbility extends StorageBucketAbility {
* session-recording feature flag is enabled.
*/
get canUpdate() {
return this.features.isEnabled('session-recording')
return this.features.isEnabled('ssh-session-recording')
? super.canUpdate
: false;
}
@@ -33,7 +35,7 @@ export default class OverrideStorageBucketAbility extends StorageBucketAbility {
* session-recording feature flag is enabled.
*/
get canDelete() {
return this.features.isEnabled('session-recording')
return this.features.isEnabled('ssh-session-recording')
? super.canDelete
: false;
}
+48
View File
@@ -0,0 +1,48 @@
import CanHelper from 'ember-can/helpers/can';
import { inject as service } from '@ember/service';
import config from 'admin/config/environment';
const isDevelopment = config.environment === 'development';
/**
* Normally, abilities that access tracked values are not recomputed on change.
* This helper overrides the Ember Can helper to trigger recompute whenever
* a feature flag changes. This functionality is available only in the
* development environment, since feature flags may be toggled in the UI, and
* also for performance reasons as this effectively recomputes _all_ `can`
* helpers on any feature flag change.
*/
class DevelopmentCanHelper extends CanHelper {
// =services
@service features;
// =methods
constructor() {
super(...arguments);
this.observeAllFlags();
}
willDestroy() {
this.removeAllFlags();
super.willDestroy();
}
observeAllFlags() {
this.features.flags.forEach((flag) =>
/* eslint-disable-next-line ember/no-observers */
this.features.addObserver(flag, this, 'recompute')
);
}
removeAllFlags() {
this.features.flags.forEach((flag) =>
/* eslint-disable-next-line ember/no-observers */
this.features.removeObserver(flag, this, 'recompute')
);
}
}
// In development, export the development helper, otherwise the original.
export default isDevelopment ? DevelopmentCanHelper : CanHelper;
+16 -18
View File
@@ -26,25 +26,23 @@
<Rose::Icon @name='flight-icons/svg/org-16' @size='medium' />
{{t 'resources.org.title_plural'}}
</nav.link>
{{#if (feature-flag 'session-recording')}}
{{#if
(can 'navigate scope' @model collection='session-recordings')
}}
<nav.link @route='scopes.scope.session-recordings'>
<Rose::Icon @name='flight-icons/svg/film-16' @size='medium' />
{{t 'resources.session-recording.title_plural'}}
</nav.link>
{{/if}}
{{#if (can 'navigate scope' @model collection='storage-buckets')}}
<nav.link @route='scopes.scope.storage-buckets'>
<Rose::Icon
@name='flight-icons/svg/cloud-upload-16'
@size='medium'
/>
{{t 'resources.storage-bucket.title_plural'}}
</nav.link>
{{/if}}
{{#if (can 'navigate scope' @model collection='session-recordings')}}
<nav.link @route='scopes.scope.session-recordings'>
<Rose::Icon @name='flight-icons/svg/film-16' @size='medium' />
{{t 'resources.session-recording.title_plural'}}
</nav.link>
{{/if}}
{{#if (can 'navigate scope' @model collection='storage-buckets')}}
<nav.link @route='scopes.scope.storage-buckets'>
<Rose::Icon
@name='flight-icons/svg/cloud-upload-16'
@size='medium'
/>
{{t 'resources.storage-bucket.title_plural'}}
</nav.link>
{{/if}}
{{#if
(and
(can 'navigate worker' @model collection='workers')
@@ -33,7 +33,7 @@
{{#if
(and
(can 'setStorageBucket target' @model)
(feature-flag 'session-recording')
(feature-flag 'ssh-session-recording')
)
}}
<sb.Sidebar>
+1 -1
View File
@@ -13,7 +13,7 @@ const devFeatures = {
const licensedFeatures = {
'multi-hop-sessions': false,
'ssh-target': false,
'session-recording': false,
'ssh-session-recording': false,
};
// Base edition declares available features, disabled by default.
const baseEdition = {
@@ -3,12 +3,13 @@ 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 { authenticateSession } from 'ember-simple-auth/test-support';
import { enableFeature } from 'ember-feature-flags/test-support';
module('Acceptance | session recordings | list', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
let featuresService;
// Selectors
const SESSION_RECORDING_TITLE = 'Session Recordings';
@@ -45,7 +46,10 @@ module('Acceptance | session recordings | list', function (hooks) {
urls.globalScope = `/scopes/global`;
urls.sessionRecordings = `${urls.globalScope}/session-recordings`;
urls.sessionRecording = `${urls.sessionRecordings}/${instances.sessionRecording.id}`;
enableFeature('session-recording');
featuresService = this.owner.lookup('service:features');
featuresService.enable('ssh-session-recording');
authenticateSession({});
});
@@ -64,7 +64,7 @@ module('Acceptance | session recordings | read', function (hooks) {
test('visiting a session recording', async function (assert) {
assert.expect(1);
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
// Visit session recordings
await visit(urls.sessionRecordings);
await a11yAudit();
@@ -76,7 +76,7 @@ module('Acceptance | session recordings | read', function (hooks) {
test('user can navigate to a session recording with proper authorization', async function (assert) {
assert.expect(3);
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
// Visit session recordings
await visit(urls.sessionRecordings);
assert.true(instances.sessionRecording.authorized_actions.includes('read'));
@@ -88,7 +88,7 @@ module('Acceptance | session recordings | read', function (hooks) {
test('user cannot navigate to a session recording without the read action', async function (assert) {
assert.expect(1);
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
instances.sessionRecording.authorized_actions =
instances.sessionRecording.authorized_actions.filter(
(item) => item !== 'read'
@@ -100,7 +100,7 @@ module('Acceptance | session recordings | read', function (hooks) {
test('user can navigate to a channel recording', async function (assert) {
assert.expect(2);
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
// Visit session recording
await visit(urls.sessionRecording);
assert.dom('table').hasClass('hds-table');
@@ -46,7 +46,7 @@ module('Acceptance | storage-buckets | create', function (hooks) {
this.server.schema.storageBuckets.all().models.length;
features = this.owner.lookup('service:features');
features.enable('session-recording');
features.enable('ssh-session-recording');
authenticateSession({});
});
@@ -57,7 +57,7 @@ module('Acceptance | storage-buckets | delete', function (hooks) {
authenticateSession({});
intl = this.owner.lookup('service:intl');
features = this.owner.lookup('service:features');
features.enable('session-recording');
features.enable('ssh-session-recording');
});
test('user can delete a storage bucket', async function (assert) {
@@ -53,7 +53,7 @@ module('Acceptance | storage-buckets | list', function (hooks) {
test('users can navigate to storage-buckets with proper authorization', async function (assert) {
assert.expect(5);
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
await visit(urls.globalScope);
assert.true(
@@ -80,7 +80,7 @@ module('Acceptance | storage-buckets | list', function (hooks) {
test('user cannot navigate to index without either list or create actions', async function (assert) {
assert.expect(5);
featuresService.enable('byow');
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
instances.scopes.global.authorized_collection_actions['storage-buckets'] =
[];
@@ -114,7 +114,7 @@ module('Acceptance | storage-buckets | list', function (hooks) {
test('user can navigate to index with only create action', async function (assert) {
assert.expect(5);
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
instances.scopes.global.authorized_collection_actions['storage-buckets'] =
instances.scopes.global.authorized_collection_actions[
@@ -148,7 +148,7 @@ module('Acceptance | storage-buckets | list', function (hooks) {
test('user can navigate to index with only list action', async function (assert) {
assert.expect(5);
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
instances.scopes.global.authorized_collection_actions['storage-buckets'] =
instances.scopes.global.authorized_collection_actions[
@@ -182,7 +182,7 @@ module('Acceptance | storage-buckets | list', function (hooks) {
assert.expect(2);
featuresService.enable('byow');
await visit(urls.globalScope);
assert.false(featuresService.isEnabled('session-recording'));
assert.false(featuresService.isEnabled('ssh-session-recording'));
assert
.dom('[title="General"] a:nth-of-type(2)')
.doesNotIncludeText(STORAGE_BUCKET_TITLE);
@@ -190,7 +190,7 @@ module('Acceptance | storage-buckets | list', function (hooks) {
test('edit action in table directs user to appropriate page', async function (assert) {
assert.expect(3);
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
await visit(urls.globalScope);
instances.storageBucket = this.server.create('storage-bucket', {
scope: instances.scopes.global,
@@ -49,7 +49,7 @@ module('Acceptance | storage-buckets | read', function (hooks) {
urls.unknownStorageBucket = `${urls.storageBuckets}/foo`;
features = this.owner.lookup('service:features');
features.enable('session-recording');
features.enable('ssh-session-recording');
authenticateSession({});
});
@@ -52,7 +52,7 @@ module('Acceptance | storage-buckets | update', function (hooks) {
urls.storageBucket = `${urls.storageBuckets}/${instances.storageBucket.id}`;
features = this.owner.lookup('service:features');
features.enable('session-recording');
features.enable('ssh-session-recording');
authenticateSession({});
});
@@ -93,14 +93,14 @@ module('Acceptance | targets | enable session recording', function (hooks) {
test('cannot enable session recording for a target without proper authorization', async function (assert) {
assert.expect(2);
assert.false(featuresService.isEnabled('session-recording'));
assert.false(featuresService.isEnabled('ssh-session-recording'));
await visit(urls.target);
await a11yAudit();
assert.dom('.target-sidebar a').doesNotExist();
});
test('users can click on enable-recording button in target session-recording sidebar and it takes them to enable session recording', async function (assert) {
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
assert.expect(2);
await visit(urls.target);
assert.dom(SETTINGS_LINK_SELECTOR).doesNotExist();
@@ -109,7 +109,7 @@ module('Acceptance | targets | enable session recording', function (hooks) {
});
test('users can click on settings link in target session-recording sidebar and it takes them to enable session recording', async function (assert) {
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
assert.expect(1);
instances.target.update({
storageBucketId: storageBucketOne.id,
@@ -120,7 +120,7 @@ module('Acceptance | targets | enable session recording', function (hooks) {
});
test('users can click on associated storage bucket card on an ssh target', async function (assert) {
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
assert.expect(1);
instances.target.update({ storageBucketId: storageBucketOne.id });
await visit(urls.target);
@@ -129,7 +129,7 @@ module('Acceptance | targets | enable session recording', function (hooks) {
});
test('toggle should be enabled and storage buckets list should be shown when enable session recording button is clicked', async function (assert) {
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
assert.expect(3);
await visit(urls.target);
await click(ENABLE_BUTTON_SELECTOR);
@@ -139,7 +139,7 @@ module('Acceptance | targets | enable session recording', function (hooks) {
});
test('storage buckets list is hidden when toggle is disabled', async function (assert) {
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
assert.expect(4);
await visit(urls.target);
await click(ENABLE_BUTTON_SELECTOR);
@@ -151,7 +151,7 @@ module('Acceptance | targets | enable session recording', function (hooks) {
});
test('link to add new storage bucket should be displayed and redirect to new storage buckets form', async function (assert) {
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
assert.expect(3);
await visit(urls.target);
await click(ENABLE_BUTTON_SELECTOR);
@@ -162,7 +162,7 @@ module('Acceptance | targets | enable session recording', function (hooks) {
});
test('retain last selected dropdown list value when the toggle is off', async function (assert) {
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
assert.expect(3);
instances.target.update({
storageBucketId: storageBucketOne.id,
@@ -184,7 +184,7 @@ module('Acceptance | targets | enable session recording', function (hooks) {
});
test('can assign a storage bucket for the target', async function (assert) {
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
assert.expect(5);
await visit(urls.target);
await click(ENABLE_BUTTON_SELECTOR);
@@ -205,7 +205,7 @@ module('Acceptance | targets | enable session recording', function (hooks) {
});
test('can cancel changes to an exisiting storage bucket selection', async function (assert) {
featuresService.enable('session-recording');
featuresService.enable('ssh-session-recording');
assert.expect(3);
instances.target.update({
storageBucketId: storageBucketOne.id,
@@ -0,0 +1,42 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'admin/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
/**
* The can helper override in admin is exported only in a development
* environment, never in tests or production. This is a simple smoke test
* to ensure that it still functions as intended.
*/
module('Integration | Helper | can', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
assert.expect(2);
const model = {
authorized_actions: ['read'],
authorized_collection_actions: {
resource: ['list'],
},
};
this.set('model', model);
await render(hbs`
{{#if (can 'read model' this.model)}}
Read
{{/if}}
`);
assert.dom(this.element).hasText('Read');
await render(hbs`
{{#if (can 'list model' this.model collection='resource')}}
List
{{/if}}
`);
assert.dom(this.element).hasText('List');
});
});
@@ -16,7 +16,6 @@ module('Unit | Abilities | session-recording', function (hooks) {
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');
});
@@ -24,6 +23,8 @@ module('Unit | Abilities | session-recording', function (hooks) {
test('can read session recording feature is enabled', function (assert) {
assert.expect(2);
features.enable('ssh-session-recording');
const recordingWithAuthorizedAction = store.createRecord(
'session-recording',
{
@@ -49,7 +50,6 @@ module('Unit | Abilities | session-recording', function (hooks) {
test('cannot read session recording when feature is disabled', function (assert) {
assert.expect(2);
features.disable('session-recording');
const recordingWithAuthorizedAction = store.createRecord(
'session-recording',
@@ -77,6 +77,8 @@ module('Unit | Abilities | session-recording', function (hooks) {
test('can list session recording when in global scope', function (assert) {
assert.expect(2);
features.enable('ssh-session-recording');
const scopeModelWithAuthorizedAction = store.createRecord('scope', {
authorized_collection_actions: { ['session-recordings']: ['list'] },
type: 'global',
@@ -103,6 +105,8 @@ module('Unit | Abilities | session-recording', function (hooks) {
test('can list session recording when in org scope when authorized', function (assert) {
assert.expect(2);
features.enable('ssh-session-recording');
const scopeModelWithAuthorizedAction = store.createRecord('scope', {
authorized_collection_actions: { ['session-recordings']: ['list'] },
type: 'org',
@@ -129,6 +133,8 @@ module('Unit | Abilities | session-recording', function (hooks) {
test('can navigate to session recording when feature is enabled', function (assert) {
assert.expect(2);
features.enable('ssh-session-recording');
const scopeModelWithAuthorizedAction = store.createRecord('scope', {
authorized_collection_actions: { ['session-recordings']: ['list'] },
type: 'global',
@@ -154,7 +160,6 @@ module('Unit | Abilities | session-recording', function (hooks) {
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: { ['session-recordings']: ['list'] },
@@ -21,12 +21,13 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
store = this.owner.lookup('service:store');
canService = this.owner.lookup('service:can');
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);
features.enable('ssh-session-recording');
const storageBucket = store.createRecord('storage-bucket', {
authorized_actions: ['read'],
type: TYPE_STORAGE_BUCKET_PLUGIN,
@@ -39,6 +40,8 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
test('cannot read storage bucket when unauthorized and feature is enabled', function (assert) {
assert.expect(1);
features.enable('ssh-session-recording');
const storageBucket = store.createRecord('storage-bucket', {
authorized_actions: [],
type: TYPE_STORAGE_BUCKET_PLUGIN,
@@ -50,7 +53,6 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
test('cannot read storage bucket when authorized and feature is disabled', function (assert) {
assert.expect(1);
features.disable('session-recording');
const storageBucket = store.createRecord('storage-bucket', {
authorized_actions: ['read'],
@@ -63,7 +65,6 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
test('cannot read storage bucket when unauthorized and feature is disabled', function (assert) {
assert.expect(1);
features.disable('session-recording');
const storageBucket = store.createRecord('storage-bucket', {
authorized_actions: [],
@@ -77,6 +78,8 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
test('can list storage bucket when authorized and in global scope', function (assert) {
assert.expect(1);
features.enable('ssh-session-recording');
const scopeModel = store.createRecord('scope', {
authorized_collection_actions: { 'storage-buckets': ['list'] },
type: 'global',
@@ -93,6 +96,8 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
test('cannot list storage bucket when unauthorized and in global scope', function (assert) {
assert.expect(1);
features.enable('ssh-session-recording');
const scopeModel = store.createRecord('scope', {
authorized_collection_actions: { 'storage-buckets': [] },
type: 'global',
@@ -109,6 +114,8 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
test('can list storage bucket when authorized and in org scope', function (assert) {
assert.expect(1);
features.enable('ssh-session-recording');
const scopeModel = store.createRecord('scope', {
authorized_collection_actions: { 'storage-buckets': ['list'] },
type: 'org',
@@ -125,6 +132,8 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
test('can create storage bucket when authorized and in global scope', function (assert) {
assert.expect(1);
features.enable('ssh-session-recording');
const scopeModel = store.createRecord('scope', {
authorized_collection_actions: { 'storage-buckets': ['create'] },
type: 'global',
@@ -141,6 +150,8 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
test('can create storage bucket when authorized and in org scope', function (assert) {
assert.expect(1);
features.enable('ssh-session-recording');
const scopeModel = store.createRecord('scope', {
authorized_collection_actions: { 'storage-buckets': ['create'] },
type: 'org',
@@ -157,6 +168,8 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
test('can create storage bucket when authorized and in project scope', function (assert) {
assert.expect(1);
features.enable('ssh-session-recording');
const scopeModel = store.createRecord('scope', {
authorized_collection_actions: { 'storage-buckets': ['create'] },
type: 'project',
@@ -173,6 +186,8 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
test('cannot create storage bucket when unauthorized', function (assert) {
assert.expect(1);
features.enable('ssh-session-recording');
const scopeModel = store.createRecord('scope', {
authorized_collection_actions: { 'storage-buckets': [] },
type: 'global',
@@ -189,6 +204,8 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
test('can navigate to storage bucket when authorized', function (assert) {
assert.expect(1);
features.enable('ssh-session-recording');
const scopeModel = store.createRecord('scope', {
authorized_collection_actions: { 'storage-buckets': ['create'] },
type: 'global',
@@ -204,7 +221,6 @@ module('Unit | Abilities | storage-bucket', function (hooks) {
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'] },