fix: 🐛 fixes issue where grant strings become corrupted in ui

This commit is contained in:
Randall Morey committed 2020-10-30 13:23:19 -04:00
1 parent cf5e17af28
commit efa851928a
8 files changed
+41 -49

No files matched your search

+7 -6
View File
@@ -1,5 +1,5 @@
import GeneratedRoleModel from '../generated/models/role';
import { fragmentArray } from 'ember-data-model-fragments/attributes';
import { fragmentArray, array } from 'ember-data-model-fragments/attributes';
export default class RoleModel extends GeneratedRoleModel {
// =attributes
@@ -18,23 +18,24 @@ export default class RoleModel extends GeneratedRoleModel {
principals;
/**
* Grants is read-only _most_ under normal circumstances. But grants can
* be persisted via a dedicated call to `saveGrants()`.
* Grants is read-only. But grants can be persisted via a dedicated
* call to `saveGrants(grants)`.
*/
@fragmentArray('fragment-string', { readOnly: true }) grants;
@array('string', { readOnly: true }) grant_strings;
// =methods
/**
* Saves the `grants` array on the role via the `set-grants` method.
* See serializer and adapter for more information.
* @param {[string]} grants
* @return {Promise}
*/
saveGrants() {
saveGrants(grants) {
return this.save({
adapterOptions: {
method: 'set-grants',
serializeGrants: true,
grants
},
});
}
+5 -24
View File
@@ -15,9 +15,9 @@ export default class RoleSerializer extends ApplicationSerializer {
* @return {object}
*/
serialize(snapshot) {
const serializeGrants = get(snapshot, 'adapterOptions.serializeGrants');
const grants = get(snapshot, 'adapterOptions.grants');
let serialized = super.serialize(...arguments);
if (serializeGrants) serialized = this.serializeWithGrants(snapshot);
if (grants) serialized = this.serializeWithGrants(snapshot, grants);
const principalIDs = snapshot?.adapterOptions?.principalIDs;
if (principalIDs)
serialized = this.serializewithPrincipals(snapshot, principalIDs);
@@ -27,14 +27,13 @@ export default class RoleSerializer extends ApplicationSerializer {
/**
* Returns a payload containing only the grants array.
* @param {Snapshot} snapshot
* @param {[string]} grants
* @return {object}
*/
serializeWithGrants(snapshot) {
serializeWithGrants(snapshot, grants) {
return {
version: snapshot.attr('version'),
grant_strings: snapshot
.attr('grants')
.map((grant) => grant.attr('value')),
grant_strings: grants
};
}
@@ -51,22 +50,4 @@ export default class RoleSerializer extends ApplicationSerializer {
principal_ids: principalIDs,
};
}
/**
* Converts string arrays to object arrays for use with `fragment-string`.
* E.g. `"foo"` -> `{value: "foo"}`
* @override
* @param {Model} typeClass
* @param {Object} hash
* @return {Object}
*/
normalize(modelClass, resourceHash) {
// TODO: can fragment string normalization be handled generically?
if (resourceHash.grant_strings) {
resourceHash.grants = resourceHash.grant_strings.map((value) => ({
value,
}));
}
return super.normalize(modelClass, resourceHash);
}
}
+3 -3
View File
@@ -17,7 +17,7 @@ module('Unit | Model | role', function (hooks) {
this.server.post('/v1/roles/123abc:set-grants', (schema, request) => {
const body = JSON.parse(request.requestBody);
assert.deepEqual(body, {
grant_strings: ['grant1', 'grant2'],
grant_strings: ['foo', 'bar'],
version: 1,
});
return { id: '123abc' };
@@ -30,7 +30,7 @@ module('Unit | Model | role', function (hooks) {
attributes: {
name: 'Role',
description: 'Description',
grants: [{ value: 'grant1' }, { value: 'grant2' }],
grant_strings: ['grant1', 'grant2'],
version: 1,
scope: {
scope_id: 'o_1',
@@ -40,7 +40,7 @@ module('Unit | Model | role', function (hooks) {
},
});
const model = store.peekRecord('role', '123abc');
await model.saveGrants();
await model.saveGrants(['foo', 'bar']);
});
test('it has an `addPrincipals` method that targets a specific POST API endpoint and serialization', async function (assert) {
@@ -11,7 +11,7 @@ module('Unit | Serializer | role', function (hooks) {
const record = store.createRecord('role', {
name: 'User',
description: 'Description',
grants: [{ value: 'foo' }, { value: 'bar' }],
grant_strings: ['foo', 'bar'],
version: 1,
});
const snapshot = record._createSnapshot();
@@ -24,19 +24,19 @@ module('Unit | Serializer | role', function (hooks) {
});
});
test('it serializes only grants when `adapterOptions.serializeGrants` is true', function (assert) {
test('it serializes only grants when `adapterOptions.grants` is set', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('role');
const record = store.createRecord('role', {
name: 'User',
description: 'Description',
grants: [{ value: 'foo' }, { value: 'bar' }],
grant_strings: ['grant1', 'grant2'],
version: 1,
});
const snapshot = record._createSnapshot();
snapshot.adapterOptions = {
serializeGrants: true,
grants: ['foo', 'bar'],
};
const serializedRecord = serializer.serialize(snapshot);
assert.deepEqual(serializedRecord, {
@@ -67,7 +67,7 @@ module('Unit | Serializer | role', function (hooks) {
type: 'role',
attributes: {
name: 'Role 1',
grants: [{ value: '*' }, { value: '*' }],
grant_strings: ['*', '*'],
principals: [],
},
relationships: {},
@@ -35,7 +35,7 @@
</Rose::Form>
<Rose::Form
@onSubmit={{@submit}}
@onSubmit={{fn @submit this.grantStrings}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
@error={{@model.errors.base}}
@@ -53,14 +53,14 @@
--}}
<Rose::List::KeyValue as |list|>
{{#each @model.grants as |grant|}}
{{#each this.grants as |grant|}}
<list.item as |item|>
<form.input
@contextual={{true}}
@name="grant"
@type="text"
@value={{grant.value}}
@error={{@model.errors.grants}}
@error={{@model.errors.grant_strings}}
as |input|>
<item.key>
<input.label>{{t "form.grant.label"}}</input.label>
@@ -74,7 +74,7 @@
@style="secondary"
@iconOnly="trash"
title={{t "actions.remove"}}
{{on "click" (fn @removeGrant grant)}}
{{on "click" (fn @removeGrant grant.value)}}
>
{{t "actions.remove"}}
</Rose::Button>
@@ -10,6 +10,16 @@ export default class FormRoleGrantsComponent extends Component {
*/
@tracked newGrantString = '';
@computed('args.model.grant_strings.[]')
get grants() {
return this.args.model.grant_strings.map(value => ({ value }));
}
@computed('grants.@each.value')
get grantStrings() {
return this.grants.map(obj => obj.value);
}
/**
* True if the grant string field is empty, false otherwise. This is used
* to disable the submit button.
@@ -37,18 +37,18 @@ export default class ScopesScopeRolesRoleGrantsRoute extends Route {
*/
@action
addGrant(role, grantString) {
role.grants.unshiftObject({ value: grantString });
role.grant_strings.unshiftObject(grantString);
}
/**
* Removes a grant from the role. Grant removal is not immediately permanent;
* users may rollback the change via "cancel" or commit it via "save".
* @param {RoleModel} role
* @param {FragmentString} grant
* @param {string} grantString
*/
@action
removeGrant(role, grant) {
role.grants.removeFragment(grant);
removeGrant(role, grantString) {
role.grant_strings.removeObject(grantString);
}
/**
@@ -59,7 +59,7 @@ export default class ScopesScopeRolesRoleGrantsRoute extends Route {
@loading
@notifyError(({ message }) => message, { catch: true })
@notifySuccess('notifications.save-success')
async save(role) {
await role.saveGrants();
async save(role, grants) {
await role.saveGrants(grants);
}
}
+1 -1
View File
@@ -206,7 +206,7 @@ export default function() {
if (method === 'set-grants') {
updatedAttrs = {
version: attrs.version,
grant_strings: attrs.grant_strings
grantStrings: attrs.grantStrings
};
}