diff --git a/addons/api/addon/generated/models/host-set.js b/addons/api/addon/generated/models/host-set.js index 5793cc4a5..6ce3eb882 100644 --- a/addons/api/addon/generated/models/host-set.js +++ b/addons/api/addon/generated/models/host-set.js @@ -9,7 +9,8 @@ export default class GeneratedHostSetModel extends BaseModel { // =attributes @attr('string', { - description: 'The type of the resource, to help differentiate schemas' + description: 'The type of the resource, to help differentiate schemas', + readOnly: true }) type; @attr('string', { @@ -36,7 +37,8 @@ export default class GeneratedHostSetModel extends BaseModel { version; @attr('number', { - description: 'The total count of hosts in this host set\nOutput only.' + description: 'The total count of hosts in this host set\nOutput only.', + readOnly: true }) size; } diff --git a/addons/api/addon/models/host-set.js b/addons/api/addon/models/host-set.js index 01a376f6b..bdfb07214 100644 --- a/addons/api/addon/models/host-set.js +++ b/addons/api/addon/models/host-set.js @@ -1,3 +1,30 @@ import GeneratedHostSetModel from '../generated/models/host-set'; +import { fragmentArray } from 'ember-data-model-fragments/attributes'; -export default class HostSetModel extends GeneratedHostSetModel {} +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()`. + */ + @fragmentArray('fragment-string', {readOnly: true}) host_ids; + + // =methods + + /** + * Saves the `host_ids` array on the host set via the `set-hosts` method. + * See serializer and adapter for more information. + * @return {Promise} + */ + saveHostIDs() { + return this.save({ + adapterOptions: { + method: 'set-hosts', + serializeHostIDs: true + } + }); + } + +} diff --git a/addons/api/addon/serializers/host-set.js b/addons/api/addon/serializers/host-set.js new file mode 100644 index 000000000..24d2fff60 --- /dev/null +++ b/addons/api/addon/serializers/host-set.js @@ -0,0 +1,52 @@ +import ApplicationSerializer from './application'; + +export default class HostSetSerializer extends ApplicationSerializer { + + // =methods + + /** + * If `adapterOptions.serializeHostIDs` is true, the serialization should + * include **only host_ids** and the version. Normally, these are not + * serialized. + * @override + * @param {Snapshot} snapshot + * @return {object} + */ + serialize(snapshot) { + const serializeHostIDs = snapshot?.adapterOptions?.serializeHostIDs; + let serialized = super.serialize(...arguments); + if (serializeHostIDs) serialized = this.serializeWithHostIDs(snapshot); + return serialized; + } + + /** + * Returns a payload containing only the host_ids array. + * @param {Snapshot} snapshot + * @return {object} + */ + serializeWithHostIDs(snapshot) { + return { + version: snapshot.attr('version'), + host_ids: snapshot.attr('host_ids').map(hostID => hostID.attr('value')) + }; + } + + /** + * 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? + // TODO: host_ids might actually be attributes.host_ids + if (resourceHash.host_ids) { + resourceHash.host_ids = + resourceHash.host_ids.map(value => ({ value })); + } + return super.normalize(modelClass, resourceHash); + } + +} diff --git a/addons/api/app/serializers/host-set.js b/addons/api/app/serializers/host-set.js new file mode 100644 index 000000000..8514878c2 --- /dev/null +++ b/addons/api/app/serializers/host-set.js @@ -0,0 +1 @@ +export { default } from 'api/serializers/host-set'; diff --git a/addons/api/tests/unit/serializers/host-set-test.js b/addons/api/tests/unit/serializers/host-set-test.js new file mode 100644 index 000000000..13b6caa0b --- /dev/null +++ b/addons/api/tests/unit/serializers/host-set-test.js @@ -0,0 +1,76 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Serializer | host set', function(hooks) { + setupTest(hooks); + + test('it serializes host sets normally, without host_ids', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('host-set'); + const record = store.createRecord('host-set', { + name: 'Host Set 1', + description: 'Description', + host_ids: [{value: '1'}, {value: '2'}, {value: '3'}], + version: 1 + }); + const snapshot = record._createSnapshot(); + snapshot.adapterOptions = {}; + const serializedRecord = serializer.serialize(snapshot); + assert.deepEqual(serializedRecord, { + name: 'Host Set 1', + description: 'Description', + version: 1 + }); + }); + + test('it serializes only host_ids when `adapterOptions.serializeHostIDs` is true', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('host-set'); + const record = store.createRecord('host-set', { + name: 'Host Set 1', + description: 'Description', + host_ids: [{value: '1'}, {value: '2'}, {value: '3'}], + version: 1 + }); + const snapshot = record._createSnapshot(); + snapshot.adapterOptions = { + serializeHostIDs: true + }; + const serializedRecord = serializer.serialize(snapshot); + assert.deepEqual(serializedRecord, { + host_ids: ['1', '2', '3'], + version: 1 + }); + }); + + test('it normalizes records with array fields', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('host-set'); + const hostSetModelClass = store.createRecord('host-set').constructor; + const payload = { + id: '1', + name: 'Host Set 1', + host_ids: ['1', '2', '3'] + }; + const normalized = serializer.normalizeSingleResponse( + store, + hostSetModelClass, + payload + ); + assert.deepEqual(normalized, { + included: [], + data: { + id: '1', + type: 'host-set', + attributes: { + name: 'Host Set 1', + host_ids: [{value: '1'}, {value: '2'}, {value: '3'}], + }, + relationships: {}, + }, + }); + }); +}); diff --git a/ui/core/mirage/factories/host-catalog.js b/ui/core/mirage/factories/host-catalog.js index 02feb756d..b6ce98947 100644 --- a/ui/core/mirage/factories/host-catalog.js +++ b/ui/core/mirage/factories/host-catalog.js @@ -6,10 +6,8 @@ export default factory.extend({ withChildren: trait({ afterCreate(hostCatalog, server) { const scope = hostCatalog.scope; - // Hosts and host sets are supposed to be associated to a host catalog. - // For simplicity of mocks, we don't actually associate them here. - server.createList('host-set', 3, { scope, hostCatalog }); - server.createList('host', 10, { scope, hostCatalog }); + const hosts = server.createList('host', 10, { scope, hostCatalog }); + server.createList('host-set', 3, { scope, hostCatalog, hosts }); } }) diff --git a/ui/core/mirage/models/host-set.js b/ui/core/mirage/models/host-set.js index 89aba81e6..2f78f5510 100644 --- a/ui/core/mirage/models/host-set.js +++ b/ui/core/mirage/models/host-set.js @@ -1,7 +1,8 @@ -import { Model, belongsTo } from 'ember-cli-mirage'; +import { Model, belongsTo, hasMany } from 'ember-cli-mirage'; export default Model.extend({ - hostCatalog: belongsTo() + hostCatalog: belongsTo(), + hosts: hasMany() }); diff --git a/ui/core/mirage/serializers/host-set.js b/ui/core/mirage/serializers/host-set.js new file mode 100644 index 000000000..9c0fff2c8 --- /dev/null +++ b/ui/core/mirage/serializers/host-set.js @@ -0,0 +1,18 @@ +import ApplicationSerializer from './application'; +import { underscore } from '@ember/string'; + +export default ApplicationSerializer.extend({ + modelName: 'host-set', + + keyForRelationshipIds(relationshipName) { + return `${this._container.inflector.singularize( + underscore(relationshipName) + )}_ids`; + }, + + _hashForModel(model) { + const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments); + json.host_ids = model.hostIds; + return json; + } +}); diff --git a/ui/core/mirage/serializers/host.js b/ui/core/mirage/serializers/host.js new file mode 100644 index 000000000..ccd3994ca --- /dev/null +++ b/ui/core/mirage/serializers/host.js @@ -0,0 +1,5 @@ +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend({ + modelName: 'host' +});