From 905ee8785898eef16075fa0232b1b9e59b52174c Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Thu, 17 Sep 2020 14:39:15 -0400 Subject: [PATCH 01/23] feat: Display password account's loginname in list --- addons/api/addon/models/account.js | 19 ++++++++++++++++++- addons/api/addon/serializers/account.js | 7 +++++-- addons/api/tests/unit/models/account-test.js | 12 ++++++++++++ .../auth-method/accounts/index.hbs | 4 ++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/addons/api/addon/models/account.js b/addons/api/addon/models/account.js index b28558732..4a6ee08c2 100644 --- a/addons/api/addon/models/account.js +++ b/addons/api/addon/models/account.js @@ -1,13 +1,26 @@ import GeneratedAccountModel from '../generated/models/account'; import { fragment } from 'ember-data-model-fragments/attributes'; +import { computed } from '@ember/object'; export default class AccountModel extends GeneratedAccountModel { + + // =attributes + /** * Attributes of this resource, if any, represented as a JSON fragment. * @type {FragmentAccountAttributesModel} */ @fragment('fragment-account-attributes', { defaultValue: {} }) attributes; + /** + * Convenience for getting login_name attribute. + * @type {string} + */ + @computed('attributes.login_name') + get username() { + return this.attributes.login_name; + } + // =methods /** @@ -41,7 +54,11 @@ export default class AccountModel extends GeneratedAccountModel { * @param {object} options.adapterOptions * @return {Promise} */ - changePassword(currentPassword, newPassword, options = { adapterOptions: {} }) { + changePassword( + currentPassword, + newPassword, + options = { adapterOptions: {} } + ) { const defaultAdapterOptions = { method: 'change-password', currentPassword, diff --git a/addons/api/addon/serializers/account.js b/addons/api/addon/serializers/account.js index 416bc488e..01618a081 100644 --- a/addons/api/addon/serializers/account.js +++ b/addons/api/addon/serializers/account.js @@ -22,8 +22,11 @@ export default class AccountSerializer extends ApplicationSerializer { } if (snapshot?.adapterOptions?.method === 'change-password') { const { currentPassword, newPassword } = snapshot?.adapterOptions; - serialized = - this.serializeForChangePassword(snapshot, currentPassword, newPassword); + serialized = this.serializeForChangePassword( + snapshot, + currentPassword, + newPassword + ); } return serialized; } diff --git a/addons/api/tests/unit/models/account-test.js b/addons/api/tests/unit/models/account-test.js index 554a72be5..b8ce36b45 100644 --- a/addons/api/tests/unit/models/account-test.js +++ b/addons/api/tests/unit/models/account-test.js @@ -9,4 +9,16 @@ module('Unit | Model | account', function (hooks) { let model = store.createRecord('account', {}); assert.ok(model); }); + + test('it has an username attribute', function (assert) { + assert.expect(2); + const store = this.owner.lookup('service:store'); + let model = store.createRecord('account', { + name: 'name', + type: 'password', + }); + assert.equal(model.username, null); + model.attributes.login_name = 'username'; + assert.equal(model.username, 'username'); + }); }); 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 8586ac7c0..83ed4fa1a 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 @@ -4,6 +4,7 @@ {{t 'form.label.name'}} + {{t 'form.label.identification'}} {{t 'form.label.id'}} @@ -19,6 +20,9 @@

{{account.description}}

