Icu 13359 refactor query requests for resources added to indexed db (#2224)

* refactor: 💡 update query requests for resources in indexedDB

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-13359
This commit is contained in:
Lisbet Alvarez authored and Zhihe Li committed 2024-04-12 14:55:34 -04:00
1 parent 5d32d987bc
commit edba33bbda
15 files changed
+159 -175

No files matched your search

@@ -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.
-91
View File
@@ -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
/**
@@ -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
@@ -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;
}
}
@@ -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<boolean>}
*/
async getRolesExist(scope_id, totalItems) {
if (totalItems > 0) {
@@ -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
@@ -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
/**
@@ -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<boolean>}
*/
async getUsersExist(scope_id, totalItems) {
if (totalItems > 0) {
@@ -84,7 +84,7 @@
@text={{t
'descriptions.no-search-results'
query=this.search
resource='roles'
resource=(t 'resources.role.title_plural')
}}
/>
</Hds::ApplicationState>
@@ -175,7 +175,7 @@
'descriptions.no-filter-results'
)
query=this.search
resource='targets'
resource=(t 'resources.target.title_plural')
}}
/>
</Hds::ApplicationState>
@@ -87,7 +87,7 @@
@text={{t
'descriptions.no-search-results'
query=this.search
resource='users'
resource=(t 'resources.user.title_plural')
}}
/>
</Hds::ApplicationState>
@@ -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: {
@@ -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: {
@@ -158,7 +158,10 @@
<Hds::ApplicationState data-test-no-session-results as |A|>
<A.Header @title={{t 'titles.no-results-found'}} />
<A.Body
@text={{t 'descriptions.no-filter-results' resource='sessions'}}
@text={{t
'descriptions.no-filter-results'
resource=(t 'resources.session.title_plural')
}}
/>
</Hds::ApplicationState>
{{/if}}
@@ -181,7 +181,7 @@
'descriptions.no-filter-results'
)
query=this.search
resource='targets'
resource=(t 'resources.target.title_plural')
}}
/>
</Hds::ApplicationState>