From edba33bbda8858707c2dcdc756408a906772c7cb Mon Sep 17 00:00:00 2001 From: Lisbet Alvarez <107949262+lisbet-alvarez@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:04:18 -0700 Subject: [PATCH] Icu 13359 refactor query requests for resources added to indexed db (#2224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 💡 update query requests for resources in indexedDB ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-13359 --- .../api/addon/handlers/indexed-db-handler.js | 2 +- addons/api/addon/models/role.js | 91 ------------------- .../scopes/scope/groups/group/add-members.js | 32 +++---- .../scopes/scope/groups/group/members.js | 40 ++++---- .../app/routes/scopes/scope/roles/index.js | 4 +- .../scopes/scope/roles/role/add-principals.js | 56 ++++-------- .../scopes/scope/roles/role/principals.js | 90 +++++++++++++++++- .../app/routes/scopes/scope/users/index.js | 2 +- .../templates/scopes/scope/roles/index.hbs | 2 +- .../templates/scopes/scope/targets/index.hbs | 2 +- .../templates/scopes/scope/users/index.hbs | 2 +- .../tests/acceptance/groups/members-test.js | 2 + .../tests/acceptance/roles/principals-test.js | 2 + .../scopes/scope/projects/sessions/index.hbs | 5 +- .../scopes/scope/projects/targets/index.hbs | 2 +- 15 files changed, 159 insertions(+), 175 deletions(-) diff --git a/addons/api/addon/handlers/indexed-db-handler.js b/addons/api/addon/handlers/indexed-db-handler.js index 99e582e33..6d760568d 100644 --- a/addons/api/addon/handlers/indexed-db-handler.js +++ b/addons/api/addon/handlers/indexed-db-handler.js @@ -112,7 +112,7 @@ export default class IndexedDbHandler { ); const dbRecords = paginateResults(indexedDbResults, page, pageSize).map( - this.indexedDb.normalizeData, + (item) => this.indexedDb.normalizeData(item, false), ); // Return the raw data if we don't push to the store. diff --git a/addons/api/addon/models/role.js b/addons/api/addon/models/role.js index e574eb7e7..610f49502 100644 --- a/addons/api/addon/models/role.js +++ b/addons/api/addon/models/role.js @@ -6,17 +6,11 @@ import GeneratedRoleModel from '../generated/models/role'; import { attr } from '@ember-data/model'; import { inject as service } from '@ember/service'; -import { all, resolve } from 'rsvp'; -import { - TYPE_AUTH_METHOD_OIDC, - TYPE_AUTH_METHOD_LDAP, -} from 'api/models/auth-method'; export default class RoleModel extends GeneratedRoleModel { // =services @service store; - @service resourceFilterStore; // =attributes @@ -78,91 +72,6 @@ export default class RoleModel extends GeneratedRoleModel { .map(({ id }) => id); } - /** - * A promise that resolves to an array of User instances. - * When calling this getter, be sure to await resolution - * before interacting with the results. - * @type {Promise[UserModel]} - */ - get users() { - const ids = this.userIDs; - - // Role has prinicipal IDs, - // return a promise which resolves model instances for those IDs - if (ids?.length) { - return this.resourceFilterStore - .queryBy('user', { id: ids }, { scope_id: 'global', recursive: true }) - .then((models) => models.map((model) => model)); - } - - // No principal IDs, - // return a promise resolving to an empty array - return resolve([]); - } - - /** - * A promise that resolves to an array of Group instances. - * When calling this getter, be sure to await resolution - * before interacting with the results. - * @type {Promise[GroupModel]} - */ - get groups() { - const ids = this.groupIDs; - - // Role has prinicipal IDs, - // return a promise which resolves model instances for those IDs - if (ids?.length) { - return this.resourceFilterStore - .queryBy('group', { id: ids }, { scope_id: 'global', recursive: true }) - .then((models) => models.map((model) => model)); - } - - // No principal IDs, - // return a promise resolving to an empty array - return resolve([]); - } - - /** - * A promise that resolves to an array of ManagedGroup instances. - * When calling this getter, be sure to await resolution - * before interacting with the results. - * @type {Promise[ManagedGroupModel]} - */ - get managedGroups() { - const ids = this.managedGroupIDs; - - // Role has prinicipal IDs - // return a promise which resolves model instances for those IDs - if (ids?.length) { - // Collect all auth methods. - const authMethods = this.resourceFilterStore.queryBy( - 'auth-method', - { type: [TYPE_AUTH_METHOD_OIDC, TYPE_AUTH_METHOD_LDAP] }, - { scope_id: 'global', recursive: true }, - ); - - // For each auth method, query all managed groups with IDs - // that match the role's manage group principal IDs. - const managedGroups = authMethods - .then((methods) => - all( - methods.map(({ id: auth_method_id }) => - this.resourceFilterStore - .queryBy('managed-group', { id: ids }, { auth_method_id }) - .then((models) => models.map((model) => model)), - ), - ), - ) - // The result is an array of arrays of model instances (grouped by - // auth methods), so these must be flattened. - .then((grouped) => grouped.flat()); - return managedGroups; - } - - // No principal IDs, - // return a promise resolving to an empty array - return resolve([]); - } // =methods /** diff --git a/ui/admin/app/routes/scopes/scope/groups/group/add-members.js b/ui/admin/app/routes/scopes/scope/groups/group/add-members.js index a04bcdc7b..82c67766b 100644 --- a/ui/admin/app/routes/scopes/scope/groups/group/add-members.js +++ b/ui/admin/app/routes/scopes/scope/groups/group/add-members.js @@ -6,16 +6,15 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import { action } from '@ember/object'; -import { hash } from 'rsvp'; +import { loading } from 'ember-loading'; +import { notifySuccess, notifyError } from 'core/decorators/notify'; import { resourceFilter } from 'core/decorators/resource-filter'; export default class ScopesScopeGroupsGroupAddMembersRoute extends Route { // =services @service store; - @service intl; @service router; - @service resourceFilterStore; // =attributes @@ -39,28 +38,25 @@ export default class ScopesScopeGroupsGroupAddMembersRoute extends Route { /** * Loads all users and returns them with the group. - * @return {Promise{GroupModel, [UserModel]}} + * @return {Promise<{group: GroupModel, users: [UserModel], scopes: [ScopeModel] }> } */ - model() { + async model() { const group = this.modelFor('scopes.scope.groups.group'); // filter out projects, since the user resource exists only on org and above const scopes = this.store .peekAll('scope') .filter((scope) => !scope.isProject); const scopeIDs = this.scope?.map((scope) => scope.id); - const users = scopeIDs?.length - ? this.resourceFilterStore.queryBy( - 'user', - { - scope_id: scopeIDs, - }, - { - scope_id: 'global', - recursive: true, - }, - ) - : this.store.query('user', { scope_id: 'global', recursive: true }); - return hash({ group, users, scopes }); + const query = { filters: { scope_id: [] } }; + scopeIDs?.forEach((scopeID) => { + query.filters.scope_id.push({ equals: scopeID }); + }); + const users = await this.store.query('user', { + scope_id: 'global', + recursive: true, + query, + }); + return { group, users, scopes }; } // =actions diff --git a/ui/admin/app/routes/scopes/scope/groups/group/members.js b/ui/admin/app/routes/scopes/scope/groups/group/members.js index 37711ed81..4ca020307 100644 --- a/ui/admin/app/routes/scopes/scope/groups/group/members.js +++ b/ui/admin/app/routes/scopes/scope/groups/group/members.js @@ -5,32 +5,40 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; -import { hash } from 'rsvp'; export default class ScopesScopeGroupsGroupMembersRoute extends Route { // =services - @service intl; - - @service resourceFilterStore; + @service store; // =methods /** * Returns users associated with this group. - * @return {Promise{group: GroupModel, members: Promise{[UserModel]}}} + * @return {Promise<{group: GroupModel, members: [UserModel]}>} */ - model() { + async model() { const group = this.modelFor('scopes.scope.groups.group'); - return hash({ - group, - members: group.member_ids?.length - ? this.resourceFilterStore.queryBy( - 'user', - { id: group.member_ids }, - { scope_id: 'global', recursive: true }, - ) - : [], - }); + const members = await this.getUsers(group.member_ids); + return { group, members }; + } + + /** + * Retrieves group members of user type. + * @param {array} ids + * @return {Promise[UserModel]} + */ + async getUsers(ids) { + let users = []; + if (ids?.length) { + const query = { filters: { id: [] } }; + ids.forEach((id) => query.filters.id.push({ equals: id })); + users = await this.store.query('user', { + scope_id: 'global', + recursive: true, + query, + }); + } + return users; } } diff --git a/ui/admin/app/routes/scopes/scope/roles/index.js b/ui/admin/app/routes/scopes/scope/roles/index.js index fc7fcda79..6d0e15823 100644 --- a/ui/admin/app/routes/scopes/scope/roles/index.js +++ b/ui/admin/app/routes/scopes/scope/roles/index.js @@ -31,7 +31,7 @@ export default class ScopesScopeRolesIndexRoute extends Route { /** * Loads queried roles and the number of roles under current scope. - * @returns {Promise<{totalItems: number, users: [RoleModel], rolesExist: boolean }> } + * @returns {Promise<{totalItems: number, roles: [RoleModel], rolesExist: boolean }> } */ async model({ search, page, pageSize }) { const scope = this.modelFor('scopes.scope'); @@ -61,7 +61,7 @@ export default class ScopesScopeRolesIndexRoute extends Route { * Sets rolesExist to true if there exists any roles. * @param {string} scope_id * @param {number} totalItems - * @returns + * @returns {Promise} */ async getRolesExist(scope_id, totalItems) { if (totalItems > 0) { diff --git a/ui/admin/app/routes/scopes/scope/roles/role/add-principals.js b/ui/admin/app/routes/scopes/scope/roles/role/add-principals.js index d64a2ed49..b5e4acc76 100644 --- a/ui/admin/app/routes/scopes/scope/roles/role/add-principals.js +++ b/ui/admin/app/routes/scopes/scope/roles/role/add-principals.js @@ -6,7 +6,6 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import { action } from '@ember/object'; -import { hash, all } from 'rsvp'; import { loading } from 'ember-loading'; import { notifySuccess, notifyError } from 'core/decorators/notify'; import { resourceFilter } from 'core/decorators/resource-filter'; @@ -18,7 +17,6 @@ import { export default class ScopesScopeRolesRoleAddPrincipalsRoute extends Route { // =services - @service intl; @service router; @service store; @service resourceFilterStore; @@ -51,30 +49,20 @@ export default class ScopesScopeRolesRoleAddPrincipalsRoute extends Route { const role = this.modelFor('scopes.scope.roles.role'); const scopes = this.store.peekAll('scope'); const scopeIDs = this.scope?.map((scope) => scope.id); - const users = scopeIDs?.length - ? this.resourceFilterStore.queryBy( - 'user', - { - scope_id: scopeIDs, - }, - { - scope_id: 'global', - recursive: true, - }, - ) - : this.store.query('user', { scope_id: 'global', recursive: true }); - const groups = scopeIDs?.length - ? this.resourceFilterStore.queryBy( - 'group', - { - scope_id: scopeIDs, - }, - { - scope_id: 'global', - recursive: true, - }, - ) - : this.store.query('group', { scope_id: 'global', recursive: true }); + const query = { filters: { scope_id: [] } }; + scopeIDs?.forEach((scopeID) => { + query.filters.scope_id.push({ equals: scopeID }); + }); + const users = await this.store.query('user', { + scope_id: 'global', + recursive: true, + query, + }); + const groups = await this.store.query('group', { + scope_id: 'global', + recursive: true, + query, + }); //query authmethods from all the scopes const authMethods = scopeIDs?.length ? await this.resourceFilterStore.queryBy( @@ -90,24 +78,20 @@ export default class ScopesScopeRolesRoleAddPrincipalsRoute extends Route { { type: [TYPE_AUTH_METHOD_OIDC, TYPE_AUTH_METHOD_LDAP] }, { scope_id: 'global', recursive: true }, ); - //extract oidc and ldap authMethod IDs - const authMethodIDs = authMethods.map(({ id }) => id); //query all the managed groups for each auth method id - const managedGroups = await all( - authMethodIDs.map((auth_method_id) => + const managedGroups = await Promise.all( + authMethods.map(({ id: auth_method_id }) => this.store.query('managed-group', { auth_method_id }), ), ); - const managedGroupModels = managedGroups - .map((models) => models.map((model) => model)) - .flat(); - return hash({ + + return { role, scopes, users, groups, - managedGroups: managedGroupModels, - }); + managedGroups: managedGroups.flat(), + }; } // =actions diff --git a/ui/admin/app/routes/scopes/scope/roles/role/principals.js b/ui/admin/app/routes/scopes/scope/roles/role/principals.js index 0e6a0ecf6..23590b04f 100644 --- a/ui/admin/app/routes/scopes/scope/roles/role/principals.js +++ b/ui/admin/app/routes/scopes/scope/roles/role/principals.js @@ -9,10 +9,15 @@ import { inject as service } from '@ember/service'; import { loading } from 'ember-loading'; import { confirm } from 'core/decorators/confirm'; import { notifySuccess, notifyError } from 'core/decorators/notify'; +import { + TYPE_AUTH_METHOD_OIDC, + TYPE_AUTH_METHOD_LDAP, +} from 'api/models/auth-method'; export default class ScopesScopeRolesRolePrincipalsRoute extends Route { // =services - @service intl; + + @service store; @service resourceFilterStore; // =methods @@ -24,16 +29,91 @@ export default class ScopesScopeRolesRolePrincipalsRoute extends Route { */ async model() { const role = this.modelFor('scopes.scope.roles.role'); - // Fetch user and group principals. - const users = await role.users; - const groups = await role.groups; - const managedGroups = await role.managedGroups; + // Fetch user principals. + const users = await this.getUsers(role.userIDs); + + // Fetch group principals. + const groups = await this.getGroups(role.groupIDs); + + // Fetch managedGroup principals. + const managedGroups = await this.getManagedGroups(role.managedGroupIDs); + // Merge polymorphic principals. const principals = [...users, ...groups, ...managedGroups]; return { role, principals }; } + /** + * Retrieves role principals of user type. + * @param {array} ids + * @return {Promise[UserModel]} + */ + async getUsers(ids) { + let users = []; + if (ids?.length) { + const query = { filters: { id: [] } }; + ids.forEach((id) => query.filters.id.push({ equals: id })); + users = await this.store.query('user', { + scope_id: 'global', + recursive: true, + query, + }); + } + return users; + } + + /** + * Retrieves role principals of group type. + * @param {array} ids + * @return {Promise[GroupModel]} + */ + async getGroups(ids) { + let groups = []; + if (ids?.length) { + const query = { filters: { id: [] } }; + ids.forEach((id) => query.filters.id.push({ equals: id })); + groups = await this.store.query('group', { + scope_id: 'global', + recursive: true, + query, + }); + } + return groups; + } + + /** + * Retrieves role principals of managed-group type. + * @param {array} ids + * @return {Promise[ManagedGroupModel]} + */ + async getManagedGroups(ids) { + let managedGroups = []; + if (ids?.length) { + // TODO: Refactor to directly use store service + // when types get added to indexedDB. + + // Collect all oidc and ldap type auth methods. + const authMethods = await this.resourceFilterStore.queryBy( + 'auth-method', + { type: [TYPE_AUTH_METHOD_OIDC, TYPE_AUTH_METHOD_LDAP] }, + { scope_id: 'global', recursive: true }, + ); + + const nestedManagedGroups = await Promise.all( + authMethods.map(({ id: auth_method_id }) => + this.resourceFilterStore.queryBy( + 'managed-group', + { id: ids }, + { auth_method_id }, + ), + ), + ); + managedGroups = nestedManagedGroups.flat(); + } + return managedGroups; + } + // =actions /** diff --git a/ui/admin/app/routes/scopes/scope/users/index.js b/ui/admin/app/routes/scopes/scope/users/index.js index ff2495328..4db25436f 100644 --- a/ui/admin/app/routes/scopes/scope/users/index.js +++ b/ui/admin/app/routes/scopes/scope/users/index.js @@ -62,7 +62,7 @@ export default class ScopesScopeUsersIndexRoute extends Route { * Sets usersExist to true if there exists any users. * @param {string} scope_id * @param {number} totalItems - * @returns + * @returns {Promise} */ async getUsersExist(scope_id, totalItems) { if (totalItems > 0) { diff --git a/ui/admin/app/templates/scopes/scope/roles/index.hbs b/ui/admin/app/templates/scopes/scope/roles/index.hbs index 2940994a9..98f8e5cd6 100644 --- a/ui/admin/app/templates/scopes/scope/roles/index.hbs +++ b/ui/admin/app/templates/scopes/scope/roles/index.hbs @@ -84,7 +84,7 @@ @text={{t 'descriptions.no-search-results' query=this.search - resource='roles' + resource=(t 'resources.role.title_plural') }} /> diff --git a/ui/admin/app/templates/scopes/scope/targets/index.hbs b/ui/admin/app/templates/scopes/scope/targets/index.hbs index 368e7d33e..a0ec48845 100644 --- a/ui/admin/app/templates/scopes/scope/targets/index.hbs +++ b/ui/admin/app/templates/scopes/scope/targets/index.hbs @@ -175,7 +175,7 @@ 'descriptions.no-filter-results' ) query=this.search - resource='targets' + resource=(t 'resources.target.title_plural') }} /> diff --git a/ui/admin/app/templates/scopes/scope/users/index.hbs b/ui/admin/app/templates/scopes/scope/users/index.hbs index c3d5ea57f..c097d00c7 100644 --- a/ui/admin/app/templates/scopes/scope/users/index.hbs +++ b/ui/admin/app/templates/scopes/scope/users/index.hbs @@ -87,7 +87,7 @@ @text={{t 'descriptions.no-search-results' query=this.search - resource='users' + resource=(t 'resources.user.title_plural') }} /> diff --git a/ui/admin/tests/acceptance/groups/members-test.js b/ui/admin/tests/acceptance/groups/members-test.js index 1665fc4a1..22c97e1bc 100644 --- a/ui/admin/tests/acceptance/groups/members-test.js +++ b/ui/admin/tests/acceptance/groups/members-test.js @@ -7,6 +7,7 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, find, findAll } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; import { @@ -19,6 +20,7 @@ import { module('Acceptance | groups | members', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupIndexedDb(hooks); const instances = { scopes: { diff --git a/ui/admin/tests/acceptance/roles/principals-test.js b/ui/admin/tests/acceptance/roles/principals-test.js index 28ca815d2..4b0d61fb5 100644 --- a/ui/admin/tests/acceptance/roles/principals-test.js +++ b/ui/admin/tests/acceptance/roles/principals-test.js @@ -7,6 +7,7 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, find, findAll } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; import { @@ -19,6 +20,7 @@ import { module('Acceptance | roles | principals', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupIndexedDb(hooks); const instances = { scopes: { diff --git a/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs b/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs index 7782a52eb..9287535a7 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs @@ -158,7 +158,10 @@ {{/if}} diff --git a/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs b/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs index 1b27913ee..3d7461695 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs @@ -181,7 +181,7 @@ 'descriptions.no-filter-results' ) query=this.search - resource='targets' + resource=(t 'resources.target.title_plural') }} />