+ + {{account.username}} + {{account.id}} From 9e482d643484e5a14bdd599543e1b1a19406303e Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 18 Sep 2020 10:16:40 -0400 Subject: [PATCH 02/23] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20initial=20s?= =?UTF-8?q?ession=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/generated/models/session.js | 30 ++++++++++++++++++++ addons/api/addon/models/session.js | 3 ++ addons/api/app/models/session.js | 1 + addons/api/tests/unit/models/session-test.js | 13 +++++++++ 4 files changed, 47 insertions(+) create mode 100644 addons/api/addon/generated/models/session.js create mode 100644 addons/api/addon/models/session.js create mode 100644 addons/api/app/models/session.js create mode 100644 addons/api/tests/unit/models/session-test.js diff --git a/addons/api/addon/generated/models/session.js b/addons/api/addon/generated/models/session.js new file mode 100644 index 000000000..c4dd740c9 --- /dev/null +++ b/addons/api/addon/generated/models/session.js @@ -0,0 +1,30 @@ +import BaseModel from '../../models/base'; +import { attr } from '@ember-data/model'; + +export default class GeneratedSessionModel extends BaseModel { + + // =attributes + + @attr('string') user_id; + @attr('string') target_id; + @attr('string') host_id; + @attr('string') host_set_id; + + @attr('string', { + description: 'The name of the current status of this session.', + }) + status; + + @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; + +} diff --git a/addons/api/addon/models/session.js b/addons/api/addon/models/session.js new file mode 100644 index 000000000..a624fb6b0 --- /dev/null +++ b/addons/api/addon/models/session.js @@ -0,0 +1,3 @@ +import Model from '@ember-data/model'; + +export default class SessionModel extends Model { } diff --git a/addons/api/app/models/session.js b/addons/api/app/models/session.js new file mode 100644 index 000000000..3106229d6 --- /dev/null +++ b/addons/api/app/models/session.js @@ -0,0 +1 @@ +export { default } from 'api/models/session'; diff --git a/addons/api/tests/unit/models/session-test.js b/addons/api/tests/unit/models/session-test.js new file mode 100644 index 000000000..2d373a2f6 --- /dev/null +++ b/addons/api/tests/unit/models/session-test.js @@ -0,0 +1,13 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Model | session', 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('session', {}); + assert.ok(model); + }); +}); From 7a17ff3bfa3aa685d9ce4d97d180ce7156784663 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 18 Sep 2020 10:59:07 -0400 Subject: [PATCH 03/23] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20support=20fo?= =?UTF-8?q?r=20custom=20cancel=20session=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/generated/models/session.js | 5 +++ addons/api/addon/models/session.js | 28 +++++++++++++++-- addons/api/addon/serializers/session.js | 31 +++++++++++++++++++ addons/api/app/serializers/session.js | 1 + .../tests/unit/serializers/session-test.js | 31 +++++++++++++++++++ 5 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 addons/api/addon/serializers/session.js create mode 100644 addons/api/app/serializers/session.js create mode 100644 addons/api/tests/unit/serializers/session-test.js diff --git a/addons/api/addon/generated/models/session.js b/addons/api/addon/generated/models/session.js index c4dd740c9..ee751a988 100644 --- a/addons/api/addon/generated/models/session.js +++ b/addons/api/addon/generated/models/session.js @@ -27,4 +27,9 @@ export default class GeneratedSessionModel extends BaseModel { }) updated_time; + @attr('number', { + description: 'Current version number of this resource.', + }) + version; + } diff --git a/addons/api/addon/models/session.js b/addons/api/addon/models/session.js index a624fb6b0..b37f08fc2 100644 --- a/addons/api/addon/models/session.js +++ b/addons/api/addon/models/session.js @@ -1,3 +1,27 @@ -import Model from '@ember-data/model'; +import GeneratedSessionModel from '../generated/models/session'; -export default class SessionModel extends Model { } +export default class SessionModel extends GeneratedSessionModel { + + // =methods + + /** + * Cancels the session via the `cancel` method. + * See serializer and adapter for more information. + * @param {object} options + * @param {object} options.adapterOptions + * @return {Promise} + */ + cancelSession(options = { adapterOptions: {} }) { + const defaultAdapterOptions = { + method: 'cancel', + }; + return this.save({ + ...options, + adapterOptions: { + ...defaultAdapterOptions, + ...options.adapterOptions, + }, + }); + } + +} diff --git a/addons/api/addon/serializers/session.js b/addons/api/addon/serializers/session.js new file mode 100644 index 000000000..d8d5ea7f8 --- /dev/null +++ b/addons/api/addon/serializers/session.js @@ -0,0 +1,31 @@ +import ApplicationSerializer from './application'; + +export default class SessionSerializer extends ApplicationSerializer { + // =methods + + /** + * If `adapterOptions.method` is `cancel`, the serialization should + * include the version field only. + * @override + * @param {Snapshot} snapshot + * @return {object} + */ + serialize(snapshot) { + let serialized = super.serialize(...arguments); + if (snapshot?.adapterOptions?.method === 'cancel') { + serialized = this.serializeForCancel(snapshot); + } + return serialized; + } + + /** + * Returns a payload containing only version + * @param {Snapshot} snapshot + * @return {object} + */ + serializeForCancel(snapshot) { + return { + version: snapshot.attr('version'), + }; + } +} diff --git a/addons/api/app/serializers/session.js b/addons/api/app/serializers/session.js new file mode 100644 index 000000000..407fe5c23 --- /dev/null +++ b/addons/api/app/serializers/session.js @@ -0,0 +1 @@ +export { default } from 'api/serializers/session'; diff --git a/addons/api/tests/unit/serializers/session-test.js b/addons/api/tests/unit/serializers/session-test.js new file mode 100644 index 000000000..fde7c215f --- /dev/null +++ b/addons/api/tests/unit/serializers/session-test.js @@ -0,0 +1,31 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Serializer | session', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let store = this.owner.lookup('service:store'); + let serializer = store.serializerFor('session'); + + assert.ok(serializer); + }); + + test('it serializes only version when `adapterOptions.method` is `cancel`', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('session'); + const record = store.createRecord('session', { + version: 1, + }); + const snapshot = record._createSnapshot(); + snapshot.adapterOptions = { + method: 'cancel', + }; + const serializedRecord = serializer.serialize(snapshot); + assert.deepEqual(serializedRecord, { + version: 1, + }); + }); +}); From 4229658815795ed6b840a4e5c75159a598593be4 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 18 Sep 2020 12:54:31 -0400 Subject: [PATCH 04/23] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20sessions=20l?= =?UTF-8?q?ist=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rose/addon/styles/rose/_typography.scss | 4 ++ ui/core/app/formats.js | 9 +++ ui/core/app/helpers/format-date-iso-human.js | 7 +++ ui/core/app/helpers/format-date-iso.js | 5 ++ ui/core/app/router.js | 1 + .../scopes/scope/projects/project/sessions.js | 25 +++++++++ .../scope/projects/project/sessions/index.js | 4 ++ .../scope/projects/project/_sidebar.hbs | 3 + .../scope/projects/project/sessions.hbs | 3 + .../scope/projects/project/sessions/index.hbs | 56 +++++++++++++++++++ ui/core/mirage/config.js | 7 +++ ui/core/mirage/factories/session.js | 35 ++++++++++++ ui/core/mirage/generated/factories/session.js | 14 +++++ ui/core/mirage/models/session.js | 11 ++++ ui/core/mirage/scenarios/default.js | 1 + ui/core/mirage/serializers/session.js | 22 ++++++++ .../helpers/format-date-iso-human-test.js | 17 ++++++ .../helpers/format-date-iso-test.js | 17 ++++++ .../scope/projects/project/sessions-test.js | 11 ++++ .../projects/project/sessions/index-test.js | 11 ++++ ui/core/translations/en-us.yaml | 5 ++ 21 files changed, 268 insertions(+) create mode 100644 ui/core/app/helpers/format-date-iso-human.js create mode 100644 ui/core/app/helpers/format-date-iso.js create mode 100644 ui/core/app/routes/scopes/scope/projects/project/sessions.js create mode 100644 ui/core/app/routes/scopes/scope/projects/project/sessions/index.js create mode 100644 ui/core/app/templates/scopes/scope/projects/project/sessions.hbs create mode 100644 ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs create mode 100644 ui/core/mirage/factories/session.js create mode 100644 ui/core/mirage/generated/factories/session.js create mode 100644 ui/core/mirage/models/session.js create mode 100644 ui/core/mirage/serializers/session.js create mode 100644 ui/core/tests/integration/helpers/format-date-iso-human-test.js create mode 100644 ui/core/tests/integration/helpers/format-date-iso-test.js create mode 100644 ui/core/tests/unit/routes/scopes/scope/projects/project/sessions-test.js create mode 100644 ui/core/tests/unit/routes/scopes/scope/projects/project/sessions/index-test.js diff --git a/addons/rose/addon/styles/rose/_typography.scss b/addons/rose/addon/styles/rose/_typography.scss index b44914ce8..c5fda86e4 100644 --- a/addons/rose/addon/styles/rose/_typography.scss +++ b/addons/rose/addon/styles/rose/_typography.scss @@ -54,3 +54,7 @@ p { a { color: var(--action); } + +time { + font-variant-numeric: tabular-nums; +} diff --git a/ui/core/app/formats.js b/ui/core/app/formats.js index f5d1f1502..9ea07ca26 100644 --- a/ui/core/app/formats.js +++ b/ui/core/app/formats.js @@ -7,6 +7,15 @@ export default { }, }, date: { + default: { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + hour12: false + }, hhmmss: { hour: 'numeric', minute: 'numeric', diff --git a/ui/core/app/helpers/format-date-iso-human.js b/ui/core/app/helpers/format-date-iso-human.js new file mode 100644 index 000000000..2744a4eeb --- /dev/null +++ b/ui/core/app/helpers/format-date-iso-human.js @@ -0,0 +1,7 @@ +import { helper } from '@ember/component/helper'; + +export default helper(function formatDateIsoHuman(params/*, hash*/) { + return params[0].toISOString() + .replace('T', ' ') + .replace(/\.\d*Z/, ''); +}); diff --git a/ui/core/app/helpers/format-date-iso.js b/ui/core/app/helpers/format-date-iso.js new file mode 100644 index 000000000..52cd9687d --- /dev/null +++ b/ui/core/app/helpers/format-date-iso.js @@ -0,0 +1,5 @@ +import { helper } from '@ember/component/helper'; + +export default helper(function formatDateIso(params/*, hash*/) { + return params[0].toISOString(); +}); diff --git a/ui/core/app/router.js b/ui/core/app/router.js index 204ced768..0894289f9 100644 --- a/ui/core/app/router.js +++ b/ui/core/app/router.js @@ -52,6 +52,7 @@ Router.map(function () { }); this.route('new'); }); + this.route('sessions', function() {}); }); this.route('new'); }); diff --git a/ui/core/app/routes/scopes/scope/projects/project/sessions.js b/ui/core/app/routes/scopes/scope/projects/project/sessions.js new file mode 100644 index 000000000..ed7c699f0 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/projects/project/sessions.js @@ -0,0 +1,25 @@ +import Route from '@ember/routing/route'; +import { all, hash } from 'rsvp'; + +export default class ScopesScopeProjectsProjectSessionsRoute extends Route { + + // =methods + + /** + * Loads all sessions under the current scope and encapsulates them into + * an array of objects containing their associated users and targets. + * @return {Promise{[{session: SessionModel, user: UserModel, target: TargetModel}]}} + */ + async model() { + const { id: scope_id } = this.modelFor('scopes.scope.projects.project'); + const sessions = await this.store.query('session', { scope_id }); + return all( + sessions.map(session => hash({ + session, + user: this.store.findRecord('user', session.user_id), + target: this.store.findRecord('target', session.target_id), + })) + ); + } + +} diff --git a/ui/core/app/routes/scopes/scope/projects/project/sessions/index.js b/ui/core/app/routes/scopes/scope/projects/project/sessions/index.js new file mode 100644 index 000000000..a8ae56692 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/projects/project/sessions/index.js @@ -0,0 +1,4 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeProjectsProjectSessionsIndexRoute extends Route { +} diff --git a/ui/core/app/templates/scopes/scope/projects/project/_sidebar.hbs b/ui/core/app/templates/scopes/scope/projects/project/_sidebar.hbs index ae4f79e0b..e29af0451 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/_sidebar.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/_sidebar.hbs @@ -1,4 +1,7 @@ + + {{t "resources.sessions"}} + {{t "resources.targets"}} diff --git a/ui/core/app/templates/scopes/scope/projects/project/sessions.hbs b/ui/core/app/templates/scopes/scope/projects/project/sessions.hbs new file mode 100644 index 000000000..37fea125e --- /dev/null +++ b/ui/core/app/templates/scopes/scope/projects/project/sessions.hbs @@ -0,0 +1,3 @@ +{{title (t "resources.sessions")}} + +{{outlet}} diff --git a/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs b/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs new file mode 100644 index 000000000..abbbbdfac --- /dev/null +++ b/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs @@ -0,0 +1,56 @@ + + + + + + + +

{{t "resources.sessions"}}

+
+ + + + {{#if @model}} + + + + + {{t "form.label.id"}} + {{t "resources.user"}} + {{t "resources.target"}} + {{t "form.label.started"}} + + + + {{#each @model as |sessionAggregate|}} + + + {{sessionAggregate.session.id}} + + {{sessionAggregate.user.displayName}} + {{sessionAggregate.target.displayName}} + + + + + {{/each}} + + + + {{else}} + + + + + {{t "descriptions.no-sessions"}} + + + + + {{/if}} + + + +
diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js index e7044298f..844211c61 100644 --- a/ui/core/mirage/config.js +++ b/ui/core/mirage/config.js @@ -292,4 +292,11 @@ export default function() { } return target.update(updatedAttrs); }); + + // session + + this.get('/sessions', function ({ sessions }, { queryParams: { scope_id } }) { + return sessions.where(session => session.scopeId === scope_id); + }); + this.get('/sessions/:id'); } diff --git a/ui/core/mirage/factories/session.js b/ui/core/mirage/factories/session.js new file mode 100644 index 000000000..9e5e31913 --- /dev/null +++ b/ui/core/mirage/factories/session.js @@ -0,0 +1,35 @@ +import factory from '../generated/factories/target'; +import { trait } from 'ember-cli-mirage'; + +export default factory.extend({ + + id: (i) => `session-id-${i}`, + + /** + * Sessions generated with this trait will automatically form + * associations to user, target, host set, and host (if available) within + * the current scope. + */ + withAssociations: trait({ + afterCreate(record, server) { + const scopeId = record.scopeId; + if (record.scope.type === 'project') { + const orgScopeId = record.scope?.scope.id; + const user = + server.schema.users.where({ scopeId: orgScopeId }).models[0]; + if (user) record.update({ user }); + } + const target = server.schema.targets.where({ scopeId }).models[0]; + if (target) { + record.update({ target }); + const hostSet = target.hostSets.models[0]; + if (hostSet) { + record.update({ hostSet }); + const host = hostSet.hosts.models[0]; + if (host) record.update({ host }); + } + } + } + }) + +}); diff --git a/ui/core/mirage/generated/factories/session.js b/ui/core/mirage/generated/factories/session.js new file mode 100644 index 000000000..25aca3759 --- /dev/null +++ b/ui/core/mirage/generated/factories/session.js @@ -0,0 +1,14 @@ +import { Factory } from 'ember-cli-mirage'; +import { random, date } from 'faker'; + +/** + * GeneratedSessionModelFactory + */ +export default Factory.extend({ + + type: 'tcp', + created_time: () => date.recent(), + updated_time: () => date.recent(), + version: () => random.number(), + +}); diff --git a/ui/core/mirage/models/session.js b/ui/core/mirage/models/session.js new file mode 100644 index 000000000..91a4da594 --- /dev/null +++ b/ui/core/mirage/models/session.js @@ -0,0 +1,11 @@ +import { Model, belongsTo } from 'ember-cli-mirage'; + +export default Model.extend({ + + scope: belongsTo(), + user: belongsTo(), + target: belongsTo(), + host: belongsTo(), + hostSet: belongsTo(), + +}); diff --git a/ui/core/mirage/scenarios/default.js b/ui/core/mirage/scenarios/default.js index 9492f92e3..5d1a85fe6 100644 --- a/ui/core/mirage/scenarios/default.js +++ b/ui/core/mirage/scenarios/default.js @@ -26,6 +26,7 @@ export default function(server) { server.schema.scopes.where({type: 'project'}).models.forEach(scope => { server.createList('host-catalog', 2, { scope }, 'withChildren'); server.createList('target', 2, { scope }, 'withRandomHostSets'); + server.createList('session', 4, { scope }, 'withAssociations'); }); } diff --git a/ui/core/mirage/serializers/session.js b/ui/core/mirage/serializers/session.js new file mode 100644 index 000000000..3e14a23e6 --- /dev/null +++ b/ui/core/mirage/serializers/session.js @@ -0,0 +1,22 @@ +import ApplicationSerializer from './application'; +import { underscore } from '@ember/string'; + +export default ApplicationSerializer.extend({ + modelName: 'session', + + keyForRelationshipId(relationshipName) { + return `${this._container.inflector.singularize( + underscore(relationshipName) + )}_id`; + }, + + _hashForModel(model) { + const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments); + json.user_id = model.userId; + json.target_id = model.targetId; + json.host_set_id = model.hostSetId; + json.host_id = model.hostId; + return json; + } + +}); diff --git a/ui/core/tests/integration/helpers/format-date-iso-human-test.js b/ui/core/tests/integration/helpers/format-date-iso-human-test.js new file mode 100644 index 000000000..94eec92fa --- /dev/null +++ b/ui/core/tests/integration/helpers/format-date-iso-human-test.js @@ -0,0 +1,17 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Helper | format-date-iso-human', function(hooks) { + setupRenderingTest(hooks); + + // Replace this with your real tests. + test('it renders', async function(assert) { + this.set('inputValue', new Date('2020-01-01T00:00:00.999Z')); + + await render(hbs`{{format-date-iso-human inputValue}}`); + + assert.equal(this.element.textContent.trim(), '2020-01-01 00:00:00'); + }); +}); diff --git a/ui/core/tests/integration/helpers/format-date-iso-test.js b/ui/core/tests/integration/helpers/format-date-iso-test.js new file mode 100644 index 000000000..2954d3890 --- /dev/null +++ b/ui/core/tests/integration/helpers/format-date-iso-test.js @@ -0,0 +1,17 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Helper | format-date-iso', function(hooks) { + setupRenderingTest(hooks); + + // Replace this with your real tests. + test('it renders', async function(assert) { + this.set('inputValue', new Date('2020-01-01T00:00:00.999Z')); + + await render(hbs`{{format-date-iso inputValue}}`); + + assert.equal(this.element.textContent.trim(), '2020-01-01T00:00:00.999Z'); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/project/sessions-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/project/sessions-test.js new file mode 100644 index 000000000..70769438e --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/projects/project/sessions-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/projects/project/sessions', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + let route = this.owner.lookup('route:scopes/scope/projects/project/sessions'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/project/sessions/index-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/project/sessions/index-test.js new file mode 100644 index 000000000..a774bf223 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/projects/project/sessions/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/projects/project/sessions/index', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + let route = this.owner.lookup('route:scopes/scope/projects/project/sessions/index'); + assert.ok(route); + }); +}); diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml index e458c2b50..2d13fa33e 100644 --- a/ui/core/translations/en-us.yaml +++ b/ui/core/translations/en-us.yaml @@ -77,6 +77,8 @@ resources: targets: Targets accounts: Accounts account: Account + sessions: Sessions + session: Session titles: iam: IAM new: New @@ -114,6 +116,7 @@ titles: accounts: Accounts account: Account accounts-welcome: Welcome to Accounts + no-sessions: No Active Sessions 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. @@ -136,6 +139,7 @@ descriptions: no-available-users: No users are available to add to this group. account-set-password: Set a password for the account. accounts-welcome: No accounts are available in this auth method. + no-sessions: There are no active sessions at this time. states: info: Info success: Success @@ -157,6 +161,7 @@ form: default_port: Default Port current_password: Current Password new_password: New Password + started: Started errors: titles: unauthenticated-error: You are not signed in From 3ae2db95625d86f5926fc833f0240ff6559e528e Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Fri, 18 Sep 2020 14:15:19 -0400 Subject: [PATCH 05/23] fix: Add type check for account username --- addons/api/addon/models/account.js | 8 +++++--- addons/api/tests/unit/models/account-test.js | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/addons/api/addon/models/account.js b/addons/api/addon/models/account.js index 4a6ee08c2..4f880e07d 100644 --- a/addons/api/addon/models/account.js +++ b/addons/api/addon/models/account.js @@ -13,12 +13,14 @@ export default class AccountModel extends GeneratedAccountModel { @fragment('fragment-account-attributes', { defaultValue: {} }) attributes; /** - * Convenience for getting login_name attribute. + * Convenience for getting username in account. * @type {string} */ - @computed('attributes.login_name') + @computed('attributes') get username() { - return this.attributes.login_name; + if(this.type === 'password') { + return this.attributes.login_name; + } } // =methods diff --git a/addons/api/tests/unit/models/account-test.js b/addons/api/tests/unit/models/account-test.js index b8ce36b45..f6cbfbcc5 100644 --- a/addons/api/tests/unit/models/account-test.js +++ b/addons/api/tests/unit/models/account-test.js @@ -10,7 +10,7 @@ module('Unit | Model | account', function (hooks) { assert.ok(model); }); - test('it has an username attribute', function (assert) { + test('it has an username attribute for password account', function (assert) { assert.expect(2); const store = this.owner.lookup('service:store'); let model = store.createRecord('account', { @@ -21,4 +21,5 @@ module('Unit | Model | account', function (hooks) { model.attributes.login_name = 'username'; assert.equal(model.username, 'username'); }); + }); From 1c16f8fe19e36a55f421d6e9f6f7d847fb5469dc Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Fri, 18 Sep 2020 14:33:10 -0400 Subject: [PATCH 06/23] fix: Lint errors for account username return type --- addons/api/addon/models/account.js | 10 +++++++--- addons/api/tests/unit/models/account-test.js | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/addons/api/addon/models/account.js b/addons/api/addon/models/account.js index 4f880e07d..de5209c34 100644 --- a/addons/api/addon/models/account.js +++ b/addons/api/addon/models/account.js @@ -16,11 +16,15 @@ export default class AccountModel extends GeneratedAccountModel { * Convenience for getting username in account. * @type {string} */ - @computed('attributes') + @computed('attributes.{login_name}') get username() { - if(this.type === 'password') { - return this.attributes.login_name; + let username = ''; + switch (this.type) { + case 'password': + username = this.attributes.login_name; + break; } + return username; } // =methods diff --git a/addons/api/tests/unit/models/account-test.js b/addons/api/tests/unit/models/account-test.js index f6cbfbcc5..f937b3c26 100644 --- a/addons/api/tests/unit/models/account-test.js +++ b/addons/api/tests/unit/models/account-test.js @@ -17,7 +17,7 @@ module('Unit | Model | account', function (hooks) { name: 'name', type: 'password', }); - assert.equal(model.username, null); + assert.notOk(model.username); model.attributes.login_name = 'username'; assert.equal(model.username, 'username'); }); From 2aa330fd3612b4a84b3e5ccc93a80fe6a848dc87 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 18 Sep 2020 15:38:32 -0400 Subject: [PATCH 07/23] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20cancel=20ses?= =?UTF-8?q?sion=20feature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../styles/rose/components/table/_table.scss | 4 ++++ .../scopes/scope/projects/project/sessions.js | 22 +++++++++++++++++++ .../scopes/scope/groups/group/members.hbs | 2 +- .../host-catalog/host-sets/host-set/hosts.hbs | 2 +- .../scope/projects/project/sessions/index.hbs | 6 +++++ .../project/targets/target/host-sets.hbs | 2 +- .../scopes/scope/roles/role/principals.hbs | 2 +- ui/core/mirage/config.js | 14 ++++++++++++ ui/core/translations/en-us.yaml | 2 ++ 9 files changed, 52 insertions(+), 4 deletions(-) diff --git a/addons/rose/addon/styles/rose/components/table/_table.scss b/addons/rose/addon/styles/rose/components/table/_table.scss index ba63d37aa..956ab5faa 100644 --- a/addons/rose/addon/styles/rose/components/table/_table.scss +++ b/addons/rose/addon/styles/rose/components/table/_table.scss @@ -34,4 +34,8 @@ padding: sizing.rems(l) sizing.rems(s); } } + + [class*="rose-button"] { + margin-bottom: 0; + } } diff --git a/ui/core/app/routes/scopes/scope/projects/project/sessions.js b/ui/core/app/routes/scopes/scope/projects/project/sessions.js index ed7c699f0..48bbb5cc0 100644 --- a/ui/core/app/routes/scopes/scope/projects/project/sessions.js +++ b/ui/core/app/routes/scopes/scope/projects/project/sessions.js @@ -1,8 +1,15 @@ import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; import { all, hash } from 'rsvp'; export default class ScopesScopeProjectsProjectSessionsRoute extends Route { + // =services + + @service intl; + @service notify; + // =methods /** @@ -22,4 +29,19 @@ export default class ScopesScopeProjectsProjectSessionsRoute extends Route { ); } + /** + * Cancels the specified session and notifies user of success or error. + * @param {SessionModel} + */ + @action + async cancelSession(session) { + try { + await session.cancelSession(); + this.notify.success(this.intl.t('notify.session-canceled')); + } catch (error) { + // TODO: replace with translated strings + this.notify.error(error.message, { closeAfter: null }); + } + } + } 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 fb562164b..24a6e13d9 100644 --- a/ui/core/app/templates/scopes/scope/groups/group/members.hbs +++ b/ui/core/app/templates/scopes/scope/groups/group/members.hbs @@ -7,7 +7,7 @@ {{t 'form.label.name'}} {{t 'form.label.id'}} - {{t "form.label.actions"}} + {{t "titles.actions"}} diff --git a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.hbs b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.hbs index 437140df4..b0ef4a625 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.hbs @@ -6,7 +6,7 @@ {{t "form.label.name"}} {{t "form.label.type"}} {{t "form.label.id"}} - {{t "form.label.actions"}} + {{t "titles.actions"}} diff --git a/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs b/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs index abbbbdfac..83e480e5e 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs @@ -19,6 +19,7 @@ {{t "resources.user"}} {{t "resources.target"}} {{t "form.label.started"}} + {{t "titles.actions"}} @@ -34,6 +35,11 @@ {{format-date-iso-human sessionAggregate.session.created_time}} + + + {{t "actions.cancel"}} + + {{/each}} 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 40f12df7f..583a8df55 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 @@ -8,7 +8,7 @@ {{t "form.label.name"}} {{t "form.label.type"}} {{t "form.label.id"}} - {{t "form.label.actions"}} + {{t "titles.actions"}} diff --git a/ui/core/app/templates/scopes/scope/roles/role/principals.hbs b/ui/core/app/templates/scopes/scope/roles/role/principals.hbs index b364b136f..160c91c7b 100644 --- a/ui/core/app/templates/scopes/scope/roles/role/principals.hbs +++ b/ui/core/app/templates/scopes/scope/roles/role/principals.hbs @@ -8,7 +8,7 @@ {{t 'form.label.name'}} {{t 'form.label.type'}} {{t 'form.label.id'}} - {{t "form.label.actions"}} + {{t "titles.actions"}} diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js index 844211c61..fa23ee0e4 100644 --- a/ui/core/mirage/config.js +++ b/ui/core/mirage/config.js @@ -299,4 +299,18 @@ export default function() { return sessions.where(session => session.scopeId === scope_id); }); this.get('/sessions/:id'); + this.post('/sessions/:idMethod', function ({ sessions }, { params: { idMethod } }) { + const attrs = this.normalizedRequestAttrs(); + const id = idMethod.split(':')[0]; + const method = idMethod.split(':')[1]; + const session = sessions.find(id); + const updatedAttrs = { + version: attrs.version + }; + if (method === 'cancel') { + updatedAttrs.status = 'canceled'; + } + return session.update(updatedAttrs); + }); + } diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml index 2d13fa33e..7fb4748de 100644 --- a/ui/core/translations/en-us.yaml +++ b/ui/core/translations/en-us.yaml @@ -117,6 +117,7 @@ titles: account: Account accounts-welcome: Welcome to Accounts no-sessions: No Active Sessions + actions: Actions 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. @@ -180,3 +181,4 @@ notify: create-success: Created succesfully. save-success: Saved succesfully. delete-success: Deleted successfully. + session-canceled: Session canceled. From 5fb1b1465887ed0de1f2acf90a483c7b6b348f6a Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 18 Sep 2020 15:59:42 -0400 Subject: [PATCH 08/23] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20show=20session=20s?= =?UTF-8?q?tatus=20in=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scopes/scope/projects/project/sessions/index.hbs | 4 ++++ ui/core/mirage/config.js | 2 +- ui/core/mirage/factories/session.js | 7 +++++++ ui/core/translations/en-us.yaml | 1 + 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs b/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs index 83e480e5e..6fe82d12b 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs @@ -19,6 +19,7 @@ {{t "resources.user"}} {{t "resources.target"}} {{t "form.label.started"}} + {{t "form.label.status"}} {{t "titles.actions"}} @@ -35,6 +36,9 @@ {{format-date-iso-human sessionAggregate.session.created_time}} + + {{sessionAggregate.session.status}} + {{t "actions.cancel"}} diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js index fa23ee0e4..87bf05300 100644 --- a/ui/core/mirage/config.js +++ b/ui/core/mirage/config.js @@ -308,7 +308,7 @@ export default function() { version: attrs.version }; if (method === 'cancel') { - updatedAttrs.status = 'canceled'; + updatedAttrs.status = 'canceling'; } return session.update(updatedAttrs); }); diff --git a/ui/core/mirage/factories/session.js b/ui/core/mirage/factories/session.js index 9e5e31913..efe1bd63a 100644 --- a/ui/core/mirage/factories/session.js +++ b/ui/core/mirage/factories/session.js @@ -1,9 +1,16 @@ import factory from '../generated/factories/target'; import { trait } from 'ember-cli-mirage'; +import { random } from 'faker'; export default factory.extend({ id: (i) => `session-id-${i}`, + status: () => random.arrayElement([ + 'pending', + 'active', + 'canceling', + 'terminated' + ]), /** * Sessions generated with this trait will automatically form diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml index 7fb4748de..a37c9bfe3 100644 --- a/ui/core/translations/en-us.yaml +++ b/ui/core/translations/en-us.yaml @@ -163,6 +163,7 @@ form: current_password: Current Password new_password: New Password started: Started + status: Status errors: titles: unauthenticated-error: You are not signed in From b8257fe6dc4e4cc05046fca714b4d3e5802d42eb Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 18 Sep 2020 16:03:52 -0400 Subject: [PATCH 09/23] chore: status is read-only --- addons/api/addon/generated/models/session.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/api/addon/generated/models/session.js b/addons/api/addon/generated/models/session.js index ee751a988..a33426e4c 100644 --- a/addons/api/addon/generated/models/session.js +++ b/addons/api/addon/generated/models/session.js @@ -12,6 +12,7 @@ export default class GeneratedSessionModel extends BaseModel { @attr('string', { description: 'The name of the current status of this session.', + readOnly: true, }) status; From 711818216c42ad2bfdb8d59f54c3a3e0a55e3919 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 19 Sep 2020 09:36:48 -0400 Subject: [PATCH 10/23] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20session=20po?= =?UTF-8?q?lling=20to=20display=20changes=20to=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scopes/scope/projects/project/sessions.js | 38 +++++++++++++++++++ ui/core/mirage/config.js | 17 ++++++++- ui/core/mirage/factories/session.js | 16 +++++--- ui/core/package.json | 1 + yarn.lock | 9 +++++ 5 files changed, 73 insertions(+), 8 deletions(-) diff --git a/ui/core/app/routes/scopes/scope/projects/project/sessions.js b/ui/core/app/routes/scopes/scope/projects/project/sessions.js index 48bbb5cc0..b41d76d7a 100644 --- a/ui/core/app/routes/scopes/scope/projects/project/sessions.js +++ b/ui/core/app/routes/scopes/scope/projects/project/sessions.js @@ -2,6 +2,9 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import { action } from '@ember/object'; import { all, hash } from 'rsvp'; +import { task, timeout } from 'ember-concurrency'; + +const POLL_TIMEOUT_SECONDS = 2.5; export default class ScopesScopeProjectsProjectSessionsRoute extends Route { @@ -10,6 +13,25 @@ export default class ScopesScopeProjectsProjectSessionsRoute extends Route { @service intl; @service notify; + // =attributes + + /** + * A simple Ember Concurrency-based polling task that refreshes the route + * every POLL_TIMEOUT_SECONDS seconds. This is necessary to display changes + * to session `status` that may occur. + * + * NOTE: tasks are sort of attributes and sort of methods, but they are not + * language-level constructs. Thus we annotate this task as if it + * is an attribute. + * @type {Task} + */ + @task(function * () { + while(true) { + yield timeout(POLL_TIMEOUT_SECONDS * 1000); + yield this.refresh(); + } + }).drop() poller; + // =methods /** @@ -29,6 +51,22 @@ export default class ScopesScopeProjectsProjectSessionsRoute extends Route { ); } + /** + * When this route is activated (entered), begin polling for changes. + */ + activate() { + this.poller.perform(); + } + + /** + * When this route is deactivated (exited), stop polling for changes. + */ + deactivate() { + this.poller.cancelAll(); + } + + // =actions + /** * Cancels the specified session and notifies user of success or error. * @param {SessionModel} diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js index 87bf05300..6803aac3d 100644 --- a/ui/core/mirage/config.js +++ b/ui/core/mirage/config.js @@ -1,5 +1,6 @@ import config from '../config/environment'; import { authHandler, deauthHandler } from './route-handlers/auth'; +import { pickRandomStatusString } from './factories/session'; //import { Response } from 'miragejs'; export default function() { @@ -295,10 +296,22 @@ export default function() { // session + // To simulate changes to `session.status` that may occur in the backend, + // we quietly randomize the value of the field on GET. this.get('/sessions', function ({ sessions }, { queryParams: { scope_id } }) { - return sessions.where(session => session.scopeId === scope_id); + sessions.where(session => session.scopeId === scope_id) + .models + .forEach(session => session.update({ + status: pickRandomStatusString() + })); + return sessions.where(session => session.scopeId === scope_id) + }); + this.get('/sessions/:id', function ({ sessions }, { params: { id } }) { + const session = sessions.find(id); + return session.update({ + status: pickRandomStatusString() + }); }); - this.get('/sessions/:id'); this.post('/sessions/:idMethod', function ({ sessions }, { params: { idMethod } }) { const attrs = this.normalizedRequestAttrs(); const id = idMethod.split(':')[0]; diff --git a/ui/core/mirage/factories/session.js b/ui/core/mirage/factories/session.js index efe1bd63a..0100a4b75 100644 --- a/ui/core/mirage/factories/session.js +++ b/ui/core/mirage/factories/session.js @@ -2,15 +2,19 @@ import factory from '../generated/factories/target'; import { trait } from 'ember-cli-mirage'; import { random } from 'faker'; +const statusStrings = [ + 'pending', + 'active', + 'canceling', + 'terminated' +]; + +export const pickRandomStatusString = () => random.arrayElement(statusStrings); + export default factory.extend({ id: (i) => `session-id-${i}`, - status: () => random.arrayElement([ - 'pending', - 'active', - 'canceling', - 'terminated' - ]), + status: pickRandomStatusString, /** * Sessions generated with this trait will automatically form diff --git a/ui/core/package.json b/ui/core/package.json index 258f13a98..212ce3416 100644 --- a/ui/core/package.json +++ b/ui/core/package.json @@ -54,6 +54,7 @@ "ember-cli-sri": "^2.1.1", "ember-cli-template-lint": "^1.0.0-beta.3", "ember-cli-uglify": "^3.0.0", + "ember-concurrency": "^1.3.0", "ember-data": "~3.16.0", "ember-data-model-fragments": "5.0.0-beta.0", "ember-exam": "^5.0.1", diff --git a/yarn.lock b/yarn.lock index 5304e5ace..658b62c2e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8923,6 +8923,15 @@ ember-compatibility-helpers@^1.1.1, ember-compatibility-helpers@^1.1.2, ember-co ember-cli-version-checker "^2.1.1" semver "^5.4.1" +ember-concurrency@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ember-concurrency/-/ember-concurrency-1.3.0.tgz#66f90fb792687470bcee1172adc0ebf33f5e8b9c" + integrity sha512-DwGlfWFpYyAkTwsedlEtK4t1DznJSculAW6Vq5S1C0shVPc5b6tTpHB2FFYisannSYkm+wpm1f1Pd40qiNPtOQ== + dependencies: + ember-cli-babel "^7.7.3" + ember-compatibility-helpers "^1.2.0" + ember-maybe-import-regenerator "^0.1.6" + ember-cookies@^0.5.0, ember-cookies@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/ember-cookies/-/ember-cookies-0.5.2.tgz#06e33463f2f83254fefaf224cc944dec3fb9d3ba" From 6c55811631e96b53aa6c2a48e9834ff25e38381c Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 19 Sep 2020 10:05:33 -0400 Subject: [PATCH 11/23] =?UTF-8?q?chore:=20=F0=9F=A4=96=20upgrade=20rose=20?= =?UTF-8?q?to=20ember-source=203.16?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit so that we can use nested components --- addons/rose/index.js | 2 +- addons/rose/package.json | 2 +- yarn.lock | 53 +++------------------------------------- 3 files changed, 5 insertions(+), 52 deletions(-) diff --git a/addons/rose/index.js b/addons/rose/index.js index 73cccf517..6de73c3ca 100644 --- a/addons/rose/index.js +++ b/addons/rose/index.js @@ -7,7 +7,7 @@ var mergeTrees = require('broccoli-merge-trees'); module.exports = { name: require('./package').name, included(app) { - this._super.included(app); + this._super.included.apply(this, arguments); this.includeStyles(app); this.includeIcons(app); diff --git a/addons/rose/package.json b/addons/rose/package.json index 5a8cccc67..fcf5cd228 100644 --- a/addons/rose/package.json +++ b/addons/rose/package.json @@ -61,7 +61,7 @@ "ember-qunit": "^4.6.0", "ember-radio-button": "^2.0.1", "ember-resolver": "^7.0.0", - "ember-source": "~3.12.0", + "ember-source": "~3.16.0", "ember-source-channel-url": "^2.0.1", "ember-try": "^1.4.0", "ember-uuid": "^2.1.0", diff --git a/yarn.lock b/yarn.lock index 5304e5ace..5fd953ce0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4295,11 +4295,6 @@ ast-types@0.13.3: resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.3.tgz#50da3f28d17bdbc7969a3a2d83a0e4a72ae755a7" integrity sha512-XTZ7xGML849LkQP86sWdQzfhwbt3YwIO6MqbX9mUNYY98VKaaVZP7YNNm70IpwecbkkxmfC5IYAzOQ/2p29zRA== -ast-types@0.9.6: - version "0.9.6" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9" - integrity sha1-ECyenpAF0+fjgpvwxPok7oYu6bk= - astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" @@ -8434,7 +8429,7 @@ ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.11.0, ember-cli-babel@^6.12.0, ember-cli-version-checker "^2.1.2" semver "^5.5.0" -ember-cli-babel@^7.1.2, ember-cli-babel@^7.10.0, ember-cli-babel@^7.11.0, ember-cli-babel@^7.11.1, ember-cli-babel@^7.12.0, ember-cli-babel@^7.13.2, ember-cli-babel@^7.17.2, ember-cli-babel@^7.18.0, ember-cli-babel@^7.19.0, ember-cli-babel@^7.4.1, ember-cli-babel@^7.5.0, ember-cli-babel@^7.7.0, ember-cli-babel@^7.7.3: +ember-cli-babel@^7.1.2, ember-cli-babel@^7.10.0, ember-cli-babel@^7.11.0, ember-cli-babel@^7.11.1, ember-cli-babel@^7.12.0, ember-cli-babel@^7.13.2, ember-cli-babel@^7.17.2, ember-cli-babel@^7.18.0, ember-cli-babel@^7.19.0, ember-cli-babel@^7.4.1, ember-cli-babel@^7.5.0, ember-cli-babel@^7.7.3: version "7.20.0" resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.20.0.tgz#aa77eadd15036a053534fc68f7933bb97c5a1e25" integrity sha512-DwV5Cbn8wlRFpG8zQXxYPRb0NRnnaj/2qVDODeUEjM/doDhoDDbTSSaEI5Pf0kIlkVIimyGB8qpGv1fkgj2IfQ== @@ -9280,13 +9275,6 @@ ember-route-action-helper@^2.0.8: ember-cli-babel "^6.8.1" ember-getowner-polyfill "^2.0.0" -ember-router-generator@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/ember-router-generator/-/ember-router-generator-1.2.3.tgz#8ed2ca86ff323363120fc14278191e9e8f1315ee" - integrity sha1-jtLKhv8yM2MSD8FCeBkeno8TFe4= - dependencies: - recast "^0.11.3" - ember-router-generator@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ember-router-generator/-/ember-router-generator-2.0.0.tgz#d04abfed4ba8b42d166477bbce47fccc672dbde0" @@ -9324,26 +9312,6 @@ ember-source-channel-url@^2.0.1: dependencies: got "^8.0.1" -ember-source@~3.12.0: - version "3.12.3" - resolved "https://registry.yarnpkg.com/ember-source/-/ember-source-3.12.3.tgz#112462b8c1dfa124c0d021ca7eeeb04b5a5488ca" - integrity sha512-uzyoOVg1ygTY/InjwP0sgIkHiMiRylDauMY9K+vM15WkmmfrMaetklxxnqWThvsL4onBRRW8MKCj+q41Ra/NQQ== - dependencies: - broccoli-funnel "^2.0.2" - broccoli-merge-trees "^3.0.2" - chalk "^2.4.2" - ember-cli-babel "^7.7.0" - ember-cli-get-component-path-option "^1.0.0" - ember-cli-is-package-missing "^1.0.0" - ember-cli-normalize-entity-name "^1.0.0" - ember-cli-path-utils "^1.0.0" - ember-cli-string-utils "^1.1.0" - ember-cli-version-checker "^3.1.3" - ember-router-generator "^1.2.3" - inflection "^1.12.0" - jquery "^3.4.1" - resolve "^1.11.1" - ember-source@~3.16.0: version "3.16.8" resolved "https://registry.yarnpkg.com/ember-source/-/ember-source-3.16.8.tgz#83f1a55a2b9835b272d7ec5b121a7af490c86725" @@ -9930,11 +9898,6 @@ esprima@~3.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.0.0.tgz#53cf247acda77313e551c3aa2e73342d3fb4f7d9" integrity sha1-U88kes2ncxPlUcOqLnM0LT+099k= -esprima@~3.1.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= - esquery@^1.0.1: version "1.3.1" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" @@ -15667,7 +15630,7 @@ prismjs@~1.17.0: optionalDependencies: clipboard "^2.0.0" -private@^0.1.6, private@^0.1.8, private@~0.1.5: +private@^0.1.6, private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== @@ -16488,16 +16451,6 @@ realpath-native@^1.1.0: dependencies: util.promisify "^1.0.0" -recast@^0.11.3: - version "0.11.23" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" - integrity sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM= - dependencies: - ast-types "0.9.6" - esprima "~3.1.0" - private "~0.1.5" - source-map "~0.5.0" - recast@^0.17.3: version "0.17.6" resolved "https://registry.yarnpkg.com/recast/-/recast-0.17.6.tgz#64ae98d0d2dfb10ff92ff5fb9ffb7371823b69fa" @@ -17786,7 +17739,7 @@ source-map@0.4.x, source-map@^0.4.2: dependencies: amdefine ">=0.0.4" -source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1: +source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= From 5d8a6db6b1079d1484134554ba0582fbecd1ec3d Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 19 Sep 2020 10:29:57 -0400 Subject: [PATCH 12/23] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20convert=20card?= =?UTF-8?q?=20components=20to=20nested=20structure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/rose/addon/components/rose/card/link.js | 7 ------- .../card/link.hbs => components/rose/card/link/index.hbs} | 0 .../rose/card/{link.stories.mdx => link/index.stories.mdx} | 0 addons/rose/addon/components/rose/cards.js | 7 ------- .../rose/cards.hbs => components/rose/cards/index.hbs} | 0 .../rose/{cards.stories.mdx => cards/index.stories.mdx} | 0 6 files changed, 14 deletions(-) delete mode 100644 addons/rose/addon/components/rose/card/link.js rename addons/rose/addon/{templates/components/rose/card/link.hbs => components/rose/card/link/index.hbs} (100%) rename addons/rose/addon/components/rose/card/{link.stories.mdx => link/index.stories.mdx} (100%) delete mode 100644 addons/rose/addon/components/rose/cards.js rename addons/rose/addon/{templates/components/rose/cards.hbs => components/rose/cards/index.hbs} (100%) rename addons/rose/addon/components/rose/{cards.stories.mdx => cards/index.stories.mdx} (100%) diff --git a/addons/rose/addon/components/rose/card/link.js b/addons/rose/addon/components/rose/card/link.js deleted file mode 100644 index 162a08a0e..000000000 --- a/addons/rose/addon/components/rose/card/link.js +++ /dev/null @@ -1,7 +0,0 @@ -import Component from '@ember/component'; -import layout from '../../../templates/components/rose/card/link'; - -export default Component.extend({ - layout, - tagName: '', -}); diff --git a/addons/rose/addon/templates/components/rose/card/link.hbs b/addons/rose/addon/components/rose/card/link/index.hbs similarity index 100% rename from addons/rose/addon/templates/components/rose/card/link.hbs rename to addons/rose/addon/components/rose/card/link/index.hbs diff --git a/addons/rose/addon/components/rose/card/link.stories.mdx b/addons/rose/addon/components/rose/card/link/index.stories.mdx similarity index 100% rename from addons/rose/addon/components/rose/card/link.stories.mdx rename to addons/rose/addon/components/rose/card/link/index.stories.mdx diff --git a/addons/rose/addon/components/rose/cards.js b/addons/rose/addon/components/rose/cards.js deleted file mode 100644 index c54fb638d..000000000 --- a/addons/rose/addon/components/rose/cards.js +++ /dev/null @@ -1,7 +0,0 @@ -import Component from '@ember/component'; -import layout from '../../templates/components/rose/cards'; - -export default Component.extend({ - layout, - tagName: '', -}); diff --git a/addons/rose/addon/templates/components/rose/cards.hbs b/addons/rose/addon/components/rose/cards/index.hbs similarity index 100% rename from addons/rose/addon/templates/components/rose/cards.hbs rename to addons/rose/addon/components/rose/cards/index.hbs diff --git a/addons/rose/addon/components/rose/cards.stories.mdx b/addons/rose/addon/components/rose/cards/index.stories.mdx similarity index 100% rename from addons/rose/addon/components/rose/cards.stories.mdx rename to addons/rose/addon/components/rose/cards/index.stories.mdx From 1f52f02727e58aa246ce92044d450c6f9cd483c6 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Mon, 21 Sep 2020 10:45:45 -0400 Subject: [PATCH 13/23] fix: Add type computed property check to account username --- addons/api/addon/models/account.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/api/addon/models/account.js b/addons/api/addon/models/account.js index de5209c34..71b337f44 100644 --- a/addons/api/addon/models/account.js +++ b/addons/api/addon/models/account.js @@ -16,7 +16,7 @@ export default class AccountModel extends GeneratedAccountModel { * Convenience for getting username in account. * @type {string} */ - @computed('attributes.{login_name}') + @computed('type', 'attributes.{login_name}') get username() { let username = ''; switch (this.type) { From 2a4616e426b0559f6224dcc80eba0561a79f5137 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Mon, 21 Sep 2020 11:18:44 -0400 Subject: [PATCH 14/23] test: Add set password error testcase --- ui/core/tests/acceptance/accounts-test.js | 58 ++++++++++++++++------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/ui/core/tests/acceptance/accounts-test.js b/ui/core/tests/acceptance/accounts-test.js index 9412f115a..a7e6a544c 100644 --- a/ui/core/tests/acceptance/accounts-test.js +++ b/ui/core/tests/acceptance/accounts-test.js @@ -246,23 +246,49 @@ module('Acceptance | accounts', function (hooks) { ); }); - test('can set a new password for account', async function (assert) { - assert.expect(1); - this.server.post('/accounts/:id', (_, { requestBody }) => { - const attrs = JSON.parse(requestBody); - assert.equal(attrs.password, 'update password', "attributes don't match the expected ones"); + module('password', function() { + test('can set a new password for account', async function (assert) { + assert.expect(1); + this.server.post('/accounts/:id', (_, { requestBody }) => { + const attrs = JSON.parse(requestBody); + assert.equal(attrs.password, 'update password', 'new password is set'); + }); + await visit(urls.setAccountPassword); + await fillIn('[name="password"]', 'update password'); + await click('form [type="submit"]:not(:disabled)'); + await a11yAudit(); }); - await visit(urls.setAccountPassword); - await fillIn('[name="password"]', 'update password'); - await click('form [type="submit"]:not(:disabled)'); - await a11yAudit(); - }); - test('can cancel setting new password', async function (assert) { - assert.expect(1); - await visit(urls.setAccountPassword); - await fillIn('[name="password"]', 'update password'); - await click('form button:not([type="submit"])'); - assert.notOk(find('[name="password"]').textContent.trim()); + test('can cancel setting new password', async function (assert) { + assert.expect(1); + await visit(urls.setAccountPassword); + await fillIn('[name="password"]', 'update password'); + await click('form button:not([type="submit"])'); + assert.notOk(find('[name="password"]').textContent.trim()); + }); + + test('errors are displayed when setting password fails', async function (assert) { + assert.expect(1); + this.server.post('/accounts/:id', () => { + return new Response( + 490, + {}, + { + status: 490, + code: 'error', + message: 'Oops.', + } + ); + }); + await visit(urls.setAccountPassword); + await fillIn('[name="password"]', 'update password'); + await click('form [type="submit"]'); + await a11yAudit(); + assert.ok( + find('[role="alert"]').textContent.trim(), + 'Oops.', + 'Displays primary error message.' + ); + }); }); }); From a94f02fdf92e0fae140bfefc4d3e21b9bf296bb0 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Mon, 21 Sep 2020 11:40:33 -0400 Subject: [PATCH 15/23] test: Add authmethods acceptance tests --- ui/core/tests/acceptance/auth-methods-test.js | 233 ++++++++++++++++-- 1 file changed, 216 insertions(+), 17 deletions(-) diff --git a/ui/core/tests/acceptance/auth-methods-test.js b/ui/core/tests/acceptance/auth-methods-test.js index d7f025c63..fa46de1ed 100644 --- a/ui/core/tests/acceptance/auth-methods-test.js +++ b/ui/core/tests/acceptance/auth-methods-test.js @@ -1,69 +1,268 @@ import { module, test } from 'qunit'; -//import { visit, currentURL } from '@ember/test-helpers'; +import { visit, currentURL, click, find, fillIn } 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 { -// authenticateSession, +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'; +} from 'ember-simple-auth/test-support'; module('Acceptance | auth methods', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + const instances = { + scopes: { + global: null, + org: null, + }, + authMethod: null, + }; + const urls = { + orgScope: null, + authMethods: null, + newAuthMethod: null, + authMethod: null, + }; + 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.authMethod = this.server.create('auth-method', { + scope: instances.scopes.org, + }); + // Generate route URLs for resources + urls.orgScope = `/scopes/${instances.scopes.org.id}`; + urls.authMethods = `${urls.orgScope}/auth-methods`; + urls.newAuthMethod = `${urls.authMethods}/new`; + urls.authMethod = `${urls.authMethods}/${instances.authMethod.id}`; }); test('visiting auth methods', async function (assert) { - assert.expect(0); + assert.expect(1); + await visit(urls.authMethods); + await a11yAudit(); + assert.equal(currentURL(), urls.authMethods); }); test('can navigate to an auth method form', async function (assert) { - assert.expect(0); + assert.expect(1); + await visit(urls.authMethods); + await click('main tbody .rose-table-header-cell:nth-child(1) a'); + await a11yAudit(); + assert.equal(currentURL(), urls.authMethod); }); test('can update an auth method and save changes', async function (assert) { - assert.expect(0); + assert.expect(1); + await visit(urls.authMethod); + await fillIn('[name="name"]', 'update name'); + await click('form [type="submit"]:not(:disabled)'); + assert.equal(this.server.db.authMethods[0].name, 'update name'); }); test('can update an auth method and cancel changes', async function (assert) { - assert.expect(0); + assert.expect(1); + await visit(urls.authMethod) + await fillIn('[name="name"]', 'update name'); + await click('form button:not([type="submit"])'); + assert.notEqual(this.server.db.authMethods[0].name, 'update name'); }); test('can create new auth method', async function (assert) { - assert.expect(0); + assert.expect(1); + const authMethodsCount = this.server.db.authMethods.length; + await visit(urls.newAuthMethod); + await fillIn('[name="name"]', 'AuthMethod name'); + await fillIn('[name="description"]', 'description'); + await click('form [type="submit"]:not(:disabled)'); + assert.equal(this.server.db.authMethods.length, authMethodsCount + 1); }); test('can cancel new auth method creation', async function (assert) { - assert.expect(0); + assert.expect(2); + const authMethodsCount = this.server.db.authMethods.length; + await visit(urls.newAuthMethod); + await fillIn('[name="name"]', 'AuthMethod name'); + await fillIn('[name="description"]', 'description'); + await click('form button:not([type="submit"])'); + assert.equal(this.server.db.authMethods.length, authMethodsCount); + assert.equal(currentURL(), urls.authMethods); }); test('can delete an auth method', async function (assert) { - assert.expect(0); + assert.expect(1); + const authMethodsCount = this.server.db.authMethods.length; + await visit(urls.authMethod); + await click('.rose-layout-page-actions .rose-dropdown-button-danger'); + assert.equal(this.server.db.authMethods.length, authMethodsCount - 1); }); test('saving a new auth method with invalid fields displays error messages', async function (assert) { - assert.expect(0); + assert.expect(2); + this.server.post('/auth-methods', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: { + request_fields: [ + { + name: 'name', + description: 'Name is required.', + }, + ], + }, + } + ); + }); + await visit(urls.newAuthMethod); + await fillIn('[name="name"]', 'new auth method'); + await click('form [type="submit"]'); + await a11yAudit(); + assert.ok( + find('[role="alert"]').textContent.trim(), + 'The request was invalid.', + 'Displays primary error message.' + ); + assert.ok( + find('.rose-form-error-message').textContent.trim(), + 'Name is required.', + 'Displays field-level errors.' + ); }); test('errors are displayed when save on auth method fails', async function (assert) { - assert.expect(0); + assert.expect(1); + this.server.patch('/auth-methods/:id', () => { + return new Response( + 490, + {}, + { + status: 490, + code: 'error', + message: 'Oops.', + } + ); + }); + await visit(urls.authMethod); + await fillIn('[name="name"]', 'save auth method'); + await click('form [type="submit"]'); + await a11yAudit(); + assert.ok( + find('[role="alert"]').textContent.trim(), + 'Oops.', + 'Displays primary error message.' + ); }); test('errors are displayed when delete on an auth method fails', async function (assert) { - assert.expect(0); + assert.expect(1); + this.server.del('/auth-methods/:id', () => { + return new Response( + 490, + {}, + { + status: 490, + code: 'error', + message: 'Oops.', + } + ); + }); + await visit(urls.authMethod); + await click('.rose-layout-page-actions .rose-dropdown-button-danger'); + await a11yAudit(); + assert.ok( + find('[role="alert"]').textContent.trim(), + 'Oops.', + 'Displays primary error message.' + ); }); test('saving a new auth method with invalid fields displays error messages', async function (assert) { - assert.expect(0); + assert.expect(2); + this.server.patch('/auth-methods/:id', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: { + request_fields: [ + { + name: 'name', + description: 'Name is required.', + }, + ], + }, + } + ); + }); + await visit(urls.authMethod); + await fillIn('[name="name"]', 'existing auth method'); + await click('form [type="submit"]'); + await a11yAudit(); + assert.ok( + find('[role="alert"]').textContent.trim(), + 'The request was invalid.', + 'Displays primary error message.' + ); + assert.ok( + find('.rose-form-error-message').textContent.trim(), + 'Name is required.', + 'Displays field-level errors.' + ); }); test('saving an existing auth method with invalid fields displays error messages', async function (assert) { - assert.expect(0); + assert.expect(2); + this.server.patch('/auth-methods/:id', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: { + request_fields: [ + { + name: 'name', + description: 'Name is required.', + }, + ], + }, + } + ); + }); + await visit(urls.authMethod); + await fillIn('[name="name"]', 'existing auth method'); + await click('form [type="submit"]'); + await a11yAudit(); + assert.ok( + find('[role="alert"]').textContent.trim(), + 'The request was invalid.', + 'Displays primary error message.' + ); + assert.ok( + find('.rose-form-error-message').textContent.trim(), + 'Name is required.', + 'Displays field-level errors.' + ); }); }); From 99aeb0007a1dc64fbe62484f539aa18a74d06591 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Mon, 21 Sep 2020 11:57:33 -0400 Subject: [PATCH 16/23] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20target=20hos?= =?UTF-8?q?t=20set=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/core/mirage/models/host-catalog.js | 5 ++- ui/core/tests/acceptance/targets-test.js | 51 +++++++++++++++++++----- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/ui/core/mirage/models/host-catalog.js b/ui/core/mirage/models/host-catalog.js index 08b1f5ad3..8c2757e71 100644 --- a/ui/core/mirage/models/host-catalog.js +++ b/ui/core/mirage/models/host-catalog.js @@ -1,7 +1,8 @@ -import { Model, belongsTo } from 'ember-cli-mirage'; +import { Model, belongsTo, hasMany } from 'ember-cli-mirage'; export default Model.extend({ - scope: belongsTo() + scope: belongsTo(), + hostSets: hasMany() }); diff --git a/ui/core/tests/acceptance/targets-test.js b/ui/core/tests/acceptance/targets-test.js index 1f3ca2e13..883fb8ae7 100644 --- a/ui/core/tests/acceptance/targets-test.js +++ b/ui/core/tests/acceptance/targets-test.js @@ -1,5 +1,5 @@ import { module, test } from 'qunit'; -import { visit, currentURL, click, find, fillIn } from '@ember/test-helpers'; +import { visit, currentURL, click, find, findAll, fillIn } 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'; @@ -47,11 +47,13 @@ module('Acceptance | targets', function (hooks) { type: 'project', scope: { id: instances.scopes.org.id, type: 'org' }, }); - instances.hostSets = this.server.createList('host-set', 4, { - scope: instances.scopes.project, - }); + + instances.hostCatalog = this.server.create('host-catalog', { + scope: instances.scopes.project + }, 'withChildren'); instances.target = this.server.create('target', { scope: instances.scopes.project, + hostSets: instances.hostCatalog.hostSets }); // Generate route URLs for resources @@ -258,22 +260,53 @@ module('Acceptance | targets', function (hooks) { }); test('visiting target host sets', async function (assert) { - assert.expect(1); + assert.expect(3); + const targetHostSetCount = instances.target.hostSets.length; await visit(urls.targetHostSets); await a11yAudit(); assert.equal(currentURL(), urls.targetHostSets); + assert.ok(targetHostSetCount); + assert.equal(findAll('tbody tr').length, targetHostSetCount); }); test('can delete a host sets', async function (assert) { - assert.expect(0); + assert.expect(2); + const targetHostSetCount = instances.target.hostSets.length; + await visit(urls.targetHostSets); + assert.equal(findAll('tbody tr').length, targetHostSetCount); + await click('tbody tr .rose-dropdown-button-danger'); + assert.equal(findAll('tbody tr').length, targetHostSetCount - 1); }); test('select and save host sets to add', async function (assert) { - assert.expect(0); - await visit(urls.targetAddHostSets); + assert.expect(4); + const targetHostSetCount = instances.target.hostSets.length; + await visit(urls.targetHostSets); + assert.equal(findAll('tbody tr').length, targetHostSetCount); + // first, remove a target host set (otherwise none would be available to add) + await click('tbody tr .rose-dropdown-button-danger'); + assert.equal(findAll('tbody tr').length, targetHostSetCount - 1); + await click('.rose-layout-page-actions a') + assert.equal(currentURL(), urls.targetAddHostSets); + await click('tbody label'); + await click('form [type="submit"]'); + await visit(urls.targetHostSets); + assert.equal(findAll('tbody tr').length, targetHostSetCount); }); test('select and cancel host sets to add', async function (assert) { - assert.expect(0); + assert.expect(4); + const targetHostSetCount = instances.target.hostSets.length; + await visit(urls.targetHostSets); + assert.equal(findAll('tbody tr').length, targetHostSetCount); + // first, remove a target host set (otherwise none would be available to add) + await click('tbody tr .rose-dropdown-button-danger'); + assert.equal(findAll('tbody tr').length, targetHostSetCount - 1); + await click('.rose-layout-page-actions a') + assert.equal(currentURL(), urls.targetAddHostSets); + await click('tbody label'); + await click('form [type="button"]'); + await visit(urls.targetHostSets); + assert.equal(findAll('tbody tr').length, targetHostSetCount - 1); }); }); From f728fb7087cffc3aa857949b11dc0184f5d28f02 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Mon, 21 Sep 2020 13:28:03 -0400 Subject: [PATCH 17/23] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20error=20test?= =?UTF-8?q?=20cases=20for=20target=20host=20sets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/core/tests/acceptance/targets-test.js | 88 ++++++++++++++---------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/ui/core/tests/acceptance/targets-test.js b/ui/core/tests/acceptance/targets-test.js index 883fb8ae7..822986f12 100644 --- a/ui/core/tests/acceptance/targets-test.js +++ b/ui/core/tests/acceptance/targets-test.js @@ -163,16 +163,8 @@ module('Acceptance | targets', function (hooks) { await fillIn('[name="name"]', 'new target'); await click('form [type="submit"]'); await a11yAudit(); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'The request was invalid.', - 'Displays primary error message.' - ); - assert.ok( - find('.rose-form-error-message').textContent.trim(), - 'Name is required.', - 'Displays field-level errors.' - ); + assert.ok(find('[role="alert"]')); + assert.ok(find('.rose-form-error-message')); }); test('errors are displayed when save on target fails', async function (assert) { @@ -192,11 +184,7 @@ module('Acceptance | targets', function (hooks) { await fillIn('[name="name"]', 'save target'); await click('form [type="submit"]'); await a11yAudit(); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'Oops.', - 'Displays primary error message.' - ); + assert.ok(find('[role="alert"]')); }); test('errors are displayed when delete on a target fails', async function (assert) { @@ -215,11 +203,7 @@ module('Acceptance | targets', function (hooks) { await visit(urls.target); await click('.rose-layout-page-actions .rose-dropdown-button-danger'); await a11yAudit(); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'Oops.', - 'Displays primary error message.' - ); + assert.ok(find('[role="alert"]')); }); test('saving an existing target with invalid fields displays error messages', async function (assert) { @@ -247,16 +231,8 @@ module('Acceptance | targets', function (hooks) { await fillIn('[name="name"]', 'existing target'); await click('form [type="submit"]'); await a11yAudit(); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'The request was invalid.', - 'Displays primary error message.' - ); - assert.ok( - find('.rose-form-error-message').textContent.trim(), - 'Name is required.', - 'Displays field-level errors.' - ); + assert.ok(find('[role="alert"]')); + assert.ok(find('.rose-form-error-message')); }); test('visiting target host sets', async function (assert) { @@ -278,20 +254,38 @@ module('Acceptance | targets', function (hooks) { assert.equal(findAll('tbody tr').length, targetHostSetCount - 1); }); - test('select and save host sets to add', async function (assert) { - assert.expect(4); + test('shows error message on host set remove', async function (assert) { + assert.expect(2); + this.server.post('/targets/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); const targetHostSetCount = instances.target.hostSets.length; await visit(urls.targetHostSets); assert.equal(findAll('tbody tr').length, targetHostSetCount); - // first, remove a target host set (otherwise none would be available to add) await click('tbody tr .rose-dropdown-button-danger'); - assert.equal(findAll('tbody tr').length, targetHostSetCount - 1); + assert.ok(find('[role="alert"]')); + }); + + test('select and save host sets to add', async function (assert) { + assert.expect(3); + instances.target.update({ hostSetIds: [] }); + await visit(urls.targetHostSets); + assert.equal(findAll('tbody tr').length, 0); await click('.rose-layout-page-actions a') assert.equal(currentURL(), urls.targetAddHostSets); await click('tbody label'); await click('form [type="submit"]'); await visit(urls.targetHostSets); - assert.equal(findAll('tbody tr').length, targetHostSetCount); + assert.equal(findAll('tbody tr').length, 1); }); test('select and cancel host sets to add', async function (assert) { @@ -309,4 +303,28 @@ module('Acceptance | targets', function (hooks) { await visit(urls.targetHostSets); assert.equal(findAll('tbody tr').length, targetHostSetCount - 1); }); + + test('shows error message on host set add', async function (assert) { + assert.expect(4); + this.server.post('/targets/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + instances.target.update({ hostSetIds: [] }); + await visit(urls.targetAddHostSets); + assert.equal(instances.target.hostSets.length, 0); + assert.equal(currentURL(), urls.targetAddHostSets); + await click('tbody label'); + await click('form [type="submit"]'); + assert.equal(currentURL(), urls.targetAddHostSets); + assert.equal(instances.target.hostSets.length, 0); + }); }); From fe2232b32033cc260fad90388ea2cf88ec9c7560 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Mon, 21 Sep 2020 14:40:16 -0400 Subject: [PATCH 18/23] fix: Do not serialize scope_id for nested resources Nested resources currently are accounts, host-sets, and hosts. --- addons/api/addon/models/account.js | 7 +++++++ addons/api/addon/models/host-set.js | 8 ++++++++ addons/api/addon/models/host.js | 8 ++++++++ addons/api/addon/serializers/application.js | 8 +++++--- .../tests/unit/serializers/application-test.js | 15 +++++++++++++++ 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/addons/api/addon/models/account.js b/addons/api/addon/models/account.js index 71b337f44..ff6b9d583 100644 --- a/addons/api/addon/models/account.js +++ b/addons/api/addon/models/account.js @@ -4,6 +4,13 @@ import { computed } from '@ember/object'; export default class AccountModel extends GeneratedAccountModel { + // =properties + + /** + * Account resource is nested within auth methods. + */ + nestedResource = true; + // =attributes /** diff --git a/addons/api/addon/models/host-set.js b/addons/api/addon/models/host-set.js index 61033dd79..6b4587ce6 100644 --- a/addons/api/addon/models/host-set.js +++ b/addons/api/addon/models/host-set.js @@ -2,6 +2,14 @@ import GeneratedHostSetModel from '../generated/models/host-set'; import { fragmentArray } from 'ember-data-model-fragments/attributes'; export default class HostSetModel extends GeneratedHostSetModel { + + // =properties + + /** + * Account resource is nested within auth methods. + */ + nestedResource = true; + // =attributes /** diff --git a/addons/api/addon/models/host.js b/addons/api/addon/models/host.js index 7748e023b..2b6957dc2 100644 --- a/addons/api/addon/models/host.js +++ b/addons/api/addon/models/host.js @@ -2,6 +2,14 @@ import GeneratedHostModel from '../generated/models/host'; import { fragment } from 'ember-data-model-fragments/attributes'; export default class HostModel extends GeneratedHostModel { + + // =properties + + /** + * Account resource is nested within auth methods. + */ + nestedResource = true; + /** * Attributes of this resource, if any, represented as a JSON fragment. * @type {FragmentHostAttributesModel} diff --git a/addons/api/addon/serializers/application.js b/addons/api/addon/serializers/application.js index a41c97730..cdbd2b56d 100644 --- a/addons/api/addon/serializers/application.js +++ b/addons/api/addon/serializers/application.js @@ -70,9 +70,11 @@ export default class ApplicationSerializer extends RESTSerializer { const serialized = super.serialize(...arguments); // Delete `scope` field delete serialized.scope; - // And serialize `scope_id` - const scope_id = snapshot?.attr('scope')?.attr('scope_id'); - if (scope_id) serialized.scope_id = scope_id; + // And serialize `scope_id` when not a nested resource + if(!snapshot?.record?.nestedResource) { + const scope_id = snapshot?.attr('scope')?.attr('scope_id'); + if (scope_id) serialized.scope_id = scope_id; + } return serialized; } diff --git a/addons/api/tests/unit/serializers/application-test.js b/addons/api/tests/unit/serializers/application-test.js index 3ab733e52..85702eac4 100644 --- a/addons/api/tests/unit/serializers/application-test.js +++ b/addons/api/tests/unit/serializers/application-test.js @@ -49,6 +49,21 @@ module('Unit | Serializer | application', function (hooks) { }); }); + test('it does not serialize scope_id when resource is nested', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const record = store.createRecord('account', { + name: 'Account', + description: 'Description', + scope: { + scope_id: 'global', + type: 'global', + }, + }); + const serializedRecord = record.serialize(); + assert.notOk(serializedRecord.scope_id); + }); + test('it serializes non-nullish version fields', function (assert) { assert.expect(2); const store = this.owner.lookup('service:store'); From aca96d6dd8912b3c635b09878bef7f805c9a0bf8 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Mon, 21 Sep 2020 15:03:23 -0400 Subject: [PATCH 19/23] test: Refactor 'nestedResource' to 'serializeScopeID' --- addons/api/addon/models/account.js | 4 ++-- addons/api/addon/models/base.js | 8 ++++++++ addons/api/addon/models/host-set.js | 4 ++-- addons/api/addon/models/host.js | 4 ++-- addons/api/addon/serializers/application.js | 4 ++-- .../tests/unit/serializers/application-test.js | 18 ++++++++++++------ 6 files changed, 28 insertions(+), 14 deletions(-) diff --git a/addons/api/addon/models/account.js b/addons/api/addon/models/account.js index ff6b9d583..77d631bca 100644 --- a/addons/api/addon/models/account.js +++ b/addons/api/addon/models/account.js @@ -7,9 +7,9 @@ export default class AccountModel extends GeneratedAccountModel { // =properties /** - * Account resource is nested within auth methods. + * @type {boolean} */ - nestedResource = true; + serializeScopeID = false; // =attributes diff --git a/addons/api/addon/models/base.js b/addons/api/addon/models/base.js index 2a01e39f5..f79834fe5 100644 --- a/addons/api/addon/models/base.js +++ b/addons/api/addon/models/base.js @@ -26,6 +26,14 @@ import { get } from '@ember/object'; * 2) Or as an ID: `record.scopeID`. */ export default class BaseModel extends Model { + + // =properties + + /** + * @type {boolean} + */ + serializeScopeID = true; + // =relationships /** diff --git a/addons/api/addon/models/host-set.js b/addons/api/addon/models/host-set.js index 6b4587ce6..fce605ebb 100644 --- a/addons/api/addon/models/host-set.js +++ b/addons/api/addon/models/host-set.js @@ -6,9 +6,9 @@ export default class HostSetModel extends GeneratedHostSetModel { // =properties /** - * Account resource is nested within auth methods. + * @type {boolean} */ - nestedResource = true; + serializeScopeID = false; // =attributes diff --git a/addons/api/addon/models/host.js b/addons/api/addon/models/host.js index 2b6957dc2..6790b482c 100644 --- a/addons/api/addon/models/host.js +++ b/addons/api/addon/models/host.js @@ -6,9 +6,9 @@ export default class HostModel extends GeneratedHostModel { // =properties /** - * Account resource is nested within auth methods. + * @type {boolean} */ - nestedResource = true; + serializeScopeID = false; /** * Attributes of this resource, if any, represented as a JSON fragment. diff --git a/addons/api/addon/serializers/application.js b/addons/api/addon/serializers/application.js index cdbd2b56d..5473a05bc 100644 --- a/addons/api/addon/serializers/application.js +++ b/addons/api/addon/serializers/application.js @@ -70,8 +70,8 @@ export default class ApplicationSerializer extends RESTSerializer { const serialized = super.serialize(...arguments); // Delete `scope` field delete serialized.scope; - // And serialize `scope_id` when not a nested resource - if(!snapshot?.record?.nestedResource) { + // And serialize `scope_id` + if(snapshot?.record?.serializeScopeID) { const scope_id = snapshot?.attr('scope')?.attr('scope_id'); if (scope_id) serialized.scope_id = scope_id; } diff --git a/addons/api/tests/unit/serializers/application-test.js b/addons/api/tests/unit/serializers/application-test.js index 85702eac4..1089e3b74 100644 --- a/addons/api/tests/unit/serializers/application-test.js +++ b/addons/api/tests/unit/serializers/application-test.js @@ -30,7 +30,7 @@ module('Unit | Serializer | application', function (hooks) { }); }); - test('it serializes scope_id when scope is set', function (assert) { + test('it serializes scope_id', function (assert) { assert.expect(1); const store = this.owner.lookup('service:store'); const record = store.createRecord('user', { @@ -49,19 +49,25 @@ module('Unit | Serializer | application', function (hooks) { }); }); - test('it does not serialize scope_id when resource is nested', function (assert) { + test('it does not serialize scope_id when serializeScopeID is false', function (assert) { assert.expect(1); const store = this.owner.lookup('service:store'); - const record = store.createRecord('account', { - name: 'Account', + const serializer = store.serializerFor('user'); + const record = store.createRecord('user', { + name: 'User', description: 'Description', scope: { scope_id: 'global', type: 'global', }, }); - const serializedRecord = record.serialize(); - assert.notOk(serializedRecord.scope_id); + const snapshot = record._createSnapshot(); + snapshot.record.serializeScopeID = false; + const serializedRecord = serializer.serialize(snapshot); + assert.deepEqual(serializedRecord, { + name: 'User', + description: 'Description' + }); }); test('it serializes non-nullish version fields', function (assert) { From 20cc45d57abfd4260fd85a679200dcb7c6db8f47 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Mon, 21 Sep 2020 15:19:14 -0400 Subject: [PATCH 20/23] fix: use inline "fill" attributes instead of embedded CSS for logo colors --- ui/core/public/logo-boundary.svg | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/ui/core/public/logo-boundary.svg b/ui/core/public/logo-boundary.svg index ea71154fb..21465bdef 100644 --- a/ui/core/public/logo-boundary.svg +++ b/ui/core/public/logo-boundary.svg @@ -2,56 +2,55 @@ height="30" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 310 67"> - Date: Mon, 21 Sep 2020 17:26:46 -0400 Subject: [PATCH 21/23] feat: Add doc context to all resources --- .../routes/scopes/scope/roles/role/grants.js | 16 +++++++++++ .../scopes/scope/roles/role/principals.js | 15 +++++++++++ .../auth-methods/auth-method/-header.hbs | 1 + .../scopes/scope/auth-methods/index.hbs | 1 + .../scopes/scope/groups/group/-header.hbs | 1 + .../templates/scopes/scope/groups/index.hbs | 1 + .../app/templates/scopes/scope/orgs/index.hbs | 1 + .../templates/scopes/scope/projects/index.hbs | 1 + .../host-catalogs/host-catalog/-header.hbs | 1 + .../host-catalog/host-sets/-header.hbs | 3 ++- .../host-catalog/hosts/-header.hbs | 3 ++- .../projects/project/host-catalogs/index.hbs | 2 +- .../scopes/scope/projects/project/index.hbs | 1 + .../scope/projects/project/targets/index.hbs | 1 + .../project/targets/target/-header.hbs | 1 + .../templates/scopes/scope/roles/index.hbs | 1 + .../scopes/scope/roles/role/-header.hbs | 1 + .../roles/role/add-principals/-header.hbs | 2 +- .../scope/roles/role/grants/-header.hbs | 2 ++ .../scope/roles/role/principals/-header.hbs | 2 ++ .../templates/scopes/scope/users/index.hbs | 1 + .../scopes/scope/users/user/index.hbs | 1 + ui/core/translations/en-us.yaml | 27 +++++++++++++++++++ 23 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 ui/core/app/templates/scopes/scope/roles/role/grants/-header.hbs create mode 100644 ui/core/app/templates/scopes/scope/roles/role/principals/-header.hbs 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 60368286b..d53bf4919 100644 --- a/ui/core/app/routes/scopes/scope/roles/role/grants.js +++ b/ui/core/app/routes/scopes/scope/roles/role/grants.js @@ -10,6 +10,22 @@ export default class ScopesScopeRolesRoleGrantsRoute extends Route { // =methods + /** + * Renders the grants-specific header template. + * Empties the actions and navigation outlets and renders a custom empty header. + * @override + */ + renderTemplate() { + super.renderTemplate(...arguments); + + this.render('scopes/scope/roles/role/grants/-header', { + into: 'scopes/scope/roles/role', + outlet: 'header', + }); + } + + // =actions + /** * Adds a new grant to the role at the beginning of the grants list. * Grant creation is not immediately permanent; users may rollback the change diff --git a/ui/core/app/routes/scopes/scope/roles/role/principals.js b/ui/core/app/routes/scopes/scope/roles/role/principals.js index ca364acfd..3d5e55055 100644 --- a/ui/core/app/routes/scopes/scope/roles/role/principals.js +++ b/ui/core/app/routes/scopes/scope/roles/role/principals.js @@ -39,6 +39,20 @@ export default class ScopesScopeRolesRolePrincipalsRoute extends Route { return all(users.concat(groups)); } + /** + * Renders the principals-specific header template. + * Empties the actions and navigation outlets and renders a custom empty header. + * @override + */ + renderTemplate() { + super.renderTemplate(...arguments); + + this.render('scopes/scope/roles/role/principals/-header', { + into: 'scopes/scope/roles/role', + outlet: 'header', + }); + } + // =actions /** @@ -57,4 +71,5 @@ export default class ScopesScopeRolesRolePrincipalsRoute extends Route { this.notify.error(error.message, { closeAfter: null }); } } + } diff --git a/ui/core/app/templates/scopes/scope/auth-methods/auth-method/-header.hbs b/ui/core/app/templates/scopes/scope/auth-methods/auth-method/-header.hbs index 8eab49f00..f83a85430 100644 --- a/ui/core/app/templates/scopes/scope/auth-methods/auth-method/-header.hbs +++ b/ui/core/app/templates/scopes/scope/auth-methods/auth-method/-header.hbs @@ -1 +1,2 @@

{{t "resources.auth-method"}}

+

{{t "resources.descriptions.auth-method"}}

diff --git a/ui/core/app/templates/scopes/scope/auth-methods/index.hbs b/ui/core/app/templates/scopes/scope/auth-methods/index.hbs index f06afc224..78e39a496 100644 --- a/ui/core/app/templates/scopes/scope/auth-methods/index.hbs +++ b/ui/core/app/templates/scopes/scope/auth-methods/index.hbs @@ -1,6 +1,7 @@

{{t "resources.auth-methods"}}

+

{{t "resources.descriptions.auth-methods"}}

diff --git a/ui/core/app/templates/scopes/scope/groups/group/-header.hbs b/ui/core/app/templates/scopes/scope/groups/group/-header.hbs index 6631940a3..1c7ff249d 100644 --- a/ui/core/app/templates/scopes/scope/groups/group/-header.hbs +++ b/ui/core/app/templates/scopes/scope/groups/group/-header.hbs @@ -1 +1,2 @@

{{t "resources.group"}}

+

{{t "resources.descriptions.group"}}

diff --git a/ui/core/app/templates/scopes/scope/groups/index.hbs b/ui/core/app/templates/scopes/scope/groups/index.hbs index 0a973d184..21fd64095 100644 --- a/ui/core/app/templates/scopes/scope/groups/index.hbs +++ b/ui/core/app/templates/scopes/scope/groups/index.hbs @@ -6,6 +6,7 @@

{{t "resources.groups"}}

+

{{t "resources.descriptions.groups"}}

diff --git a/ui/core/app/templates/scopes/scope/orgs/index.hbs b/ui/core/app/templates/scopes/scope/orgs/index.hbs index fac9c044e..10b4f04a9 100644 --- a/ui/core/app/templates/scopes/scope/orgs/index.hbs +++ b/ui/core/app/templates/scopes/scope/orgs/index.hbs @@ -6,6 +6,7 @@

{{t "resources.orgs"}}

+

{{t "resources.descriptions.orgs"}}

diff --git a/ui/core/app/templates/scopes/scope/projects/index.hbs b/ui/core/app/templates/scopes/scope/projects/index.hbs index f892904cd..9c5ae805a 100644 --- a/ui/core/app/templates/scopes/scope/projects/index.hbs +++ b/ui/core/app/templates/scopes/scope/projects/index.hbs @@ -6,6 +6,7 @@

{{t "resources.projects"}}

+

{{t "resources.descriptions.projects"}}

diff --git a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/-header.hbs b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/-header.hbs index 82af78ffe..655ecb273 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/-header.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/-header.hbs @@ -1 +1,2 @@

{{t "resources.host-catalog"}}

+

{{t "resources.descriptions.host-catalog"}}

\ No newline at end of file diff --git a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/-header.hbs b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/-header.hbs index ae86a8707..05fa412e3 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/-header.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/-header.hbs @@ -1 +1,2 @@ -

{{t "resources.host-set"}}

\ No newline at end of file +

{{t "resources.host-set"}}

+

{{t "resources.descriptions.host-set"}}

\ No newline at end of file diff --git a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/hosts/-header.hbs b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/hosts/-header.hbs index 3187158d1..d9eea7635 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/hosts/-header.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/hosts/-header.hbs @@ -1 +1,2 @@ -

{{t "resources.host"}}

\ No newline at end of file +

{{t "resources.host"}}

+

{{t "resources.descriptions.host"}}

\ No newline at end of file diff --git a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/index.hbs b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/index.hbs index 630275b18..1a4717479 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/index.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/index.hbs @@ -6,7 +6,7 @@

{{t "resources.host-catalogs"}}

-

{{t "descriptions.host-catalogs"}}

+

{{t "resources.descriptions.host-catalogs"}}

diff --git a/ui/core/app/templates/scopes/scope/projects/project/index.hbs b/ui/core/app/templates/scopes/scope/projects/project/index.hbs index c050b9570..243ef1276 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/index.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/index.hbs @@ -6,6 +6,7 @@

{{t "resources.project"}}

+

{{t "resources.descriptions.project"}}

diff --git a/ui/core/app/templates/scopes/scope/projects/project/targets/index.hbs b/ui/core/app/templates/scopes/scope/projects/project/targets/index.hbs index cb6254a49..af99edaec 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/targets/index.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/targets/index.hbs @@ -6,6 +6,7 @@

{{t "resources.targets"}}

+

{{t "resources.descriptions.targets"}}

diff --git a/ui/core/app/templates/scopes/scope/projects/project/targets/target/-header.hbs b/ui/core/app/templates/scopes/scope/projects/project/targets/target/-header.hbs index 721607f47..0dc39d88d 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/targets/target/-header.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/targets/target/-header.hbs @@ -1 +1,2 @@

{{t "resources.target"}}

+

{{t "resources.descriptions.target"}}

diff --git a/ui/core/app/templates/scopes/scope/roles/index.hbs b/ui/core/app/templates/scopes/scope/roles/index.hbs index 835881d77..dcb462b4d 100644 --- a/ui/core/app/templates/scopes/scope/roles/index.hbs +++ b/ui/core/app/templates/scopes/scope/roles/index.hbs @@ -6,6 +6,7 @@

{{t "resources.roles"}}

+

{{t "resources.descriptions.roles"}}

diff --git a/ui/core/app/templates/scopes/scope/roles/role/-header.hbs b/ui/core/app/templates/scopes/scope/roles/role/-header.hbs index d7985c901..96ed6300a 100644 --- a/ui/core/app/templates/scopes/scope/roles/role/-header.hbs +++ b/ui/core/app/templates/scopes/scope/roles/role/-header.hbs @@ -1 +1,2 @@

{{t "resources.role"}}

+

{{t "resources.descriptions.role"}}

diff --git a/ui/core/app/templates/scopes/scope/roles/role/add-principals/-header.hbs b/ui/core/app/templates/scopes/scope/roles/role/add-principals/-header.hbs index 8696fed6d..5ce0ab603 100644 --- a/ui/core/app/templates/scopes/scope/roles/role/add-principals/-header.hbs +++ b/ui/core/app/templates/scopes/scope/roles/role/add-principals/-header.hbs @@ -1 +1 @@ -

{{t "actions.add-principals"}}

+

{{t "actions.add-principals"}}

\ No newline at end of file diff --git a/ui/core/app/templates/scopes/scope/roles/role/grants/-header.hbs b/ui/core/app/templates/scopes/scope/roles/role/grants/-header.hbs new file mode 100644 index 000000000..6c9ebd67b --- /dev/null +++ b/ui/core/app/templates/scopes/scope/roles/role/grants/-header.hbs @@ -0,0 +1,2 @@ +

{{t "resources.role"}}

+

{{t "resources.descriptions.grants"}}

diff --git a/ui/core/app/templates/scopes/scope/roles/role/principals/-header.hbs b/ui/core/app/templates/scopes/scope/roles/role/principals/-header.hbs new file mode 100644 index 000000000..a549d9a8b --- /dev/null +++ b/ui/core/app/templates/scopes/scope/roles/role/principals/-header.hbs @@ -0,0 +1,2 @@ +

{{t "resources.role"}}

+

{{t "resources.descriptions.principals"}}

diff --git a/ui/core/app/templates/scopes/scope/users/index.hbs b/ui/core/app/templates/scopes/scope/users/index.hbs index ad58e6ca8..506abc01f 100644 --- a/ui/core/app/templates/scopes/scope/users/index.hbs +++ b/ui/core/app/templates/scopes/scope/users/index.hbs @@ -6,6 +6,7 @@

{{t "resources.users"}}

+

{{t "resources.descriptions.users"}}

diff --git a/ui/core/app/templates/scopes/scope/users/user/index.hbs b/ui/core/app/templates/scopes/scope/users/user/index.hbs index 1f8c67630..a57e6f8eb 100644 --- a/ui/core/app/templates/scopes/scope/users/user/index.hbs +++ b/ui/core/app/templates/scopes/scope/users/user/index.hbs @@ -6,6 +6,7 @@

{{t "resources.user"}}

+

{{t "resources.descriptions.user"}}

diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml index 029474e65..4c044bb2f 100644 --- a/ui/core/translations/en-us.yaml +++ b/ui/core/translations/en-us.yaml @@ -79,6 +79,33 @@ resources: account: Account sessions: Sessions session: Session + descriptions: + user: A User inherits from Principal ("is-a" Principal) allowing it to be associated with zero or more Roles. + users: A user is any entity authorized to access Boundary using authentication credentials specific to one of the configured Authentication Methods. A User is owned by one and only one Organization. A User is associated with zero or one Authentication Methods. A User not associated with an Authentication Method can not log in to the system. A User can belong to zero or more Groups. A User is deleted when the Organization it belongs to is deleted. The lifecycle of a User is not tied to the lifecycle of any Authentication Method, Group or Role, but rather to its Organization only. + role: A Role can be assigned to zero or more Principals. A Principal assigned a Role receives all capabilities granted by any Direct Grant owned by the Role. A Role is deleted when the Scope it belongs to is deleted. All Direct Grants owned by a Role are deleted when the Role is deleted. + roles: A role is a collection of capabilities granted to any Principal the Role is assigned to. A Role belongs to one and only one Scope. A Role owns zero or more Direct Grants. The lifecycle of a Role is not tied to the lifecycle of any Principal. + grants: A grant represents a set of capabilities granted to a role. It couples a set of Actions to either a set of Resource Types or an individual Resource. A Direct Grant belongs exclusively to one and only one Role. However, equivalent Direct Grants may exist across different Roles. A Direct Grant references one or more Actions and either one or more Resource types or one specific Resource. A Direct Grant is deleted when the Role it belongs to is deleted. A Direct Grant is also deleted if it is associated with a specific Resource and that Resource itself is deleted. The lifecycle of a Direct Grant is not tied to the lifecycle of any Action or Resource Type. + principals: A principal is any entity which can be assigned capabilities. Principal is abstract. User, Group, and Project are concrete Principals. A Principal can be assigned to zero or more Roles. + group: All Users in a Group are granted the capabilities of all Roles the Group is associated with. A Group is deleted when the Scope it belongs to is deleted. + groups: Groups are collections of Users used only for access control purposes. A Group is owned by one and only one Scope. A Group can contain zero or more Users. A Group inherits from Principal ("is-a" Principal) allowing it to be associated with zero or more Roles. The lifecycle of a Group is not tied to the lifecycle of any User or Role. + org: When an Organization is deleted, all resources owned by it are also deleted. + orgs: An Organization is a top-level container, and owns zero to many Projects and zero to many Authentication Methods. An Organization inherits from Scope ("is-a" Scope) allowing it to own zero to many Groups, Roles, Targets, or Host Catalogs. The lifecycle of an Organization is not tied to anything else. A Role at the Organization level can grant permissions at the Project level. + project: When a Project is deleted, all resources owned by it are also deleted. The lifecycle of a Project is not tied to any resource it contains. + projects: A project is a child scope of an organization and is owned by an Organization. A Project inherits from Scope ("is-a" Scope) allowing it to own zero to many Groups, Roles, Targets, or Host Catalogs. A Project also inherits from Principal ("is-a" Principal) allowing it to be associated with zero or more Roles. A Project has no additional relationships beyond those defined in Scope and Principal. A Project is deleted when the Organization it belongs to is deleted. + host-catalog: A host catalog is a permission boundary modeled as a container. + host-catalogs: A host catalog is a permission boundary modeled as a container. + auth-method: An Authentication Method creates and references zero or more Users. An Authentication Method is deleted when the Organization it belongs to is deleted. The lifecycle of an Authentication Method is not tied to the lifecycle of any User. + auth-methods: An authentication method is a method configured for an Organization to authenticate Users. An Authentication Method is owned by one and only one Organization. + host-set: Host Set is abstract. A Host Set is deleted when the Host Catalog it belongs to is deleted. + host-sets: A host set is a resource which provides a subset of Hosts from the set of Hosts of the Host Catalog it belongs to. A Host Set belongs to one and only one Host Catalog. A Host Set can contain zero or more Hosts. A Host Set can be contained by zero or more Targets. The lifecycle of a Host Set is not tied to the lifecycle of any Targets or Hosts. + host: Host is abstract. A Host is deleted when the Host Catalog it belongs to is deleted. + hosts: A host is a computing element with a network address reachable from Boundary. A Host belongs to one and only one Host Catalog. A Host can be contained by zero or more Host Sets. The lifecycle of a Host is not tied to the lifecycle of any Host Sets. + target: Target is abstract. A Target is deleted when the Scope it belongs to is deleted. + targets: A target is a networked service a User can connect to and interact with through Boundary. A Target does not directly contain any secrets. A Target contains a collection of Hosts. A Target belongs to one and only one Scope. A Target can contain zero or more Host Sets. The lifecycle of a Target is not tied to the lifecycle of any Host Set. + accounts: Accounts + account: Account + sessions: Sessions + session: Session titles: iam: IAM new: New From 0a1eafe55e96c3360e2439dcb30c01ac6b8990c3 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Mon, 21 Sep 2020 18:23:32 -0400 Subject: [PATCH 22/23] refactor: Refactor seralizeScopeID from models to serializers --- addons/api/addon/models/account.js | 7 ------ addons/api/addon/models/base.js | 7 ------ addons/api/addon/models/host-set.js | 7 ------ addons/api/addon/models/host.js | 8 +------ addons/api/addon/serializers/account.js | 8 +++++++ addons/api/addon/serializers/application.js | 10 +++++++- addons/api/addon/serializers/host-set.js | 8 +++++++ addons/api/addon/serializers/host.js | 12 ++++++++++ addons/api/app/serializers/host.js | 1 + .../unit/serializers/application-test.js | 2 +- .../api/tests/unit/serializers/host-test.js | 23 +++++++++++++++++++ 11 files changed, 63 insertions(+), 30 deletions(-) create mode 100644 addons/api/addon/serializers/host.js create mode 100644 addons/api/app/serializers/host.js create mode 100644 addons/api/tests/unit/serializers/host-test.js diff --git a/addons/api/addon/models/account.js b/addons/api/addon/models/account.js index 77d631bca..71b337f44 100644 --- a/addons/api/addon/models/account.js +++ b/addons/api/addon/models/account.js @@ -4,13 +4,6 @@ import { computed } from '@ember/object'; export default class AccountModel extends GeneratedAccountModel { - // =properties - - /** - * @type {boolean} - */ - serializeScopeID = false; - // =attributes /** diff --git a/addons/api/addon/models/base.js b/addons/api/addon/models/base.js index f79834fe5..d883c8846 100644 --- a/addons/api/addon/models/base.js +++ b/addons/api/addon/models/base.js @@ -27,13 +27,6 @@ import { get } from '@ember/object'; */ export default class BaseModel extends Model { - // =properties - - /** - * @type {boolean} - */ - serializeScopeID = true; - // =relationships /** diff --git a/addons/api/addon/models/host-set.js b/addons/api/addon/models/host-set.js index fce605ebb..583971735 100644 --- a/addons/api/addon/models/host-set.js +++ b/addons/api/addon/models/host-set.js @@ -3,13 +3,6 @@ import { fragmentArray } from 'ember-data-model-fragments/attributes'; export default class HostSetModel extends GeneratedHostSetModel { - // =properties - - /** - * @type {boolean} - */ - serializeScopeID = false; - // =attributes /** diff --git a/addons/api/addon/models/host.js b/addons/api/addon/models/host.js index 6790b482c..a2e855084 100644 --- a/addons/api/addon/models/host.js +++ b/addons/api/addon/models/host.js @@ -3,16 +3,10 @@ import { fragment } from 'ember-data-model-fragments/attributes'; export default class HostModel extends GeneratedHostModel { - // =properties - - /** - * @type {boolean} - */ - serializeScopeID = false; - /** * Attributes of this resource, if any, represented as a JSON fragment. * @type {FragmentHostAttributesModel} */ @fragment('fragment-host-attributes', { defaultValue: {} }) attributes; + } diff --git a/addons/api/addon/serializers/account.js b/addons/api/addon/serializers/account.js index 01618a081..5f5291dcd 100644 --- a/addons/api/addon/serializers/account.js +++ b/addons/api/addon/serializers/account.js @@ -1,6 +1,14 @@ import ApplicationSerializer from './application'; export default class AccountSerializer extends ApplicationSerializer { + + // =properties + + /** + * @type {boolean} + */ + serializeScopeID = false; + // =methods /** diff --git a/addons/api/addon/serializers/application.js b/addons/api/addon/serializers/application.js index 5473a05bc..2819231fa 100644 --- a/addons/api/addon/serializers/application.js +++ b/addons/api/addon/serializers/application.js @@ -7,6 +7,14 @@ import { copy } from 'ember-copy'; * Manages serialization/normalization of data to/from the API. */ export default class ApplicationSerializer extends RESTSerializer { + + // =properties + + /** + * @type {boolean} + */ + serializeScopeID = true; + // =methods /** @@ -71,7 +79,7 @@ export default class ApplicationSerializer extends RESTSerializer { // Delete `scope` field delete serialized.scope; // And serialize `scope_id` - if(snapshot?.record?.serializeScopeID) { + if(this.serializeScopeID) { const scope_id = snapshot?.attr('scope')?.attr('scope_id'); if (scope_id) serialized.scope_id = scope_id; } diff --git a/addons/api/addon/serializers/host-set.js b/addons/api/addon/serializers/host-set.js index 591da7c76..ee38bd5eb 100644 --- a/addons/api/addon/serializers/host-set.js +++ b/addons/api/addon/serializers/host-set.js @@ -1,6 +1,14 @@ import ApplicationSerializer from './application'; export default class HostSetSerializer extends ApplicationSerializer { + + // =properties + + /** + * @type {boolean} + */ + serializeScopeID = false; + // =methods /** diff --git a/addons/api/addon/serializers/host.js b/addons/api/addon/serializers/host.js new file mode 100644 index 000000000..fad6f2ca7 --- /dev/null +++ b/addons/api/addon/serializers/host.js @@ -0,0 +1,12 @@ +import ApplicationSerializer from './application'; + +export default class HostSerializer extends ApplicationSerializer { + + // =properties + + /** + * @type {boolean} + */ + serializeScopeID = false; + +} diff --git a/addons/api/app/serializers/host.js b/addons/api/app/serializers/host.js new file mode 100644 index 000000000..33844fbaf --- /dev/null +++ b/addons/api/app/serializers/host.js @@ -0,0 +1 @@ +export { default } from 'api/serializers/host'; diff --git a/addons/api/tests/unit/serializers/application-test.js b/addons/api/tests/unit/serializers/application-test.js index 1089e3b74..5c2c07a20 100644 --- a/addons/api/tests/unit/serializers/application-test.js +++ b/addons/api/tests/unit/serializers/application-test.js @@ -62,7 +62,7 @@ module('Unit | Serializer | application', function (hooks) { }, }); const snapshot = record._createSnapshot(); - snapshot.record.serializeScopeID = false; + serializer.serializeScopeID = false; const serializedRecord = serializer.serialize(snapshot); assert.deepEqual(serializedRecord, { name: 'User', diff --git a/addons/api/tests/unit/serializers/host-test.js b/addons/api/tests/unit/serializers/host-test.js new file mode 100644 index 000000000..c31a5ce4e --- /dev/null +++ b/addons/api/tests/unit/serializers/host-test.js @@ -0,0 +1,23 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Serializer | host', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let store = this.owner.lookup('service:store'); + let serializer = store.serializerFor('host'); + + assert.ok(serializer); + }); + + test('it serializes records', function(assert) { + let store = this.owner.lookup('service:store'); + let record = store.createRecord('host', {}); + + let serializedRecord = record.serialize(); + + assert.ok(serializedRecord); + }); +}); From a6ffdc2cadab1fc9b16b5be91c15ddf9d9924298 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Tue, 22 Sep 2020 12:08:20 -0400 Subject: [PATCH 23/23] chore: remove logo --- ui/core/app/templates/application.hbs | 2 +- ui/core/public/logo-boundary.svg | 68 --------------------------- 2 files changed, 1 insertion(+), 69 deletions(-) delete mode 100644 ui/core/public/logo-boundary.svg diff --git a/ui/core/app/templates/application.hbs b/ui/core/app/templates/application.hbs index 06a6a4291..2f295e9b4 100644 --- a/ui/core/app/templates/application.hbs +++ b/ui/core/app/templates/application.hbs @@ -5,7 +5,7 @@ - + {{#if this.session.isAuthenticated}} diff --git a/ui/core/public/logo-boundary.svg b/ui/core/public/logo-boundary.svg deleted file mode 100644 index 21465bdef..000000000 --- a/ui/core/public/logo-boundary.svg +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -