diff --git a/addons/api/addon/adapters/user.js b/addons/api/addon/adapters/user.js new file mode 100644 index 000000000..0d8242019 --- /dev/null +++ b/addons/api/addon/adapters/user.js @@ -0,0 +1,13 @@ +import ApplicationAdapter from './application'; + +export default class UserAdapter extends ApplicationAdapter { + + // =attributes + + /** + * Users use the new, unprefixed pathing style. + * @override + */ + hasScopePrefix = false; + +} diff --git a/addons/api/addon/serializers/user.js b/addons/api/addon/serializers/user.js new file mode 100644 index 000000000..7e9e54c21 --- /dev/null +++ b/addons/api/addon/serializers/user.js @@ -0,0 +1,13 @@ +import ApplicationSerializer from './application'; + +export default class UserSerializer extends ApplicationSerializer { + + // =attributes + + /** + * Serialized the scope ID into the `scope_id` field. + * @type {boolean} + */ + serializeScopeID = true; + +} diff --git a/addons/api/app/adapters/user.js b/addons/api/app/adapters/user.js new file mode 100644 index 000000000..78f340b99 --- /dev/null +++ b/addons/api/app/adapters/user.js @@ -0,0 +1 @@ +export { default } from 'api/adapters/user'; diff --git a/addons/api/app/serializers/user.js b/addons/api/app/serializers/user.js new file mode 100644 index 000000000..5c5a92643 --- /dev/null +++ b/addons/api/app/serializers/user.js @@ -0,0 +1 @@ +export { default } from 'api/serializers/user'; diff --git a/addons/api/tests/unit/adapters/user-test.js b/addons/api/tests/unit/adapters/user-test.js new file mode 100644 index 000000000..0332ed354 --- /dev/null +++ b/addons/api/tests/unit/adapters/user-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Adapter | user', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let adapter = this.owner.lookup('adapter:user'); + assert.ok(adapter); + }); +}); diff --git a/addons/api/tests/unit/models/base-test.js b/addons/api/tests/unit/models/base-test.js index 40be071fe..cc89967a0 100644 --- a/addons/api/tests/unit/models/base-test.js +++ b/addons/api/tests/unit/models/base-test.js @@ -97,17 +97,17 @@ module('Unit | Model | base', function (hooks) { assert.expect(3); const store = this.owner.lookup('service:store'); const model = store.createRecord('user', { scopeID: 'o_123' }); - this.server.post('/v1/scopes/o_123/users', () => { + this.server.post('/v1/users', () => { assert.ok(true, 'Correctly scoped create record URL was requested.') return {}; }); await model.save(); - this.server.patch('/v1/scopes/o_123/users', () => { + this.server.patch('/v1/users', () => { assert.ok(true, 'Correctly scoped update record URL was requested.') return {}; }); await model.save(); - this.server.delete('/v1/scopes/o_123/users', () => { + this.server.delete('/v1/users', () => { assert.ok(true, 'Correctly scoped delete record URL was requested.') return {}; }); @@ -120,17 +120,17 @@ module('Unit | Model | base', function (hooks) { const customScopeID = 'global'; const scope = {scope_id: 'o_123'}; const model = store.createRecord('user', { scope }); - this.server.post('/v1/scopes/global/users', () => { + this.server.post('/v1/users', () => { assert.ok(true, 'Correctly scoped create record URL was requested.') return {}; }); await model.save({adapterOptions: {scopeID: customScopeID}}); - this.server.patch('/v1/scopes/global/users', () => { + this.server.patch('/v1/users', () => { assert.ok(true, 'Correctly scoped update record URL was requested.') return {}; }); await model.save({adapterOptions: {scopeID: customScopeID}}); - this.server.delete('/v1/scopes/global/users', () => { + this.server.delete('/v1/users', () => { assert.ok(true, 'Correctly scoped delete record URL was requested.') return {}; }); @@ -143,7 +143,7 @@ module('Unit | Model | base', function (hooks) { const method = 'my-custom-method'; const scope = {scope_id: 'o_123'}; const model = store.createRecord('user', { scope }); - this.server.post(`/v1/scopes/o_123/users:${method}`, () => { + this.server.post(`/v1/users:${method}`, () => { assert.ok(true, 'Correctly scoped create record URL was requested.') return {}; }); diff --git a/addons/api/tests/unit/serializers/application-test.js b/addons/api/tests/unit/serializers/application-test.js index e838a8da0..07385b071 100644 --- a/addons/api/tests/unit/serializers/application-test.js +++ b/addons/api/tests/unit/serializers/application-test.js @@ -25,7 +25,8 @@ module('Unit | Serializer | application', function (hooks) { const serializedRecord = record.serialize(); assert.deepEqual(serializedRecord, { name: 'User', - description: 'Description' + description: 'Description', + scope_id: 'global' }); }); @@ -33,6 +34,7 @@ module('Unit | Serializer | application', function (hooks) { assert.expect(2); const store = this.owner.lookup('service:store'); const serializer = store.serializerFor('user'); + serializer.serializeScopeID = false; const record = store.createRecord('user', { name: 'User', description: 'Description', diff --git a/addons/api/tests/unit/serializers/user-test.js b/addons/api/tests/unit/serializers/user-test.js new file mode 100644 index 000000000..b96a53b13 --- /dev/null +++ b/addons/api/tests/unit/serializers/user-test.js @@ -0,0 +1,23 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Serializer | user', 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('user'); + + assert.ok(serializer); + }); + + test('it serializes records', function(assert) { + let store = this.owner.lookup('service:store'); + let record = store.createRecord('user', {}); + + let serializedRecord = record.serialize(); + + assert.ok(serializedRecord); + }); +}); diff --git a/ui/core/app/routes/scopes/scope/groups/group/add-members.js b/ui/core/app/routes/scopes/scope/groups/group/add-members.js index f3b8717a9..303a73662 100644 --- a/ui/core/app/routes/scopes/scope/groups/group/add-members.js +++ b/ui/core/app/routes/scopes/scope/groups/group/add-members.js @@ -24,10 +24,11 @@ export default class ScopesScopeGroupsGroupAddMembersRoute extends Route { * @return {Promise{GroupModel, [UserModel]}} */ model() { - const { id: scopeID } = this.modelFor('scopes.scope'); + const group = this.modelFor('scopes.scope.groups.group'); + const { scopeID: scope_id } = group; return hash({ group: this.modelFor('scopes.scope.groups.group'), - users: this.store.findAll('user', { adapterOptions: { scopeID } }) + users: this.store.query('user', { scope_id }) }); } diff --git a/ui/core/app/routes/scopes/scope/roles/role/add-principals.js b/ui/core/app/routes/scopes/scope/roles/role/add-principals.js index 30f40366c..afa955a90 100644 --- a/ui/core/app/routes/scopes/scope/roles/role/add-principals.js +++ b/ui/core/app/routes/scopes/scope/roles/role/add-principals.js @@ -12,27 +12,17 @@ export default class ScopesScopeRolesRoleAddPrincipalsRoute extends Route { // =methods - /** - * Empty out any previously loaded users and groups. - * (collectively, principals). - */ - beforeModel() { - this.store.unloadAll('user'); - //this.store.unloadAll('group'); - } - /** * Returns the current role, all users, and all groups * @return {{role: RoleModel, users: [UserModel], groups: [GroupModel]}} */ model() { const role = this.modelFor('scopes.scope.roles.role'); - const { scopeID } = role; - const options = { adapterOptions: { scopeID } }; + const { scopeID: scope_id } = role; return hash({ role, - users: this.store.findAll('user', options), - groups: this.store.query('group', { scope_id: scopeID }) + users: this.store.query('user', { scope_id }), + groups: this.store.query('group', { scope_id }) }); } diff --git a/ui/core/app/routes/scopes/scope/users.js b/ui/core/app/routes/scopes/scope/users.js index 20731c90f..3efffbde1 100644 --- a/ui/core/app/routes/scopes/scope/users.js +++ b/ui/core/app/routes/scopes/scope/users.js @@ -23,8 +23,8 @@ export default class ScopesScopeUsersRoute extends Route { * @return {Promise{[UserModel]}} */ async model() { - const { id: scopeID } = this.modelFor('scopes.scope'); - return this.store.findAll('user', { adapterOptions: { scopeID } }); + const { id: scope_id } = this.modelFor('scopes.scope'); + return this.store.query('user', { scope_id }); } // =actions diff --git a/ui/core/app/routes/scopes/scope/users/user.js b/ui/core/app/routes/scopes/scope/users/user.js index ffbf19e7e..d26912707 100644 --- a/ui/core/app/routes/scopes/scope/users/user.js +++ b/ui/core/app/routes/scopes/scope/users/user.js @@ -10,9 +10,6 @@ export default class ScopesScopeUsersUserRoute extends Route { * @return {UserModel} */ async model({ user_id }) { - const { id: scopeID } = this.modelFor('scopes.scope'); - return this.store.findRecord('user', user_id, { - adapterOptions: { scopeID }, - }); + return this.store.findRecord('user', user_id); } } diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js index 0446758fb..f3d185893 100644 --- a/ui/core/mirage/config.js +++ b/ui/core/mirage/config.js @@ -54,11 +54,47 @@ export default function() { this.post('/scopes/:id_method', deauthHandler); // IAM : Users - this.get('/scopes/:scope_id/users'); - this.post('/scopes/:scope_id/users'); - this.get('/scopes/:scope_id/users/:id'); - this.patch('/scopes/:scope_id/users/:id'); - this.del('/scopes/:scope_id/users/:id'); + this.get('/users', ({ users }, { queryParams: { scope_id: scopeId } }) => { + return users.where({ scopeId }); + }); + this.post('/users'); + this.get('/users/:id'); + this.patch('/users/:id'); + this.del('/users/:id'); + + // IAM: Groups + this.get('/groups', ({ groups }, { queryParams: { scope_id: scopeId } }) => { + return groups.where({ scopeId }); + }); + this.post('/groups'); + this.get('/groups/:id'); + this.patch('/groups/:id'); + this.del('/groups/:id'); + this.post('/groups/:idMethod', function ({ groups }, { params: { idMethod } }) { + const attrs = this.normalizedRequestAttrs(); + const id = idMethod.split(':')[0]; + const method = idMethod.split(':')[1]; + const group = groups.find(id); + const updatedAttrs = { + version: attrs.version, + memberIds: group.memberIds + }; + // If adding members, push them into the array + if (method === 'add-members') { + attrs.memberIds.forEach(id => { + if (!updatedAttrs.memberIds.includes(id)) { + updatedAttrs.memberIds.push(id); + } + }); + } + // If deleting members, filter them out of the array + if (method === 'remove-members') { + updatedAttrs.memberIds = updatedAttrs.memberIds.filter(id => { + return !attrs.memberIds.includes(id); + }); + } + return group.update(updatedAttrs); + }); // IAM: Roles this.get('/scopes/:scope_id/roles'); @@ -106,40 +142,6 @@ export default function() { return role.update(updatedAttrs); }); - // IAM: Groups - this.get('/groups', ({ groups }, { queryParams: { scope_id: scopeId } }) => { - return groups.where({ scopeId }); - }); - this.post('/groups'); - this.get('/groups/:id'); - this.patch('/groups/:id'); - this.del('/groups/:id'); - this.post('/groups/:idMethod', function ({ groups }, { params: { idMethod } }) { - const attrs = this.normalizedRequestAttrs(); - const id = idMethod.split(':')[0]; - const method = idMethod.split(':')[1]; - const group = groups.find(id); - const updatedAttrs = { - version: attrs.version, - memberIds: group.memberIds - }; - // If adding members, push them into the array - if (method === 'add-members') { - attrs.memberIds.forEach(id => { - if (!updatedAttrs.memberIds.includes(id)) { - updatedAttrs.memberIds.push(id); - } - }); - } - // If deleting members, filter them out of the array - if (method === 'remove-members') { - updatedAttrs.memberIds = updatedAttrs.memberIds.filter(id => { - return !attrs.memberIds.includes(id); - }); - } - return group.update(updatedAttrs); - }); - // Other resources // host-catalog diff --git a/ui/core/mirage/models/user.js b/ui/core/mirage/models/user.js new file mode 100644 index 000000000..08b1f5ad3 --- /dev/null +++ b/ui/core/mirage/models/user.js @@ -0,0 +1,7 @@ +import { Model, belongsTo } from 'ember-cli-mirage'; + +export default Model.extend({ + + scope: belongsTo() + +}); diff --git a/ui/core/mirage/serializers/user.js b/ui/core/mirage/serializers/user.js index 142cbf508..499b249eb 100644 --- a/ui/core/mirage/serializers/user.js +++ b/ui/core/mirage/serializers/user.js @@ -1,5 +1,22 @@ import ApplicationSerializer from './application'; +import { underscore } from '@ember/string'; export default ApplicationSerializer.extend({ - modelName: 'user' + modelName: 'user', + + keyForRelationshipId(relationshipName) { + return `${this._container.inflector.singularize( + underscore(relationshipName) + )}_id`; + }, + + _hashForModel(model) { + const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments); + json.scope = { + id: model.scope.id, + type: model.scope.type + }; + return json; + } + }); diff --git a/ui/core/tests/acceptance/bread-crumbs-test.js b/ui/core/tests/acceptance/bread-crumbs-test.js index aafeecb5a..32382e582 100644 --- a/ui/core/tests/acceptance/bread-crumbs-test.js +++ b/ui/core/tests/acceptance/bread-crumbs-test.js @@ -42,7 +42,7 @@ module('Acceptance | breadcrumbs', function (hooks) { this.server.schema.scopes.where({ type: 'project' }).models[0]; project = getProjectScope(); - user = this.server.create('user'); + user = this.server.create('user', { scope: orgScope }); role = this.server.create('role'); group = this.server.create('group', { scope: orgScope }); hostCatalog = this.server.create('host-catalog', { diff --git a/ui/core/tests/acceptance/users-test.js b/ui/core/tests/acceptance/users-test.js index 55e4ac108..26e6b3c55 100644 --- a/ui/core/tests/acceptance/users-test.js +++ b/ui/core/tests/acceptance/users-test.js @@ -30,10 +30,7 @@ module('Acceptance | users', function (hooks) { ); const user = this.server.create('user', { - scope: { - id: orgScope.id, - type: orgScope.type, - }, + scope: orgScope }); usersURL = `/scopes/${orgScope.id}/users`; @@ -78,7 +75,7 @@ module('Acceptance | users', function (hooks) { test('saving a new user with invalid fields displays error messages', async function (assert) { assert.expect(2); - this.server.post('scopes/:scope_id/users', () => { + this.server.post('/users', () => { return new Response( 400, {}, @@ -139,7 +136,7 @@ module('Acceptance | users', function (hooks) { test('saving an existing user with invalid fields displays error messages', async function (assert) { assert.expect(2); - this.server.patch('/scopes/:scope_id/users/:id', () => { + this.server.patch('/users/:id', () => { return new Response( 400, {}, @@ -175,7 +172,7 @@ module('Acceptance | users', function (hooks) { test('errors are displayed when saving user fails', async function (assert) { assert.expect(1); - this.server.patch('/scopes/:scope_id/users/:id', () => { + this.server.patch('/users/:id', () => { return new Response( 490, {}, @@ -198,7 +195,7 @@ module('Acceptance | users', function (hooks) { test('errors are displayed when user deletion fails', async function (assert) { assert.expect(1); - this.server.del('/scopes/:scope_id/users/:id', () => { + this.server.del('/users/:id', () => { return new Response( 490, {},