From 013395eec1a0928c3e9900d1187e63de29c05337 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Thu, 13 Aug 2020 17:05:09 -0400 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20initial=20mode?= =?UTF-8?q?ling=20for=20host=20sets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/generated/models/host-set.js | 42 +++++++++++++++++++ addons/api/addon/models/host-catalog.js | 10 ++++- addons/api/addon/models/host-set.js | 13 ++++++ addons/api/app/models/host-set.js | 1 + addons/api/tests/unit/models/host-set-test.js | 13 ++++++ 5 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 addons/api/addon/generated/models/host-set.js create mode 100644 addons/api/addon/models/host-set.js create mode 100644 addons/api/app/models/host-set.js create mode 100644 addons/api/tests/unit/models/host-set-test.js diff --git a/addons/api/addon/generated/models/host-set.js b/addons/api/addon/generated/models/host-set.js new file mode 100644 index 000000000..5793cc4a5 --- /dev/null +++ b/addons/api/addon/generated/models/host-set.js @@ -0,0 +1,42 @@ +import BaseModel from '../../models/base'; +import { attr } from '@ember-data/model'; + +/** + * HostSet is a collection of Hosts created and managed by a HostCatalog + */ +export default class GeneratedHostSetModel extends BaseModel { + + // =attributes + + @attr('string', { + description: 'The type of the resource, to help differentiate schemas' + }) type; + + @attr('string', { + description: 'Optional name for identification purposes' + }) name; + + @attr('string', { + description: 'Optional user-set description for identification purposes' + }) description; + + @attr('date', { + description: 'The time this resource was created\nOutput only.', + readOnly: true + }) created_time; + + @attr('date', { + description: 'The time this resource was last updated\nOutput only.', + readOnly: true + }) updated_time; + + @attr('number', { + description: 'Current version number of this resource.', + }) + version; + + @attr('number', { + description: 'The total count of hosts in this host set\nOutput only.' + }) size; + +} diff --git a/addons/api/addon/models/host-catalog.js b/addons/api/addon/models/host-catalog.js index a0162bfeb..6f3729a47 100644 --- a/addons/api/addon/models/host-catalog.js +++ b/addons/api/addon/models/host-catalog.js @@ -1,3 +1,11 @@ import GeneratedHostCatalogModel from '../generated/models/host-catalog'; +import { hasMany } from '@ember-data/model'; -export default class HostCatalogModel extends GeneratedHostCatalogModel { } +export default class HostCatalogModel extends GeneratedHostCatalogModel { + + /** + * @type {HostSetModel[]} + */ + @hasMany('host-set') hostSets; + +} diff --git a/addons/api/addon/models/host-set.js b/addons/api/addon/models/host-set.js new file mode 100644 index 000000000..ad1fd89a6 --- /dev/null +++ b/addons/api/addon/models/host-set.js @@ -0,0 +1,13 @@ +import GeneratedHostSetModel from '../generated/models/host-set'; +import { belongsTo } from '@ember-data/model'; + +export default class HostSetModel extends GeneratedHostSetModel { + + // =relationships + + /** + * @type {HostCatalogModel} + */ + @belongsTo('host-catalog') hostCatalog; + +} diff --git a/addons/api/app/models/host-set.js b/addons/api/app/models/host-set.js new file mode 100644 index 000000000..6963bf5e3 --- /dev/null +++ b/addons/api/app/models/host-set.js @@ -0,0 +1 @@ +export { default } from 'api/models/host-set'; diff --git a/addons/api/tests/unit/models/host-set-test.js b/addons/api/tests/unit/models/host-set-test.js new file mode 100644 index 000000000..61e007f70 --- /dev/null +++ b/addons/api/tests/unit/models/host-set-test.js @@ -0,0 +1,13 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Model | host set', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let store = this.owner.lookup('service:store'); + let model = store.createRecord('host-set', {}); + assert.ok(model); + }); +}); From 2c1ff699f6f7a3a701eb05c53c7340bf059c0371 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 14 Aug 2020 10:29:24 -0400 Subject: [PATCH 2/3] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20adapter=20to?= =?UTF-8?q?=20generate=20URLs=20for=20host=20sets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/adapters/host-set.js | 38 +++++++++++++++++++ addons/api/addon/mixins/adapter-build-url.js | 38 +++++++++++++------ addons/api/app/adapters/host-set.js | 1 + .../api/tests/unit/adapters/host-set-test.js | 28 ++++++++++++++ 4 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 addons/api/addon/adapters/host-set.js create mode 100644 addons/api/app/adapters/host-set.js create mode 100644 addons/api/tests/unit/adapters/host-set-test.js diff --git a/addons/api/addon/adapters/host-set.js b/addons/api/addon/adapters/host-set.js new file mode 100644 index 000000000..fbbe11495 --- /dev/null +++ b/addons/api/addon/adapters/host-set.js @@ -0,0 +1,38 @@ +import ApplicationAdapter from './application'; +import { inject as service } from '@ember/service'; + +export default class HostSetAdapter extends ApplicationAdapter { + + // =services + + @service store; + + // =methods + + /** + * Append a host catalog URL ahead of the host sets path if `hostCatalogID` is + * passed via adapter options. Otherwise, generate the prefix as normal. + * + * TODO: generalize the interface to resource-prefixed URLs by accepting + * a modelName and an ID, rather than looking for a known key. + * + * @override + * @param {string} path + * @param {string} parentURL + * @param {string} modelName + * @param {string} id + * @param {object} snapshot + * @return {string} urlPrefix + */ + urlPrefix(path, parentURL, modelName, id, snapshot) { + const hostCatalogID = snapshot?.adapterOptions?.hostCatalogID; + if (hostCatalogID) { + const hostCatalogAdapter = this.store.adapterFor('host-catalog'); + return hostCatalogAdapter + ._buildPrefixedURL('host-catalog', hostCatalogID, snapshot); + } else { + return super.urlPrefix(...arguments); + } + } + +} diff --git a/addons/api/addon/mixins/adapter-build-url.js b/addons/api/addon/mixins/adapter-build-url.js index 264364d0d..cee278e05 100644 --- a/addons/api/addon/mixins/adapter-build-url.js +++ b/addons/api/addon/mixins/adapter-build-url.js @@ -46,6 +46,31 @@ export default Mixin.create({ return ''; }, + /** + * Builds the (mostly full) URL with a prefix, but not the suffix. + * The `_buildURL` method adds a suffix. + * Do not call directly. + * @override + * @param {String} modelName + * @param {String} id + * @return {String} url + */ + _buildPrefixedURL(modelName, id, snapshot) { + let path; + let url = []; + const host = get(this, 'host'); + const prefix = this.urlPrefix(null, null, modelName, id, snapshot); + if (modelName) { + path = this.pathForType(modelName); + if (path) url.push(path); + } + if (id) url.push(encodeURIComponent(id)); + if (prefix) url.unshift(prefix); + url = url.join('/'); + if (!host && url && url.charAt(0) !== '/') url = '/' + url; + return `${url}`; + }, + /** * Overrides the default `_buildURL` with a nearly identical version. * This method simply passes arguments into the `urlPrefix` method for @@ -58,19 +83,8 @@ export default Mixin.create({ * @return {String} url */ _buildURL(modelName, id, snapshot) { - let path; - let url = []; - const host = get(this, 'host'); - const prefix = this.urlPrefix(null, null, modelName, id, snapshot); + const url = this._buildPrefixedURL(modelName, id, snapshot); const suffix = this.urlSuffix(modelName, id, snapshot); - if (modelName) { - path = this.pathForType(modelName); - if (path) url.push(path); - } - if (id) url.push(encodeURIComponent(id)); - if (prefix) url.unshift(prefix); - url = url.join('/'); - if (!host && url && url.charAt(0) !== '/') url = '/' + url; return `${url}${suffix}`; }, diff --git a/addons/api/app/adapters/host-set.js b/addons/api/app/adapters/host-set.js new file mode 100644 index 000000000..958a53f64 --- /dev/null +++ b/addons/api/app/adapters/host-set.js @@ -0,0 +1 @@ +export { default } from 'api/adapters/host-set'; diff --git a/addons/api/tests/unit/adapters/host-set-test.js b/addons/api/tests/unit/adapters/host-set-test.js new file mode 100644 index 000000000..e47c7a7d6 --- /dev/null +++ b/addons/api/tests/unit/adapters/host-set-test.js @@ -0,0 +1,28 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Adapter | host set', function(hooks) { + setupTest(hooks); + + test('it generates correct complete URLs', function (assert) { + assert.expect(2); + const scopeID = 'o_1'; + const hostCatalogID = 'hc_098'; + const method = 'my-custom-method'; + const mockSnapshot = { + adapterOptions: { + scopeID, + method + } + }; + const adapter = this.owner.lookup('adapter:host-set'); + // Without hostCatalogID... + let url = adapter.buildURL('host-set', null, mockSnapshot, 'findAll'); + assert.equal(url, '/v1/scopes/o_1/host-sets:my-custom-method'); + // With hostCatalogID... + mockSnapshot.adapterOptions.hostCatalogID = hostCatalogID; + url = adapter.buildURL('host-set', null, mockSnapshot, 'findAll'); + assert.equal(url, '/v1/scopes/o_1/host-catalogs/hc_098/host-sets:my-custom-method'); + }); + +}); From c7f1b6830e2e874ee470e62eb65a27b1abf0cdd1 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 14 Aug 2020 10:46:17 -0400 Subject: [PATCH 3/3] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20host=20set=20?= =?UTF-8?q?mocks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project/host-catalogs/host-catalog.js | 16 ++++++++++++++++ ui/core/mirage/config.js | 8 ++++++++ ui/core/mirage/factories/host-catalog.js | 15 ++++++++++++++- ui/core/mirage/factories/host-set.js | 3 +++ ui/core/mirage/factories/scope.js | 2 +- ui/core/mirage/generated/factories/host-set.js | 17 +++++++++++++++++ ui/core/mirage/scenarios/default.js | 2 +- 7 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 ui/core/mirage/factories/host-set.js create mode 100644 ui/core/mirage/generated/factories/host-set.js diff --git a/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog.js b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog.js index f54947470..d25608633 100644 --- a/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog.js +++ b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog.js @@ -1,4 +1,20 @@ import Route from '@ember/routing/route'; export default class ScopesScopeProjectsProjectHostCatalogsHostCatalogRoute extends Route { + + // =methods + + /** + * Load a host catalog. + * @param {object} params + * @param {string} params.host_catalog_id + * @return {HostCatalogModel} + */ + async model({ host_catalog_id }) { + const { id: scopeID } = this.modelFor('scopes.scope'); + return this.store.findRecord('host-catalog', host_catalog_id, { + adapterOptions: { scopeID }, + }); + } + } diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js index 73342c6b0..89159b0e0 100644 --- a/ui/core/mirage/config.js +++ b/ui/core/mirage/config.js @@ -88,6 +88,14 @@ export default function() { this.patch('/scopes/:scope_id/host-catalogs/:id'); this.del('/scopes/:scope_id/host-catalogs/:id'); + // host-set + + this.get('/scopes/:scope_id/host-catalogs/:host_catalog_id/host-sets'); + this.post('/scopes/:scope_id/host-catalogs/:host_catalog_id/host-sets'); + this.get('/scopes/:scope_id/host-catalogs/:host_catalog_id/host-sets/:id'); + this.patch('/scopes/:scope_id/host-catalogs/:host_catalog_id/host-sets/:id'); + this.del('/scopes/:scope_id/host-catalogs/:host_catalog_id/host-sets/:id'); + // Uncomment the following line and the Response import above // Then change the response code to simulate error responses. // this.get('/scopes/:scope_id/projects', () => new Response(505)); diff --git a/ui/core/mirage/factories/host-catalog.js b/ui/core/mirage/factories/host-catalog.js index ffd635036..d2f84b3c5 100644 --- a/ui/core/mirage/factories/host-catalog.js +++ b/ui/core/mirage/factories/host-catalog.js @@ -1,3 +1,16 @@ import factory from '../generated/factories/host-catalog'; +import { trait } from 'ember-cli-mirage'; -export default factory.extend(); +export default factory.extend({ + + /** + * Create catalogs with associated host sets and hosts. + */ + withChildren: trait({ + afterCreate(record, server) { + const host_set_ids = server.createList('host-set', 3).map(set => set.id); + record.update({ host_set_ids }); + } + }) + +}); diff --git a/ui/core/mirage/factories/host-set.js b/ui/core/mirage/factories/host-set.js new file mode 100644 index 000000000..2f12361c6 --- /dev/null +++ b/ui/core/mirage/factories/host-set.js @@ -0,0 +1,3 @@ +import factory from '../generated/factories/host-set'; + +export default factory.extend(); diff --git a/ui/core/mirage/factories/scope.js b/ui/core/mirage/factories/scope.js index 703e80ad4..d60486d15 100644 --- a/ui/core/mirage/factories/scope.js +++ b/ui/core/mirage/factories/scope.js @@ -14,7 +14,7 @@ export default factory.extend({ }, 'withChildren'); } if (record.type === 'org') { - server.createList('scope', 5, { + server.createList('scope', 3, { type: 'project', scope: { id: record.id, type: record.type } }); diff --git a/ui/core/mirage/generated/factories/host-set.js b/ui/core/mirage/generated/factories/host-set.js new file mode 100644 index 000000000..95636bda0 --- /dev/null +++ b/ui/core/mirage/generated/factories/host-set.js @@ -0,0 +1,17 @@ +import { Factory } from 'ember-cli-mirage'; +import { random, date } from 'faker'; + +/** + * GeneratedHostSetModelFactory + */ +export default Factory.extend({ + + type: 'static', + name: () => random.words(), + description: () => random.words(), + created_time: () => date.recent(), + updated_time: () => date.recent(), + version: () => random.number(), + size: () => random.number(), + +}); diff --git a/ui/core/mirage/scenarios/default.js b/ui/core/mirage/scenarios/default.js index 6f8e29775..9215e8a80 100644 --- a/ui/core/mirage/scenarios/default.js +++ b/ui/core/mirage/scenarios/default.js @@ -27,7 +27,7 @@ export default function(server) { server.db.scopes.where({type: 'project'}).forEach(projectScope => { const scope = { id: projectScope.id, type: projectScope.type }; - server.createList('host-catalog', 2, { scope }); + server.createList('host-catalog', 2, { scope }, 'withChildren'); }); }