From 7ddeb32ceb3206fed2c38d34ad440f80d94188b4 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Thu, 27 Aug 2020 16:38:49 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20"add=20host=20sets?= =?UTF-8?q?"=20workflow=20for=20targets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/generated/models/host-set.js | 5 ++ .../form/target/add-host-sets/index.hbs | 46 +++++++++++++ .../form/target/add-host-sets/index.js | 54 +++++++++++++++ ui/core/app/router.js | 1 + .../project/targets/target/add-host-sets.js | 66 +++++++++++++++++++ .../scope/projects/project/targets/target.hbs | 5 ++ .../project/targets/target/add-host-sets.hbs | 21 ++++++ ui/core/mirage/factories/host-set.js | 4 +- ui/core/mirage/serializers/host-set.js | 1 + ui/core/tests/acceptance/targets-test.js | 8 +++ .../form/target/add-host-sets-test.js | 12 ++++ .../targets/target/add-host-sets-test.js | 11 ++++ ui/core/translations/en-us.yaml | 3 + 13 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 ui/core/app/components/form/target/add-host-sets/index.hbs create mode 100644 ui/core/app/components/form/target/add-host-sets/index.js create mode 100644 ui/core/app/routes/scopes/scope/projects/project/targets/target/add-host-sets.js create mode 100644 ui/core/app/templates/scopes/scope/projects/project/targets/target/add-host-sets.hbs create mode 100644 ui/core/tests/integration/components/form/target/add-host-sets-test.js create mode 100644 ui/core/tests/unit/routes/scopes/scope/projects/project/targets/target/add-host-sets-test.js diff --git a/addons/api/addon/generated/models/host-set.js b/addons/api/addon/generated/models/host-set.js index 6ce3eb882..d1af3fe32 100644 --- a/addons/api/addon/generated/models/host-set.js +++ b/addons/api/addon/generated/models/host-set.js @@ -13,6 +13,11 @@ export default class GeneratedHostSetModel extends BaseModel { readOnly: true }) type; + @attr('string', { + description: 'The owning host catalog ID.', + readOnly: true + }) host_catalog_id; + @attr('string', { description: 'Optional name for identification purposes' }) name; diff --git a/ui/core/app/components/form/target/add-host-sets/index.hbs b/ui/core/app/components/form/target/add-host-sets/index.hbs new file mode 100644 index 000000000..b3c0a1719 --- /dev/null +++ b/ui/core/app/components/form/target/add-host-sets/index.hbs @@ -0,0 +1,46 @@ + + + + + + + + {{t 'form.label.id'}} + {{t 'form.label.name'}} + {{t 'form.label.type'}} + {{t 'resources.host-catalog'}} + + + + {{#each this.hostSetGroups as |hostSetGroup|}} + {{#each hostSetGroup.hostSets as |hostSet|}} + + + + + {{hostSet.name}} + + {{hostSet.type}} + + + {{hostSetGroup.hostCatalog.id}} + + + {{/each}} + {{/each}} + + + + diff --git a/ui/core/app/components/form/target/add-host-sets/index.js b/ui/core/app/components/form/target/add-host-sets/index.js new file mode 100644 index 000000000..afd811806 --- /dev/null +++ b/ui/core/app/components/form/target/add-host-sets/index.js @@ -0,0 +1,54 @@ +import Component from '@glimmer/component'; +import { computed, action } from '@ember/object'; +import { A } from '@ember/array'; + +export default class FormTargetAddHostSetsComponent extends Component { + + // =properties + + /** + * Array of selected host set IDs. + * @type {EmberArray} + */ + selectedHostSetIDs = A(); + + /** + * Groups host sets not already added to the target by host catalog. + * @type {[{hostCatalog: HostCatalogModel, hostSets: [{HostSetModel}]}]} + */ + @computed('args.{hostCatalogs.[],hostSets.[],model.host_sets.[]}') + get hostSetGroups() { + // Get IDs for host sets already added to the current target + const alreadyAddedHostSetIDs = this.args.model.host_sets + .map(({ host_set_id }) => host_set_id); + const notAddedHostSets = this.args.hostSets + .filter(({ id }) => !alreadyAddedHostSetIDs.includes(id)); + // Group host sets by host catalog + const hostSetGroupsObject = notAddedHostSets.reduce((obj, hostSet) => { + if (!obj[hostSet.host_catalog_id]) { + const hostCatalog = this.args.hostCatalogs.findBy('id', hostSet.host_catalog_id); + obj[hostSet.host_catalog_id] = { hostCatalog, hostSets: [] }; + } + obj[hostSet.host_catalog_id].hostSets.push(hostSet); + return obj; + }, {}); + const hostSetGroups = Object.values(hostSetGroupsObject); + return hostSetGroups; + } + + // =actions + + @action + toggleHostSet(hostSet) { + if (!this.selectedHostSetIDs.includes(hostSet.id)) { + this.selectedHostSetIDs.addObject(hostSet.id); + } else { + this.selectedHostSetIDs.removeObject(hostSet.id); + } + } + + @action + submit(fn) { + fn(this.selectedHostSetIDs); + } +} diff --git a/ui/core/app/router.js b/ui/core/app/router.js index e5577e83a..effcb4523 100644 --- a/ui/core/app/router.js +++ b/ui/core/app/router.js @@ -40,6 +40,7 @@ Router.map(function () { this.route('host-sets', function() { this.route('host-set', { path: ':host_set_id'}, function() {}); }); + this.route('add-host-sets'); }); this.route('new'); }); diff --git a/ui/core/app/routes/scopes/scope/projects/project/targets/target/add-host-sets.js b/ui/core/app/routes/scopes/scope/projects/project/targets/target/add-host-sets.js new file mode 100644 index 000000000..f88851fcc --- /dev/null +++ b/ui/core/app/routes/scopes/scope/projects/project/targets/target/add-host-sets.js @@ -0,0 +1,66 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import { all } from 'rsvp'; + +export default class ScopesScopeProjectsProjectTargetsTargetAddHostSetsRoute extends Route { + + // =services + + @service intl; + @service notify; + + // =methods + + /** + * Emtpy out any previously loaded host catalogs and host sets. + */ + beforeModel() { + this.store.unloadAll('host-catalog'); + this.store.unloadAll('host-set'); + } + + /** + * Returns the current target, all host catalogs, and all host sets. + * @return {{target: TargetModel, hostCatalogs: [HostCatalogModel], hostSets: [HostSetModel]}} + */ + async model() { + const target = + this.modelFor('scopes.scope.projects.project.targets.target'); + const { scopeID } = target; + const hostCatalogs = + await this.store.findAll('host-catalog', { adapterOptions: { scopeID } }); + await all(hostCatalogs.map(({ id: hostCatalogID }) => + this.store + .findAll('host-set', { adapterOptions: { scopeID, hostCatalogID } }) + )); + const hostSets = this.store.peekAll('host-set'); + return { + target, + hostCatalogs, + hostSets + }; + } + + // =actions + + @action + async save(target, hostSetIDs) { + try { + await target.addHostSets(hostSetIDs); + this.replaceWith('scopes.scope.projects.project.targets.target.host-sets'); + this.notify.success(this.intl.t('notify.save-success')); + } catch (error) { + // TODO: replace with translated strings + this.notify.error(error.message, { closeAfter: null }); + } + } + + /** + * Redirect to target host sets as if nothing ever happened. + */ + @action + cancel() { + this.replaceWith('scopes.scope.projects.project.targets.target.host-sets'); + } +} diff --git a/ui/core/app/templates/scopes/scope/projects/project/targets/target.hbs b/ui/core/app/templates/scopes/scope/projects/project/targets/target.hbs index 50b97cd47..a04f2ea32 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/targets/target.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/targets/target.hbs @@ -1,3 +1,5 @@ +{{title @model.displayName}} + @@ -21,6 +23,9 @@ + + {{t 'actions.add-host-sets'}} + +{{/if}} + +{{#unless @model.hostSets}} + + + + {{t "descriptions.no-host-sets-for-target"}} + + + +{{/unless}} diff --git a/ui/core/mirage/factories/host-set.js b/ui/core/mirage/factories/host-set.js index 2f12361c6..ad9ae1fa1 100644 --- a/ui/core/mirage/factories/host-set.js +++ b/ui/core/mirage/factories/host-set.js @@ -1,3 +1,5 @@ import factory from '../generated/factories/host-set'; -export default factory.extend(); +export default factory.extend({ + id: (i) => `host-set-id-${i}` +}); diff --git a/ui/core/mirage/serializers/host-set.js b/ui/core/mirage/serializers/host-set.js index c4640cc93..bd680326e 100644 --- a/ui/core/mirage/serializers/host-set.js +++ b/ui/core/mirage/serializers/host-set.js @@ -13,6 +13,7 @@ export default ApplicationSerializer.extend({ _hashForModel(model) { const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments); json.host_ids = model.hostIds; + json.host_catalog_id = model.hostCatalogId; return json; } diff --git a/ui/core/tests/acceptance/targets-test.js b/ui/core/tests/acceptance/targets-test.js index 01f73d443..27f011324 100644 --- a/ui/core/tests/acceptance/targets-test.js +++ b/ui/core/tests/acceptance/targets-test.js @@ -62,4 +62,12 @@ module('Acceptance | targets', function (hooks) { test('viewing target host sets', async function (assert) { assert.expect(0); }); + + test('select and save host sets to add', async function (assert) { + assert.expect(0); + }); + + test('select and cancel host sets to add', async function (assert) { + assert.expect(0); + }); }); diff --git a/ui/core/tests/integration/components/form/target/add-host-sets-test.js b/ui/core/tests/integration/components/form/target/add-host-sets-test.js new file mode 100644 index 000000000..6013607fe --- /dev/null +++ b/ui/core/tests/integration/components/form/target/add-host-sets-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +// import { render } from '@ember/test-helpers'; +// import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | form/target/add-host-sets', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + assert.expect(0); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/project/targets/target/add-host-sets-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/project/targets/target/add-host-sets-test.js new file mode 100644 index 000000000..520a4e41f --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/projects/project/targets/target/add-host-sets-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/projects/project/targets/target/add-host-sets', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + let route = this.owner.lookup('route:scopes/scope/projects/project/targets/target/add-host-sets'); + assert.ok(route); + }); +}); diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml index 8e2ffd179..9974a7b70 100644 --- a/ui/core/translations/en-us.yaml +++ b/ui/core/translations/en-us.yaml @@ -20,6 +20,7 @@ actions: delete-target: Delete target add-host-set: Create Host Set add-host: Create Host + add-host-sets: Add Host Sets auth: titles: no-scopes: No Scopes Available @@ -88,6 +89,7 @@ titles: targets: Targets global: Global no-host-set-hosts: No Hosts Available + no-host-sets-for-target: No Host Sets Available descriptions: host-catalogs: Overview of collections of hosts and host sets. empty-set: There are no items to display yet. You may be able to add items or try back later. @@ -102,6 +104,7 @@ descriptions: targets-welcome: Targets provide Users the means to connect to and interact networked services. A Target contains a collection of Hosts and a collection of Credentials. target-host-sets-welcome: Add Host Sets to this target to provide access. no-host-set-hosts: Before associating hosts with this host set, create hosts within the host catalog. + no-host-sets-for-target: No host sets are available to add to this target. states: info: Info success: Success