diff --git a/addons/api/addon/models/host-set.js b/addons/api/addon/models/host-set.js
index 2750c0cb8..3c56d2879 100644
--- a/addons/api/addon/models/host-set.js
+++ b/addons/api/addon/models/host-set.js
@@ -6,8 +6,8 @@ export default class HostSetModel extends GeneratedHostSetModel {
// =attributes
/**
- * Grants is read-only _most_ under normal circumstances. But grants can
- * be persisted via a dedicated call to `saveGrants()`.
+ * Host IDs are read-only under normal circumstances. But these can
+ * be persisted via a dedicated call to `addHosts()`.
*/
@fragmentArray('fragment-string', {
readOnly: true,
diff --git a/addons/api/addon/models/role.js b/addons/api/addon/models/role.js
index adc7778f5..be29c1c24 100644
--- a/addons/api/addon/models/role.js
+++ b/addons/api/addon/models/role.js
@@ -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()`.
+ * Grant strings are read-only. But grants can be persisted via a dedicated
+ * call to `saveGrantStrings(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.
+ * Saves grant strings on the role via the `set-grants` method.
* See serializer and adapter for more information.
+ * @param {[string]} grantStrings
* @return {Promise}
*/
- saveGrants() {
+ saveGrantStrings(grantStrings) {
return this.save({
adapterOptions: {
method: 'set-grants',
- serializeGrants: true,
+ grantStrings
},
});
}
diff --git a/addons/api/addon/serializers/role.js b/addons/api/addon/serializers/role.js
index 5efd37c0f..59271fe44 100644
--- a/addons/api/addon/serializers/role.js
+++ b/addons/api/addon/serializers/role.js
@@ -15,9 +15,10 @@ export default class RoleSerializer extends ApplicationSerializer {
* @return {object}
*/
serialize(snapshot) {
- const serializeGrants = get(snapshot, 'adapterOptions.serializeGrants');
+ const grantStrings = get(snapshot, 'adapterOptions.grantStrings');
let serialized = super.serialize(...arguments);
- if (serializeGrants) serialized = this.serializeWithGrants(snapshot);
+ if (grantStrings) serialized =
+ this.serializeWithGrantStrings(snapshot, grantStrings);
const principalIDs = snapshot?.adapterOptions?.principalIDs;
if (principalIDs)
serialized = this.serializewithPrincipals(snapshot, principalIDs);
@@ -27,14 +28,13 @@ export default class RoleSerializer extends ApplicationSerializer {
/**
* Returns a payload containing only the grants array.
* @param {Snapshot} snapshot
+ * @param {[string]} grantStrings
* @return {object}
*/
- serializeWithGrants(snapshot) {
+ serializeWithGrantStrings(snapshot, grantStrings) {
return {
version: snapshot.attr('version'),
- grant_strings: snapshot
- .attr('grants')
- .map((grant) => grant.attr('value')),
+ grant_strings: grantStrings
};
}
@@ -51,22 +51,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);
- }
}
diff --git a/addons/api/tests/unit/models/role-test.js b/addons/api/tests/unit/models/role-test.js
index a3b593e4b..9d64e9ac2 100644
--- a/addons/api/tests/unit/models/role-test.js
+++ b/addons/api/tests/unit/models/role-test.js
@@ -12,12 +12,12 @@ module('Unit | Model | role', function (hooks) {
assert.ok(model);
});
- test('it has a `saveGrants` method that targets a specific POST API endpoint and serialization', async function (assert) {
+ test('it has a `saveGrantStrings` method that targets a specific POST API endpoint and serialization', async function (assert) {
assert.expect(1);
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.saveGrantStrings(['foo', 'bar']);
});
test('it has an `addPrincipals` method that targets a specific POST API endpoint and serialization', async function (assert) {
diff --git a/addons/api/tests/unit/serializers/role-test.js b/addons/api/tests/unit/serializers/role-test.js
index 37efeb341..f0d14b17a 100644
--- a/addons/api/tests/unit/serializers/role-test.js
+++ b/addons/api/tests/unit/serializers/role-test.js
@@ -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();
@@ -25,19 +25,19 @@ module('Unit | Serializer | role', function (hooks) {
});
});
- test('it serializes only grants when `adapterOptions.serializeGrants` is true', function (assert) {
+ test('it serializes only grant strings when `adapterOptions.grantStrings` 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,
+ grantStrings: ['foo', 'bar'],
};
const serializedRecord = serializer.serialize(snapshot);
assert.deepEqual(serializedRecord, {
@@ -68,7 +68,7 @@ module('Unit | Serializer | role', function (hooks) {
type: 'role',
attributes: {
name: 'Role 1',
- grants: [{ value: '*' }, { value: '*' }],
+ grant_strings: ['*', '*'],
principals: [],
},
relationships: {},
diff --git a/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx b/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx
index be2871af1..bafffaaa4 100644
--- a/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx
+++ b/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx
@@ -1,7 +1,7 @@
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
import { hbs } from 'ember-cli-htmlbars';
import { action } from '@storybook/addon-actions';
-import { text, boolean } from '@storybook/addon-knobs';
+import { text, boolean, select } from '@storybook/addon-knobs';
@@ -44,6 +44,7 @@ Renders a form radiofield group.
{{yield}}
diff --git a/addons/rose/addon/components/rose/form/radio/radio/index.stories.mdx b/addons/rose/addon/components/rose/form/radio/radio/index.stories.mdx
index 3aeae1f22..633a5c082 100644
--- a/addons/rose/addon/components/rose/form/radio/radio/index.stories.mdx
+++ b/addons/rose/addon/components/rose/form/radio/radio/index.stories.mdx
@@ -1,6 +1,6 @@
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
import { hbs } from 'ember-cli-htmlbars';
-import { text, boolean } from '@storybook/addon-knobs';
+import { text, boolean, select } from '@storybook/addon-knobs';
@@ -28,6 +28,7 @@ Renders a form radiofield.
@value={{value}}
@selectedValue={{selectedValue}}
@disabled={{disabled}}
+ @icon={{icon}}
/>
`,
context: {
@@ -39,6 +40,17 @@ Renders a form radiofield.
'value and selectedValue must match to select radiofield'
),
disabled: boolean('disabled', false),
+ icon: select(
+ 'icon',
+ {
+ 'none': null,
+ 'user-organization': 'user-organization',
+ 'copy-action': 'copy-action',
+ 'unfold-more': 'unfold-more',
+ 'unfold-less': 'unfold-less',
+ },
+ null
+ ),
}
}}
diff --git a/addons/rose/addon/styles/rose/components/form/_radio.scss b/addons/rose/addon/styles/rose/components/form/_radio.scss
index f20d53a1b..860c39ca5 100644
--- a/addons/rose/addon/styles/rose/components/form/_radio.scss
+++ b/addons/rose/addon/styles/rose/components/form/_radio.scss
@@ -33,6 +33,11 @@ $data-svg-prefix: 'data:image/svg+xml;utf-8,';
color: currentColor;
padding-left: sizing.rems(l);
+ .rose-icon {
+ margin-right: sizing.rems(xxs);
+ margin-left: sizing.rems(xxs);
+ }
+
&::before {
position: absolute;
top: sizing.rems(xs);
diff --git a/addons/rose/tests/integration/components/rose/form/radio/radio-test.js b/addons/rose/tests/integration/components/rose/form/radio/radio-test.js
index f3aa7df11..9f1677571 100644
--- a/addons/rose/tests/integration/components/rose/form/radio/radio-test.js
+++ b/addons/rose/tests/integration/components/rose/form/radio/radio-test.js
@@ -17,6 +17,11 @@ module('Integration | Component | rose/form/radio/radio', function (hooks) {
assert.equal(await find('label').textContent.trim(), 'Label');
});
+ test('it supports an icon', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('svg'));
+ });
+
test('it is checked when @value and @selectedValue values match', async function (assert) {
await render(
hbs``
diff --git a/docker-compose-embedding.yml b/docker-compose-embedding.yml
index c65137f29..278634b15 100644
--- a/docker-compose-embedding.yml
+++ b/docker-compose-embedding.yml
@@ -6,7 +6,7 @@ version: '3.8'
services:
node: &node
- image: node:12
+ image: docker.mirror.hashicorp.services/node:12
volumes:
- './:/src'
- 'icu_node_modules:/src/node_modules'
diff --git a/ui/core/app/components/form/role/grants/index.hbs b/ui/core/app/components/form/role/grants/index.hbs
index 8b5e97f28..bbe47e7d6 100644
--- a/ui/core/app/components/form/role/grants/index.hbs
+++ b/ui/core/app/components/form/role/grants/index.hbs
@@ -35,7 +35,7 @@
- {{#each @model.grants as |grant|}}
+ {{#each this.grants as |grant|}}
{{t "form.grant.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"}}
diff --git a/ui/core/app/components/form/role/grants/index.js b/ui/core/app/components/form/role/grants/index.js
index f1f0f8ce3..a55a28093 100644
--- a/ui/core/app/components/form/role/grants/index.js
+++ b/ui/core/app/components/form/role/grants/index.js
@@ -10,6 +10,22 @@ export default class FormRoleGrantsComponent extends Component {
*/
@tracked newGrantString = '';
+ /**
+ * @type {[object]}
+ */
+ @computed('args.model.grant_strings.[]')
+ get grants() {
+ return this.args.model.grant_strings.map(value => ({ value }));
+ }
+
+ /**
+ * @type {[string]}
+ */
+ @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.
diff --git a/ui/core/app/components/form/role/index.hbs b/ui/core/app/components/form/role/index.hbs
index ac5d4af80..eaf8bdce2 100644
--- a/ui/core/app/components/form/role/index.hbs
+++ b/ui/core/app/components/form/role/index.hbs
@@ -54,13 +54,15 @@
{{#each @grantScopes as |grantScope|}}
{{#each grantScope.subScopes as |subScope|}}
@@ -68,6 +70,7 @@
{{#each subScope.subScopes as |subSubScope|}}
diff --git a/ui/core/app/routes/scopes/scope/roles/role/grants.js b/ui/core/app/routes/scopes/scope/roles/role/grants.js
index af56f1990..721e93446 100644
--- a/ui/core/app/routes/scopes/scope/roles/role/grants.js
+++ b/ui/core/app/routes/scopes/scope/roles/role/grants.js
@@ -37,29 +37,30 @@ 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);
}
/**
* Save an role in current scope.
* @param {RoleModel} role
+ * @param {[string]} grantStrings
*/
@action
@loading
@notifyError(({ message }) => message, { catch: true })
@notifySuccess('notifications.save-success')
- async save(role) {
- await role.saveGrants();
+ async save(role, grantStrings) {
+ await role.saveGrantStrings(grantStrings);
}
}
diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js
index 50d1b5344..d2596e43f 100644
--- a/ui/core/mirage/config.js
+++ b/ui/core/mirage/config.js
@@ -206,7 +206,7 @@ export default function() {
if (method === 'set-grants') {
updatedAttrs = {
version: attrs.version,
- grant_strings: attrs.grant_strings
+ grantStrings: attrs.grantStrings
};
}