From 6b4e82b9b2f77cc1d1a36b6890622ebc423d95c1 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sun, 4 Oct 2020 10:14:47 -0400 Subject: [PATCH 1/7] fix: copyable component should not wrap --- ui/core/app/styles/app.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/core/app/styles/app.scss b/ui/core/app/styles/app.scss index 90663507b..8a45265fa 100644 --- a/ui/core/app/styles/app.scss +++ b/ui/core/app/styles/app.scss @@ -94,6 +94,8 @@ } .copyable { + white-space: nowrap; + .copyable-button { margin: 0; padding: 0 sizing.rems(xxs); From cb03ba5a382126c26693dbb6f747d7e829102a10 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Mon, 5 Oct 2020 11:07:34 -0400 Subject: [PATCH 2/7] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20host=20set=20h?= =?UTF-8?q?osts/add-hosts=20tests=20and=20remove=20dead=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/form/host-set/hosts/index.hbs | 50 ------ .../components/form/host-set/hosts/index.js | 21 --- .../host-sets/host-set/add-hosts.js | 19 -- .../tests/acceptance/host-sets/hosts-test.js | 162 ++++++++++++++++++ .../components/form/host-set/hosts-test.js | 25 --- 5 files changed, 162 insertions(+), 115 deletions(-) delete mode 100644 ui/core/app/components/form/host-set/hosts/index.hbs delete mode 100644 ui/core/app/components/form/host-set/hosts/index.js create mode 100644 ui/core/tests/acceptance/host-sets/hosts-test.js delete mode 100644 ui/core/tests/integration/components/form/host-set/hosts-test.js diff --git a/ui/core/app/components/form/host-set/hosts/index.hbs b/ui/core/app/components/form/host-set/hosts/index.hbs deleted file mode 100644 index 6fb9e5158..000000000 --- a/ui/core/app/components/form/host-set/hosts/index.hbs +++ /dev/null @@ -1,50 +0,0 @@ - - - - - {{!-- - {{#if @model.errors.host_ids}} - - {{#each @model.errors.host_ids as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - --}} - - - - - {{t 'form.id.label'}} - {{t 'form.name.label'}} - {{t 'form.address.label'}} - - - - {{#each this.options as |option|}} - - - - - {{option.host.name}} - {{option.host.attributes.address}} - - {{/each}} - - - - diff --git a/ui/core/app/components/form/host-set/hosts/index.js b/ui/core/app/components/form/host-set/hosts/index.js deleted file mode 100644 index 5b6484bc9..000000000 --- a/ui/core/app/components/form/host-set/hosts/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import Component from '@glimmer/component'; -import { computed } from '@ember/object'; - -export default class FormHostSetHostsComponent extends Component { - // =attributes - - /** - * An array of wrapped `HostModel` instances where the wrapper includes - * a `selected` boolean field indicating if the host is represented - * in `model.host_ids`. - * @type {[{host: HostModel, selected: boolean}]} - */ - @computed('args.{hosts.[],model.host_ids.[]}') - get options() { - const { hosts, model: hostSet } = this.args; - return hosts.map((host) => ({ - host, - selected: Boolean(hostSet.host_ids.findBy('value', host.id)), - })); - } -} diff --git a/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts.js b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts.js index 75fd25649..d892490ab 100644 --- a/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts.js +++ b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts.js @@ -64,25 +64,6 @@ export default class ScopesScopeProjectsProjectHostCatalogsHostCatalogHostSetsHo // =actions - /** - * Toggles the presence of the passed host ID in the passed host set's - * `host_ids` field. - * @param {HostSetModel} hostSet - * @param {HostModel} host - */ - @action - toggleHost(hostSet, host) { - const isHostSelected = Boolean(hostSet.host_ids.findBy('value', host.id)); - const fragment = hostSet.host_ids.findBy('value', host.id); - if (isHostSelected) { - // if host is already selected, filter it out - hostSet.host_ids.removeObject(fragment); - } else { - // if host is not selected, add it - hostSet.host_ids.addObject({ value: host.id }); - } - } - /** * Saves host IDs on the host set. * @param {HostSetModel} hostSet diff --git a/ui/core/tests/acceptance/host-sets/hosts-test.js b/ui/core/tests/acceptance/host-sets/hosts-test.js new file mode 100644 index 000000000..2bd0d6997 --- /dev/null +++ b/ui/core/tests/acceptance/host-sets/hosts-test.js @@ -0,0 +1,162 @@ +import { module, test } from 'qunit'; +import { visit, currentURL, click, find, findAll } from '@ember/test-helpers'; +import { setupApplicationTest } from 'ember-qunit'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import a11yAudit from 'ember-a11y-testing/test-support/audit'; +import { Response } from 'miragejs'; +import { + authenticateSession, + // These are left here intentionally for future reference. + //currentSession, + //invalidateSession, +} from 'ember-simple-auth/test-support'; + +module('Acceptance | host-sets | hosts', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + const instances = { + scopes: { + global: null, + org: null, + project: null, + }, + hostCatalog: null, + hostSet: null, + }; + const urls = { + orgScope: null, + projects: null, + project: null, + hostCatalogs: null, + hostCatalog: null, + hostSets: null, + hostSet: null, + hostSetHosts: null + }; + let hostSetHostsCount; + + hooks.beforeEach(function () { + // Setup Mirage mock resources for this test + authenticateSession(); + instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + instances.scopes.project = this.server.create('scope', { + type: 'project', + scope: { id: instances.scopes.org.id, type: 'org' }, + }); + instances.hostCatalog = this.server.create('host-catalog', { + scope: instances.scopes.project, + }, 'withChildren'); + instances.hostSet = this.server.schema.hostSets.all().models[0]; + // Generate route URLs for resources + urls.orgScope = `/scopes/${instances.scopes.org.id}`; + urls.projects = `${urls.orgScope}/projects`; + urls.project = `${urls.projects}/${instances.hostCatalog.scope.id}`; + urls.hostCatalogs = `${urls.project}/host-catalogs`; + urls.hostCatalog = `${urls.hostCatalogs}/${instances.hostCatalog.id}`; + urls.hostSets = `${urls.hostCatalog}/host-sets`; + urls.hostSet = `${urls.hostSets}/${instances.hostSet.id}`; + urls.hostSetHosts = `${urls.hostSet}/hosts`; + urls.addHosts = `${urls.hostSet}/add-hosts`; + // Counts + hostSetHostsCount = instances.hostSet.hosts.length; + }); + + test('visiting host set hosts', async function (assert) { + assert.expect(2); + await visit(urls.hostSetHosts); + await a11yAudit(); + assert.equal(currentURL(), urls.hostSetHosts); + assert.equal(findAll('tbody tr').length, hostSetHostsCount); + }); + + test('can remove a host', async function (assert) { + assert.expect(2); + await visit(urls.hostSetHosts); + assert.equal(findAll('tbody tr').length, hostSetHostsCount); + await click('tbody tr .rose-dropdown-button-danger'); + assert.equal(findAll('tbody tr').length, hostSetHostsCount - 1); + }); + + test('shows error message on host remove error', async function (assert) { + assert.expect(2); + this.server.post('/host-sets/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + await visit(urls.hostSetHosts); + assert.equal(findAll('tbody tr').length, hostSetHostsCount); + await click('tbody tr .rose-dropdown-button-danger'); + assert.ok(find('[role="alert"]')); + }); + + test('visiting add hosts', async function(assert) { + assert.expect(1); + await visit(urls.addHosts); + await a11yAudit(); + assert.equal(currentURL(), urls.addHosts); + }); + + test('select and save hosts to add', async function (assert) { + assert.expect(3); + instances.hostSet.update({ hostIds: [] }); + await visit(urls.hostSetHosts); + assert.equal(findAll('tbody tr').length, 0); + await click('.rose-layout-page-actions a') + assert.equal(currentURL(), urls.addHosts); + // Click three times to select, unselect, then reselect (for coverage) + await click('tbody label'); + await click('tbody label'); + await click('tbody label'); + await click('form [type="submit"]'); + await visit(urls.hostSetHosts); + assert.equal(findAll('tbody tr').length, 1); + }); + + test('select and cancel hosts to add', async function (assert) { + assert.expect(4); + await visit(urls.hostSetHosts); + assert.equal(findAll('tbody tr').length, hostSetHostsCount); + await click('tbody tr .rose-dropdown-button-danger'); + assert.equal(findAll('tbody tr').length, hostSetHostsCount - 1); + await click('.rose-layout-page-actions a') + assert.equal(currentURL(), urls.addHosts); + await click('tbody label'); + await click('form [type="button"]'); + await visit(urls.hostSetHosts); + assert.equal(findAll('tbody tr').length, hostSetHostsCount - 1); + }); + + test('shows error message on host add error', async function (assert) { + assert.expect(1); + this.server.post('/host-sets/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + instances.hostSet.update({ hostIds: [] }); + await visit(urls.addHosts); + await click('tbody label'); + await click('form [type="submit"]'); + assert.ok(find('[role="alert"]')); + }); +}); diff --git a/ui/core/tests/integration/components/form/host-set/hosts-test.js b/ui/core/tests/integration/components/form/host-set/hosts-test.js deleted file mode 100644 index e5d2bfe87..000000000 --- a/ui/core/tests/integration/components/form/host-set/hosts-test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { render, find } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; - -module('Integration | Component | form/host-set/hosts', function (hooks) { - setupRenderingTest(hooks); - - test('it renders', async function (assert) { - this.submit = () => {}; - this.cancel = () => {}; - this.model = { host_ids: [] }; - this.hosts = []; - - await render(hbs` - `); - - assert.ok(find('form')); - }); -}); From 47c55b56b491d71ab37a2014c72bb07762b5b270 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Mon, 5 Oct 2020 12:35:07 -0400 Subject: [PATCH 3/7] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20update=20copyabl?= =?UTF-8?q?e=20component=20for=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit and ignore untestable clipboard code from coverage --- ui/core/app/components/copyable/index.hbs | 43 ++++++++++--------- ui/core/app/components/copyable/index.js | 35 +++++++-------- .../integration/components/copyable-test.js | 2 +- 3 files changed, 39 insertions(+), 41 deletions(-) diff --git a/ui/core/app/components/copyable/index.hbs b/ui/core/app/components/copyable/index.hbs index 497e5f2f7..618f3315b 100644 --- a/ui/core/app/components/copyable/index.hbs +++ b/ui/core/app/components/copyable/index.hbs @@ -1,20 +1,21 @@ -{{#if this.isClipboardLibrarySupported}} - - {{#if this.copied}} - - {{yield}} - - {{else}} - - {{yield}} - - {{/if}} + + {{#if this.copied}} + + {{yield}} + + {{else}} + + {{yield}} + + {{/if}} + + {{#if this.isClipboardLibrarySupported}} {{this.actionText}} - -{{else}} - {{yield}} -{{/if}} + > + {{this.actionText}} + + {{/if}} + diff --git a/ui/core/app/components/copyable/index.js b/ui/core/app/components/copyable/index.js index 0801d56b5..17652e911 100644 --- a/ui/core/app/components/copyable/index.js +++ b/ui/core/app/components/copyable/index.js @@ -3,7 +3,6 @@ import ClipboardJS from 'clipboard'; import { action } from '@ember/object'; import { generateComponentID } from 'rose/utilities/component-auto-id'; import { task, timeout } from 'ember-concurrency'; -import { bind } from '@ember/runloop'; import { tracked } from '@glimmer/tracking'; export default class CopyableComponent extends Component { @@ -20,6 +19,8 @@ export default class CopyableComponent extends Component { @tracked copied = false; + clipboard = null; + /** * A Ember Concurrency-based task that updates copy icon to a success state * and back after 1 second. This icon flash indicates success on @@ -30,13 +31,12 @@ export default class CopyableComponent extends Component { * is an attribute. * @type {Task} */ + /* istanbul ignore next */ @task(function * () { this.copied = true; yield timeout(1000); this.copied = false; - }).drop() successIconTimer; - - // =methods + }).drop() confirmCopyTimer; /** * Checks for ClipboardJS support. @@ -46,14 +46,6 @@ export default class CopyableComponent extends Component { return ClipboardJS.isSupported(); } - /** - * When copy to clipboard is successful, begin timer to show success icon. - */ - onCopySuccess(clipboardEvent) { - clipboardEvent.clearSelection(); - this.successIconTimer.perform(); - } - // =actions /** @@ -61,12 +53,16 @@ export default class CopyableComponent extends Component { */ @action register() { - if (!this.clipboard) { - this.clipboard = new ClipboardJS(`#${this.copyableButtonId}`, { - text: () => this.args.text, - }); - this.clipboard.on('success', bind(this, this.onCopySuccess)); - } + this.destroy(); + /* istanbul ignore next */ + this.clipboard = new ClipboardJS(`#${this.copyableButtonId}`, { + text: () => this.args.text, + }); + /* istanbul ignore next */ + this.clipboard.on('success', (clipboardEvent) => { + clipboardEvent.clearSelection(); + this.confirmCopyTimer.perform(); + }); } /** @@ -75,7 +71,8 @@ export default class CopyableComponent extends Component { @action destroy() { this.clipboard?.destroy(); - this.successIconTimer.cancelAll(); + this.clipboard = null; + this.confirmCopyTimer.cancelAll(); } } diff --git a/ui/core/tests/integration/components/copyable-test.js b/ui/core/tests/integration/components/copyable-test.js index 8d6a29cf9..0123c353c 100644 --- a/ui/core/tests/integration/components/copyable-test.js +++ b/ui/core/tests/integration/components/copyable-test.js @@ -7,7 +7,7 @@ module('Integration | Component | copyable', function(hooks) { setupRenderingTest(hooks); test('it renders', async function(assert) { - await render(hbs`text`); + await render(hbs`text`); assert.ok(find('.copyable')); assert.equal(find('.copyable-content').textContent.trim(), 'text'); assert.equal(find('.copyable-button').textContent.trim(), 'Copy me'); From f5c3c6e3916c54bf24ac379486720fd694132ca2 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Mon, 5 Oct 2020 11:44:42 -0400 Subject: [PATCH 4/7] fix: Update user accounts table name from 'username' to 'name --- ui/core/app/templates/scopes/scope/users/user/accounts.hbs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/core/app/templates/scopes/scope/users/user/accounts.hbs b/ui/core/app/templates/scopes/scope/users/user/accounts.hbs index 8e0715fa5..e89dd285b 100644 --- a/ui/core/app/templates/scopes/scope/users/user/accounts.hbs +++ b/ui/core/app/templates/scopes/scope/users/user/accounts.hbs @@ -5,7 +5,7 @@ - {{t 'form.username.label'}} + {{t 'form.name.label'}} {{t 'form.id.label'}} {{t "titles.actions"}} @@ -14,7 +14,7 @@ {{#each @model.accounts as |account|}} - {{account.username}} + {{account.displayName}} Date: Mon, 5 Oct 2020 13:05:36 -0400 Subject: [PATCH 5/7] fix: Update user account columns to show name and username --- ui/core/app/templates/scopes/scope/users/user/accounts.hbs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/core/app/templates/scopes/scope/users/user/accounts.hbs b/ui/core/app/templates/scopes/scope/users/user/accounts.hbs index e89dd285b..e011e4fd1 100644 --- a/ui/core/app/templates/scopes/scope/users/user/accounts.hbs +++ b/ui/core/app/templates/scopes/scope/users/user/accounts.hbs @@ -6,6 +6,7 @@ {{t 'form.name.label'}} + {{t 'form.username.label'}} {{t 'form.id.label'}} {{t "titles.actions"}} @@ -15,7 +16,11 @@ {{account.displayName}} +

{{account.description}}

+ + {{account.username}} + Date: Mon, 5 Oct 2020 14:47:13 -0400 Subject: [PATCH 6/7] feat: Add 'sessions' breadcrumb --- .../scopes/scope/projects/project/sessions.js | 19 +++++++++++++++++++ .../scope/projects/project/sessions-test.js | 12 ++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 ui/core/app/controllers/scopes/scope/projects/project/sessions.js create mode 100644 ui/core/tests/unit/controllers/scopes/scope/projects/project/sessions-test.js diff --git a/ui/core/app/controllers/scopes/scope/projects/project/sessions.js b/ui/core/app/controllers/scopes/scope/projects/project/sessions.js new file mode 100644 index 000000000..4c49105c6 --- /dev/null +++ b/ui/core/app/controllers/scopes/scope/projects/project/sessions.js @@ -0,0 +1,19 @@ +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class ScopesScopeProjectsProjectSessionsController extends Controller { + + // =services + + @service intl; + + // =attributes + + /** + * @type {string} + */ + get breadCrumb() { + return this.intl.t('resources.session.title_plural'); + } + +} diff --git a/ui/core/tests/unit/controllers/scopes/scope/projects/project/sessions-test.js b/ui/core/tests/unit/controllers/scopes/scope/projects/project/sessions-test.js new file mode 100644 index 000000000..8361f91f2 --- /dev/null +++ b/ui/core/tests/unit/controllers/scopes/scope/projects/project/sessions-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | scopes/scope/projects/project/sessions', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let controller = this.owner.lookup('controller:scopes/scope/projects/project/sessions'); + assert.ok(controller); + }); +}); From a33a987fd30482da8f8356e4e78b710db5ac2901 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Mon, 5 Oct 2020 15:52:44 -0400 Subject: [PATCH 7/7] fix: Add links to messages for context Let's not abandon users without pointing towards a further action. --- .../components/form/group/add-members/index.hbs | 14 +++++++++----- .../auth-methods/auth-method/accounts/index.hbs | 4 ++++ .../scopes/scope/groups/group/members.hbs | 4 ++++ .../projects/project/targets/target/host-sets.hbs | 4 ++++ .../templates/scopes/scope/users/user/accounts.hbs | 4 ++++ ui/core/translations/resources/en-us.yaml | 3 +++ 6 files changed, 28 insertions(+), 5 deletions(-) diff --git a/ui/core/app/components/form/group/add-members/index.hbs b/ui/core/app/components/form/group/add-members/index.hbs index 3f93cdc7a..53cfd450f 100644 --- a/ui/core/app/components/form/group/add-members/index.hbs +++ b/ui/core/app/components/form/group/add-members/index.hbs @@ -39,10 +39,14 @@ {{#unless this.availableUsers}} - - - {{t "descriptions.no-available-users"}} - - + + + {{t "resources.group.messages.no-members.description"}} + + + + {{t "actions.back"}} + + {{/unless}} diff --git a/ui/core/app/templates/scopes/scope/auth-methods/auth-method/accounts/index.hbs b/ui/core/app/templates/scopes/scope/auth-methods/auth-method/accounts/index.hbs index 291055676..864a55b8b 100644 --- a/ui/core/app/templates/scopes/scope/auth-methods/auth-method/accounts/index.hbs +++ b/ui/core/app/templates/scopes/scope/auth-methods/auth-method/accounts/index.hbs @@ -44,6 +44,10 @@ {{t "resources.account.messages.welcome.description"}} + + + {{t 'resources.account.actions.create'}} + diff --git a/ui/core/app/templates/scopes/scope/groups/group/members.hbs b/ui/core/app/templates/scopes/scope/groups/group/members.hbs index a478d6b83..58a6383c6 100644 --- a/ui/core/app/templates/scopes/scope/groups/group/members.hbs +++ b/ui/core/app/templates/scopes/scope/groups/group/members.hbs @@ -53,6 +53,10 @@ {{t "resources.group.messages.members.description"}} + + + {{t 'resources.group.actions.add-members'}} + diff --git a/ui/core/app/templates/scopes/scope/projects/project/targets/target/host-sets.hbs b/ui/core/app/templates/scopes/scope/projects/project/targets/target/host-sets.hbs index f6e32b99a..5e49da20d 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/targets/target/host-sets.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/targets/target/host-sets.hbs @@ -58,6 +58,10 @@ {{t "resources.target.host-set.messages.welcome.description"}} + + + {{t 'resources.target.actions.add-host-sets'}} + diff --git a/ui/core/app/templates/scopes/scope/users/user/accounts.hbs b/ui/core/app/templates/scopes/scope/users/user/accounts.hbs index e011e4fd1..c8ea1415d 100644 --- a/ui/core/app/templates/scopes/scope/users/user/accounts.hbs +++ b/ui/core/app/templates/scopes/scope/users/user/accounts.hbs @@ -58,6 +58,10 @@ {{t "resources.user.messages.accounts.description"}} + + + {{t 'resources.user.actions.add-accounts'}} + diff --git a/ui/core/translations/resources/en-us.yaml b/ui/core/translations/resources/en-us.yaml index 81ade9e0f..fccc4b485 100644 --- a/ui/core/translations/resources/en-us.yaml +++ b/ui/core/translations/resources/en-us.yaml @@ -42,6 +42,9 @@ group: add-members: title: Add Members description: Select users to add users to this group. + no-members: + title: No Members Available + description: There are no members available to add to this group. actions: add-members: Add Members host-catalog: