From f8b3792e629dc56f127fd5d77e3bca96c4286fc9 Mon Sep 17 00:00:00 2001 From: Zhihe Li Date: Tue, 18 Mar 2025 11:20:50 -0400 Subject: [PATCH] Fix Searchbar debouncing in admin UI (#2735) Applied ember-concurrency task usage to admin UI model hooks to help fix the debouncing issue. --- .../controllers/scopes/scope/aliases/index.js | 2 - .../scopes/scope/auth-methods/index.js | 2 - .../scopes/scope/credential-stores/index.js | 2 - .../controllers/scopes/scope/groups/index.js | 4 +- .../scopes/scope/host-catalogs/index.js | 2 - .../controllers/scopes/scope/roles/index.js | 4 +- .../manage-scopes/manage-custom-scopes.js | 4 +- .../role/manage-scopes/manage-org-projects.js | 4 +- .../scopes/scope/roles/role/scopes.js | 4 +- .../controllers/scopes/scope/scopes/index.js | 4 +- .../scopes/scope/session-recordings/index.js | 4 +- .../scopes/scope/sessions/index.js | 4 +- .../controllers/scopes/scope/targets/index.js | 2 - .../controllers/scopes/scope/users/index.js | 4 +- .../app/routes/scopes/scope/aliases/index.js | 89 ++++++----- .../routes/scopes/scope/auth-methods/index.js | 97 ++++++------ .../scopes/scope/credential-stores/index.js | 79 ++++++---- .../app/routes/scopes/scope/groups/index.js | 53 ++++--- .../scopes/scope/host-catalogs/index.js | 69 +++++---- .../app/routes/scopes/scope/roles/index.js | 57 ++++--- .../manage-scopes/manage-custom-scopes.js | 79 ++++++---- .../role/manage-scopes/manage-org-projects.js | 75 +++++---- .../routes/scopes/scope/roles/role/scopes.js | 87 ++++++----- .../app/routes/scopes/scope/scopes/index.js | 57 ++++--- .../scopes/scope/session-recordings/index.js | 142 ++++++++++-------- .../app/routes/scopes/scope/sessions/index.js | 111 ++++++++------ .../app/routes/scopes/scope/targets/index.js | 108 +++++++------ .../app/routes/scopes/scope/users/index.js | 59 +++++--- 28 files changed, 689 insertions(+), 519 deletions(-) diff --git a/ui/admin/app/controllers/scopes/scope/aliases/index.js b/ui/admin/app/controllers/scopes/scope/aliases/index.js index 06c1db8b7..c9c3785d2 100644 --- a/ui/admin/app/controllers/scopes/scope/aliases/index.js +++ b/ui/admin/app/controllers/scopes/scope/aliases/index.js @@ -8,7 +8,6 @@ import { service } from '@ember/service'; import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; import { loading } from 'ember-loading'; -import { debounce } from 'core/decorators/debounce'; import { notifySuccess, notifyError } from 'core/decorators/notify'; import { confirm } from 'core/decorators/confirm'; @@ -120,7 +119,6 @@ export default class ScopesScopeAliasesIndexController extends Controller { * @param {object} event */ @action - @debounce(250) handleSearchInput(event) { const { value } = event.target; this.search = value; diff --git a/ui/admin/app/controllers/scopes/scope/auth-methods/index.js b/ui/admin/app/controllers/scopes/scope/auth-methods/index.js index 2fba30a7e..5a12091a6 100644 --- a/ui/admin/app/controllers/scopes/scope/auth-methods/index.js +++ b/ui/admin/app/controllers/scopes/scope/auth-methods/index.js @@ -10,7 +10,6 @@ import { loading } from 'ember-loading'; import { confirm } from 'core/decorators/confirm'; import { notifySuccess, notifyError } from 'core/decorators/notify'; import { tracked } from '@glimmer/tracking'; -import { debounce } from 'core/decorators/debounce'; import { TYPES_AUTH_METHOD } from 'api/models/auth-method'; export default class ScopesScopeAuthMethodsIndexController extends Controller { @@ -104,7 +103,6 @@ export default class ScopesScopeAuthMethodsIndexController extends Controller { * @param {object} event */ @action - @debounce(250) handleSearchInput(event) { const { value } = event.target; this.search = value; diff --git a/ui/admin/app/controllers/scopes/scope/credential-stores/index.js b/ui/admin/app/controllers/scopes/scope/credential-stores/index.js index 19039610d..33a6ea028 100644 --- a/ui/admin/app/controllers/scopes/scope/credential-stores/index.js +++ b/ui/admin/app/controllers/scopes/scope/credential-stores/index.js @@ -9,7 +9,6 @@ import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; import { loading } from 'ember-loading'; import { confirm } from 'core/decorators/confirm'; -import { debounce } from 'core/decorators/debounce'; import { notifySuccess, notifyError } from 'core/decorators/notify'; import { TYPES_CREDENTIAL_STORE } from 'api/models/credential-store'; @@ -133,7 +132,6 @@ export default class ScopesScopeCredentialStoresIndexController extends Controll * @param {object} event */ @action - @debounce(250) handleSearchInput(event) { const { value } = event.target; this.search = value; diff --git a/ui/admin/app/controllers/scopes/scope/groups/index.js b/ui/admin/app/controllers/scopes/scope/groups/index.js index 348e1d61d..2d570c9cc 100644 --- a/ui/admin/app/controllers/scopes/scope/groups/index.js +++ b/ui/admin/app/controllers/scopes/scope/groups/index.js @@ -10,7 +10,6 @@ import { action } from '@ember/object'; import { loading } from 'ember-loading'; import { confirm } from 'core/decorators/confirm'; import { notifySuccess, notifyError } from 'core/decorators/notify'; -import { debounce } from 'core/decorators/debounce'; export default class ScopesScopeGroupsIndexController extends Controller { // =services @@ -23,7 +22,7 @@ export default class ScopesScopeGroupsIndexController extends Controller { queryParams = ['search', 'page', 'pageSize']; - @tracked search = ''; + @tracked search; @tracked page = 1; @tracked pageSize = 10; @@ -120,7 +119,6 @@ export default class ScopesScopeGroupsIndexController extends Controller { * @param {object} event */ @action - @debounce(250) handleSearchInput(event) { const { value } = event.target; this.search = value; diff --git a/ui/admin/app/controllers/scopes/scope/host-catalogs/index.js b/ui/admin/app/controllers/scopes/scope/host-catalogs/index.js index c28af507b..144785a7d 100644 --- a/ui/admin/app/controllers/scopes/scope/host-catalogs/index.js +++ b/ui/admin/app/controllers/scopes/scope/host-catalogs/index.js @@ -7,7 +7,6 @@ import Controller from '@ember/controller'; import { service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; -import { debounce } from 'core/decorators/debounce'; import { loading } from 'ember-loading'; import { confirm } from 'core/decorators/confirm'; import { notifySuccess, notifyError } from 'core/decorators/notify'; @@ -60,7 +59,6 @@ export default class ScopesScopeHostCatalogsIndexController extends Controller { * @param {object} event */ @action - @debounce(250) handleSearchInput(event) { const { value } = event.target; this.search = value; diff --git a/ui/admin/app/controllers/scopes/scope/roles/index.js b/ui/admin/app/controllers/scopes/scope/roles/index.js index 883673939..41cf57183 100644 --- a/ui/admin/app/controllers/scopes/scope/roles/index.js +++ b/ui/admin/app/controllers/scopes/scope/roles/index.js @@ -8,7 +8,6 @@ import { service } from '@ember/service'; import { action } from '@ember/object'; import { loading } from 'ember-loading'; import { tracked } from '@glimmer/tracking'; -import { debounce } from 'core/decorators/debounce'; import { confirm } from 'core/decorators/confirm'; import { notifySuccess, notifyError } from 'core/decorators/notify'; import { GRANT_SCOPE_THIS } from 'api/models/role'; @@ -24,7 +23,7 @@ export default class ScopesScopeRolesIndexController extends Controller { queryParams = ['search', 'page', 'pageSize']; - @tracked search = ''; + @tracked search; @tracked page = 1; @tracked pageSize = 10; @@ -62,7 +61,6 @@ export default class ScopesScopeRolesIndexController extends Controller { * @param {object} event */ @action - @debounce(250) handleSearchInput(event) { const { value } = event.target; this.search = value; diff --git a/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js b/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js index 9269947ea..1d8802549 100644 --- a/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js +++ b/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js @@ -8,7 +8,6 @@ import { service } from '@ember/service'; import { action } from '@ember/object'; import { loading } from 'ember-loading'; import { tracked } from '@glimmer/tracking'; -import { debounce } from 'core/decorators/debounce'; import { notifySuccess, notifyError } from 'core/decorators/notify'; export default class ScopesScopeRolesRoleManageScopesManageCustomScopesController extends Controller { @@ -22,7 +21,7 @@ export default class ScopesScopeRolesRoleManageScopesManageCustomScopesControlle queryParams = ['search', 'page', 'pageSize']; - @tracked search = ''; + @tracked search; @tracked page = 1; @tracked pageSize = 10; @@ -33,7 +32,6 @@ export default class ScopesScopeRolesRoleManageScopesManageCustomScopesControlle * @param {object} event */ @action - @debounce(250) handleSearchInput(event) { const { value } = event.target; this.search = value; diff --git a/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects.js b/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects.js index 3d90e447b..0e509a720 100644 --- a/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects.js +++ b/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects.js @@ -8,7 +8,6 @@ import { service } from '@ember/service'; import { action } from '@ember/object'; import { loading } from 'ember-loading'; import { tracked } from '@glimmer/tracking'; -import { debounce } from 'core/decorators/debounce'; import { notifySuccess, notifyError } from 'core/decorators/notify'; export default class ScopesScopeRolesRoleManageScopesManageOrgProjectsController extends Controller { @@ -22,7 +21,7 @@ export default class ScopesScopeRolesRoleManageScopesManageOrgProjectsController queryParams = ['search', 'page', 'pageSize']; - @tracked search = ''; + @tracked search; @tracked page = 1; @tracked pageSize = 10; @@ -33,7 +32,6 @@ export default class ScopesScopeRolesRoleManageScopesManageOrgProjectsController * @param {object} event */ @action - @debounce(250) handleSearchInput(event) { const { value } = event.target; this.search = value; diff --git a/ui/admin/app/controllers/scopes/scope/roles/role/scopes.js b/ui/admin/app/controllers/scopes/scope/roles/role/scopes.js index 2c1b4d176..c8e58c342 100644 --- a/ui/admin/app/controllers/scopes/scope/roles/role/scopes.js +++ b/ui/admin/app/controllers/scopes/scope/roles/role/scopes.js @@ -7,7 +7,6 @@ import Controller, { inject as controller } from '@ember/controller'; import { service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; -import { debounce } from 'core/decorators/debounce'; import { GRANT_SCOPE_THIS, GRANT_SCOPE_CHILDREN, @@ -32,7 +31,7 @@ export default class ScopesScopeRolesRoleScopesController extends Controller { 'pageSize', ]; - @tracked search = ''; + @tracked search; @tracked parentScopes = []; @tracked types = []; @tracked page = 1; @@ -108,7 +107,6 @@ export default class ScopesScopeRolesRoleScopesController extends Controller { * @param {object} event */ @action - @debounce(250) handleSearchInput(event) { const { value } = event.target; this.search = value; diff --git a/ui/admin/app/controllers/scopes/scope/scopes/index.js b/ui/admin/app/controllers/scopes/scope/scopes/index.js index 44e08bc18..8cf136e2d 100644 --- a/ui/admin/app/controllers/scopes/scope/scopes/index.js +++ b/ui/admin/app/controllers/scopes/scope/scopes/index.js @@ -6,14 +6,13 @@ import Controller from '@ember/controller'; import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; -import { debounce } from 'core/decorators/debounce'; export default class ScopesScopeScopesIndexController extends Controller { // =attributes queryParams = ['search', 'page', 'pageSize']; - @tracked search = ''; + @tracked search; @tracked page = 1; @tracked pageSize = 10; @@ -24,7 +23,6 @@ export default class ScopesScopeScopesIndexController extends Controller { * @param {object} event */ @action - @debounce(250) handleSearchInput(event) { const { value } = event.target; this.search = value; diff --git a/ui/admin/app/controllers/scopes/scope/session-recordings/index.js b/ui/admin/app/controllers/scopes/scope/session-recordings/index.js index 71cecff0b..6b8531f70 100644 --- a/ui/admin/app/controllers/scopes/scope/session-recordings/index.js +++ b/ui/admin/app/controllers/scopes/scope/session-recordings/index.js @@ -6,7 +6,6 @@ import Controller from '@ember/controller'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; -import { debounce } from 'core/decorators/debounce'; import { service } from '@ember/service'; export default class ScopesScopeSessionRecordingsIndexController extends Controller { @@ -29,7 +28,7 @@ export default class ScopesScopeSessionRecordingsIndexController extends Control now = new Date(); - @tracked search = ''; + @tracked search; @tracked time = null; @tracked users = []; @tracked scopes = []; @@ -155,7 +154,6 @@ export default class ScopesScopeSessionRecordingsIndexController extends Control * @param {object} event */ @action - @debounce(250) handleSearchInput(event) { const { value } = event.target; this.search = value; diff --git a/ui/admin/app/controllers/scopes/scope/sessions/index.js b/ui/admin/app/controllers/scopes/scope/sessions/index.js index d2749d3c5..21030eeae 100644 --- a/ui/admin/app/controllers/scopes/scope/sessions/index.js +++ b/ui/admin/app/controllers/scopes/scope/sessions/index.js @@ -13,7 +13,6 @@ import { statusTypes, } from 'api/models/session'; import { action } from '@ember/object'; -import { debounce } from 'core/decorators/debounce'; import { notifySuccess, notifyError } from 'core/decorators/notify'; export default class ScopesScopeSessionsIndexController extends Controller { @@ -33,7 +32,7 @@ export default class ScopesScopeSessionsIndexController extends Controller { 'pageSize', ]; - @tracked search = ''; + @tracked search; @tracked users = []; @tracked targets = []; @tracked status = [ @@ -81,7 +80,6 @@ export default class ScopesScopeSessionsIndexController extends Controller { * @param {object} event */ @action - @debounce(250) handleSearchInput(event) { const { value } = event.target; this.search = value; diff --git a/ui/admin/app/controllers/scopes/scope/targets/index.js b/ui/admin/app/controllers/scopes/scope/targets/index.js index 121ab1ab7..c99384fb4 100644 --- a/ui/admin/app/controllers/scopes/scope/targets/index.js +++ b/ui/admin/app/controllers/scopes/scope/targets/index.js @@ -7,7 +7,6 @@ import Controller from '@ember/controller'; import { service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; -import { debounce } from 'core/decorators/debounce'; import { loading } from 'ember-loading'; import { confirm } from 'core/decorators/confirm'; import { notifySuccess, notifyError } from 'core/decorators/notify'; @@ -104,7 +103,6 @@ export default class ScopesScopeTargetsIndexController extends Controller { * @param {object} event */ @action - @debounce(250) handleSearchInput(event) { const { value } = event.target; this.search = value; diff --git a/ui/admin/app/controllers/scopes/scope/users/index.js b/ui/admin/app/controllers/scopes/scope/users/index.js index 7950573ab..912e8b312 100644 --- a/ui/admin/app/controllers/scopes/scope/users/index.js +++ b/ui/admin/app/controllers/scopes/scope/users/index.js @@ -7,7 +7,6 @@ import Controller from '@ember/controller'; import { service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; -import { debounce } from 'core/decorators/debounce'; import { loading } from 'ember-loading'; import { confirm } from 'core/decorators/confirm'; import { notifySuccess, notifyError } from 'core/decorators/notify'; @@ -23,7 +22,7 @@ export default class ScopesScopeUsersIndexController extends Controller { queryParams = ['search', 'page', 'pageSize']; - @tracked search = ''; + @tracked search; @tracked page = 1; @tracked pageSize = 10; @@ -59,7 +58,6 @@ export default class ScopesScopeUsersIndexController extends Controller { * @param {object} event */ @action - @debounce(250) handleSearchInput(event) { const { value } = event.target; this.search = value; diff --git a/ui/admin/app/routes/scopes/scope/aliases/index.js b/ui/admin/app/routes/scopes/scope/aliases/index.js index 2c80624a0..b78171f54 100644 --- a/ui/admin/app/routes/scopes/scope/aliases/index.js +++ b/ui/admin/app/routes/scopes/scope/aliases/index.js @@ -6,6 +6,7 @@ import Route from '@ember/routing/route'; import { service } from '@ember/service'; import { hash } from 'rsvp'; +import { restartableTask, timeout } from 'ember-concurrency'; export default class ScopesScopeAliasesIndexRoute extends Route { // =services @@ -34,46 +35,58 @@ export default class ScopesScopeAliasesIndexRoute extends Route { * Loads queried aliases and the number of aliases under current scope. * @returns {Promise<{totalItems: number, aliases: [object], aliasesExist: boolean}>} */ - async model({ search, page, pageSize }) { - const scope = this.modelFor('scopes.scope'); - const { id: scope_id } = scope; - - let aliases; - let doAliasesExist = false; - let totalItems = 0; - - if ( - this.can.can('list model', scope, { - collection: 'aliases', - }) - ) { - aliases = await this.store.query('alias', { - scope_id, - query: { search }, - page, - pageSize, - }); - totalItems = aliases.meta?.totalItems; - // since we don't receive target info from aliases list API, - // we query the store to fetch target information based on the destination id - aliases = await Promise.all( - aliases.map((alias) => - hash({ - alias, - target: alias.destination_id - ? this.store.findRecord('target', alias.destination_id, { - backgroundReload: false, - }) - : null, - }), - ), - ); - doAliasesExist = await this.getDoAliasesExist(scope_id, totalItems); - } - - return { aliases, doAliasesExist, totalItems }; + async model(params) { + const useDebounce = + this.retrieveData?.lastPerformed?.args?.[0].search !== params.search; + return this.retrieveData.perform({ ...params, useDebounce }); } + retrieveData = restartableTask( + async ({ search, page, pageSize, useDebounce }) => { + if (useDebounce) { + await timeout(250); + } + + const scope = this.modelFor('scopes.scope'); + const { id: scope_id } = scope; + + let aliases; + let doAliasesExist = false; + let totalItems = 0; + + if ( + this.can.can('list model', scope, { + collection: 'aliases', + }) + ) { + aliases = await this.store.query('alias', { + scope_id, + query: { search }, + page, + pageSize, + }); + totalItems = aliases.meta?.totalItems; + // since we don't receive target info from aliases list API, + // we query the store to fetch target information based on the destination id + aliases = await Promise.all( + aliases.map((alias) => + hash({ + alias, + target: alias.destination_id + ? this.store.findRecord('target', alias.destination_id, { + backgroundReload: false, + }) + : null, + }), + ), + ); + doAliasesExist = await this.getDoAliasesExist(scope_id, totalItems); + } + + return { aliases, doAliasesExist, totalItems }; + }, + ); + /** * Sets doAliasesExist to true if there are any aliases. * @param {string} scope_id diff --git a/ui/admin/app/routes/scopes/scope/auth-methods/index.js b/ui/admin/app/routes/scopes/scope/auth-methods/index.js index ce894c541..2ca60c9d2 100644 --- a/ui/admin/app/routes/scopes/scope/auth-methods/index.js +++ b/ui/admin/app/routes/scopes/scope/auth-methods/index.js @@ -5,6 +5,7 @@ import Route from '@ember/routing/route'; import { service } from '@ember/service'; +import { restartableTask, timeout } from 'ember-concurrency'; export default class ScopesScopeAuthMethodsIndexRoute extends Route { // =services @@ -42,50 +43,62 @@ export default class ScopesScopeAuthMethodsIndexRoute extends Route { * Loads queried auth-methods and the number of auth-methods under current scope. * @returns {Promise<{totalItems: number, authMethods: [AuthMethodModel], doAuthMethodsExist: boolean }> } */ - async model({ search, types, primary, page, pageSize }) { - const scope = this.modelFor('scopes.scope'); - const { id: scope_id } = scope; - const filters = { - scope_id: [{ equals: scope_id }], - type: [], - is_primary: [], - }; - - types.forEach((type) => { - filters.type.push({ equals: type }); - }); - if (primary.length == 1) { - primary.forEach((val) => { - filters.is_primary.push({ equals: val }); - }); - } - - let authMethods; - let totalItems = 0; - let doAuthMethodsExist = false; - if (this.can.can('list model', scope, { collection: 'auth-methods' })) { - // TODO: Remove storeToken option as this is a temporary fix for auth-methods. - const options = { storeToken: false }; - authMethods = await this.store.query( - 'auth-method', - { - scope_id, - query: { search, filters }, - page, - pageSize, - }, - options, - ); - totalItems = authMethods.meta?.totalItems; - doAuthMethodsExist = await this.getDoAuthMethodsExist( - scope_id, - totalItems, - ); - } - - return { authMethods, doAuthMethodsExist, totalItems }; + async model(params) { + const useDebounce = + this.retrieveData?.lastPerformed?.args?.[0].search !== params.search; + return this.retrieveData.perform({ ...params, useDebounce }); } + retrieveData = restartableTask( + async ({ search, types, primary, page, pageSize, useDebounce }) => { + if (useDebounce) { + await timeout(250); + } + + const scope = this.modelFor('scopes.scope'); + const { id: scope_id } = scope; + const filters = { + scope_id: [{ equals: scope_id }], + type: [], + is_primary: [], + }; + + types.forEach((type) => { + filters.type.push({ equals: type }); + }); + if (primary.length === 1) { + primary.forEach((val) => { + filters.is_primary.push({ equals: val }); + }); + } + + let authMethods; + let totalItems = 0; + let doAuthMethodsExist = false; + if (this.can.can('list model', scope, { collection: 'auth-methods' })) { + // TODO: Remove storeToken option as this is a temporary fix for auth-methods. + const options = { storeToken: false }; + authMethods = await this.store.query( + 'auth-method', + { + scope_id, + query: { search, filters }, + page, + pageSize, + }, + options, + ); + totalItems = authMethods.meta?.totalItems; + doAuthMethodsExist = await this.getDoAuthMethodsExist( + scope_id, + totalItems, + ); + } + + return { authMethods, doAuthMethodsExist, totalItems }; + }, + ); + /** * Sets doAuthMethodsExist to true if there exists any auth methods. * @param {string} scope_id diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/index.js b/ui/admin/app/routes/scopes/scope/credential-stores/index.js index a668e1217..92e04cef8 100644 --- a/ui/admin/app/routes/scopes/scope/credential-stores/index.js +++ b/ui/admin/app/routes/scopes/scope/credential-stores/index.js @@ -5,6 +5,7 @@ import Route from '@ember/routing/route'; import { service } from '@ember/service'; +import { restartableTask, timeout } from 'ember-concurrency'; export default class ScopesScopeCredentialStoresIndexRoute extends Route { // =services @@ -37,41 +38,53 @@ export default class ScopesScopeCredentialStoresIndexRoute extends Route { * Loads queried credential-stores and the number of credential-stores under current scope. * @returns {Promise<{totalItems: number, credentialStores: [CredentialStoreModel], doCredentialStoresExist: boolean }> } */ - async model({ search, types, page, pageSize }) { - const scope = this.modelFor('scopes.scope'); - const { id: scope_id } = scope; - - const filters = { - scope_id: [{ equals: scope_id }], - type: [], - }; - types.forEach((type) => { - filters.type.push({ equals: type }); - }); - - let credentialStores; - let totalItems = 0; - let doCredentialStoresExist = false; - if ( - this.can.can('list model', scope, { - collection: 'credential-stores', - }) - ) { - credentialStores = await this.store.query('credential-store', { - scope_id, - query: { search, filters }, - page, - pageSize, - }); - totalItems = credentialStores.meta?.totalItems; - doCredentialStoresExist = await this.getDoCredentialStoresExist( - scope_id, - totalItems, - ); - } - return { credentialStores, doCredentialStoresExist, totalItems }; + async model(params) { + const useDebounce = + this.retrieveData?.lastPerformed?.args?.[0].search !== params.search; + return this.retrieveData.perform({ ...params, useDebounce }); } + retrieveData = restartableTask( + async ({ search, types, page, pageSize, useDebounce }) => { + if (useDebounce) { + await timeout(250); + } + + const scope = this.modelFor('scopes.scope'); + const { id: scope_id } = scope; + + const filters = { + scope_id: [{ equals: scope_id }], + type: [], + }; + types.forEach((type) => { + filters.type.push({ equals: type }); + }); + + let credentialStores; + let totalItems = 0; + let doCredentialStoresExist = false; + if ( + this.can.can('list model', scope, { + collection: 'credential-stores', + }) + ) { + credentialStores = await this.store.query('credential-store', { + scope_id, + query: { search, filters }, + page, + pageSize, + }); + totalItems = credentialStores.meta?.totalItems; + doCredentialStoresExist = await this.getDoCredentialStoresExist( + scope_id, + totalItems, + ); + } + return { credentialStores, doCredentialStoresExist, totalItems }; + }, + ); + /** * Sets doCredentialStoresExist to true if there exists any credential-stores. * @param {string} scope_id diff --git a/ui/admin/app/routes/scopes/scope/groups/index.js b/ui/admin/app/routes/scopes/scope/groups/index.js index 7085d5dda..2859ffb0a 100644 --- a/ui/admin/app/routes/scopes/scope/groups/index.js +++ b/ui/admin/app/routes/scopes/scope/groups/index.js @@ -5,6 +5,7 @@ import Route from '@ember/routing/route'; import { service } from '@ember/service'; +import { restartableTask, timeout } from 'ember-concurrency'; export default class ScopesScopeGroupsIndexRoute extends Route { // =services @@ -33,28 +34,40 @@ export default class ScopesScopeGroupsIndexRoute extends Route { * Loads queried groups and the number of groups under current scope. * @returns {Promise<{totalItems: number, groups: [GroupModel], doGroupsExist: boolean }> } */ - async model({ search, page, pageSize }) { - const scope = this.modelFor('scopes.scope'); - const { id: scope_id } = scope; - let groups = []; - let totalItems = 0; - let doGroupsExist = false; - const filters = { scope_id: [{ equals: scope_id }] }; - - if (this.can.can('list model', scope, { collection: 'groups' })) { - groups = await this.store.query('group', { - scope_id, - query: { filters, search }, - page, - pageSize, - }); - totalItems = groups.meta?.totalItems; - doGroupsExist = await this.getDoGroupsExist(scope_id, totalItems); - } - - return { groups, doGroupsExist, totalItems }; + async model(params) { + const useDebounce = + this.retrieveData?.lastPerformed?.args?.[0].search !== params.search; + return this.retrieveData.perform({ ...params, useDebounce }); } + retrieveData = restartableTask( + async ({ search, page, pageSize, useDebounce }) => { + if (useDebounce) { + await timeout(250); + } + + const scope = this.modelFor('scopes.scope'); + const { id: scope_id } = scope; + let groups = []; + let totalItems = 0; + let doGroupsExist = false; + const filters = { scope_id: [{ equals: scope_id }] }; + + if (this.can.can('list model', scope, { collection: 'groups' })) { + groups = await this.store.query('group', { + scope_id, + query: { filters, search }, + page, + pageSize, + }); + totalItems = groups.meta?.totalItems; + doGroupsExist = await this.getDoGroupsExist(scope_id, totalItems); + } + + return { groups, doGroupsExist, totalItems }; + }, + ); + /** * Sets doGroupsExist to true if there exists any groups. * @param {string} scope_id diff --git a/ui/admin/app/routes/scopes/scope/host-catalogs/index.js b/ui/admin/app/routes/scopes/scope/host-catalogs/index.js index f75759684..144dbefac 100644 --- a/ui/admin/app/routes/scopes/scope/host-catalogs/index.js +++ b/ui/admin/app/routes/scopes/scope/host-catalogs/index.js @@ -5,6 +5,7 @@ import Route from '@ember/routing/route'; import { service } from '@ember/service'; +import { restartableTask, timeout } from 'ember-concurrency'; export default class ScopesScopeHostCatalogsIndexRoute extends Route { // =services @@ -33,36 +34,48 @@ export default class ScopesScopeHostCatalogsIndexRoute extends Route { * Loads all host catalogs under the current scope. * @returns {Promise<{doHostCatalogsExist: boolean, totalItems: number, hostCatalogs: [HostCatalogModel]}>} */ - async model({ search, page, pageSize }) { - const scope = this.modelFor('scopes.scope'); - const { id: scope_id } = scope; - - const filters = { - scope_id: [{ equals: scope_id }], - type: [], - 'plugin.name': [], - }; - - let hostCatalogs; - let totalItems = 0; - let doHostCatalogsExist = false; - if (this.can.can('list model', scope, { collection: 'host-catalogs' })) { - hostCatalogs = await this.store.query('host-catalog', { - scope_id, - query: { search, filters }, - page, - pageSize, - }); - totalItems = hostCatalogs.meta?.totalItems; - doHostCatalogsExist = await this.getDoHostCatalogsExist( - scope_id, - totalItems, - ); - } - - return { hostCatalogs, totalItems, doHostCatalogsExist }; + async model(params) { + const useDebounce = + this.retrieveData?.lastPerformed?.args?.[0].search !== params.search; + return this.retrieveData.perform({ ...params, useDebounce }); } + retrieveData = restartableTask( + async ({ search, page, pageSize, useDebounce }) => { + if (useDebounce) { + await timeout(250); + } + + const scope = this.modelFor('scopes.scope'); + const { id: scope_id } = scope; + + const filters = { + scope_id: [{ equals: scope_id }], + type: [], + 'plugin.name': [], + }; + + let hostCatalogs; + let totalItems = 0; + let doHostCatalogsExist = false; + if (this.can.can('list model', scope, { collection: 'host-catalogs' })) { + hostCatalogs = await this.store.query('host-catalog', { + scope_id, + query: { search, filters }, + page, + pageSize, + }); + totalItems = hostCatalogs.meta?.totalItems; + doHostCatalogsExist = await this.getDoHostCatalogsExist( + scope_id, + totalItems, + ); + } + + return { hostCatalogs, totalItems, doHostCatalogsExist }; + }, + ); + /** * Returns true if any host catalogs exist. * @param {string} scope_id diff --git a/ui/admin/app/routes/scopes/scope/roles/index.js b/ui/admin/app/routes/scopes/scope/roles/index.js index 44e7ad4a1..725931353 100644 --- a/ui/admin/app/routes/scopes/scope/roles/index.js +++ b/ui/admin/app/routes/scopes/scope/roles/index.js @@ -5,6 +5,7 @@ import Route from '@ember/routing/route'; import { service } from '@ember/service'; +import { restartableTask, timeout } from 'ember-concurrency'; export default class ScopesScopeRolesIndexRoute extends Route { // =attributes @@ -33,30 +34,42 @@ export default class ScopesScopeRolesIndexRoute extends Route { * Loads queried roles and the number of roles under current scope. * @returns {Promise<{totalItems: number, roles: [RoleModel], doRolesExist: boolean }> } */ - async model({ search, page, pageSize }) { - const scope = this.modelFor('scopes.scope'); - const { id: scope_id } = scope; - const filters = { - scope_id: [{ equals: scope_id }], - }; - - let roles; - let totalItems = 0; - let doRolesExist = false; - if (this.can.can('list model', scope, { collection: 'roles' })) { - roles = await this.store.query('role', { - scope_id, - query: { search, filters }, - page, - pageSize, - }); - totalItems = roles.meta?.totalItems; - doRolesExist = await this.getDoRolesExist(scope_id, totalItems); - } - - return { roles, doRolesExist, totalItems }; + async model(params) { + const useDebounce = + this.retrieveData?.lastPerformed?.args?.[0].search !== params.search; + return this.retrieveData.perform({ ...params, useDebounce }); } + retrieveData = restartableTask( + async ({ search, page, pageSize, useDebounce }) => { + if (useDebounce) { + await timeout(250); + } + + const scope = this.modelFor('scopes.scope'); + const { id: scope_id } = scope; + const filters = { + scope_id: [{ equals: scope_id }], + }; + + let roles; + let totalItems = 0; + let doRolesExist = false; + if (this.can.can('list model', scope, { collection: 'roles' })) { + roles = await this.store.query('role', { + scope_id, + query: { search, filters }, + page, + pageSize, + }); + totalItems = roles.meta?.totalItems; + doRolesExist = await this.getDoRolesExist(scope_id, totalItems); + } + + return { roles, doRolesExist, totalItems }; + }, + ); + /** * Sets doRolesExist to true if there exists any roles. * @param {string} scope_id diff --git a/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js b/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js index c4f858236..462381b71 100644 --- a/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js +++ b/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js @@ -8,6 +8,7 @@ import { action } from '@ember/object'; import { service } from '@ember/service'; import { TrackedObject } from 'tracked-built-ins'; import { TYPE_SCOPE_PROJECT } from 'api/models/scope'; +import { restartableTask, timeout } from 'ember-concurrency'; export default class ScopesScopeRolesRoleManageScopesManageCustomScopesRoute extends Route { // =attributes @@ -37,41 +38,53 @@ export default class ScopesScopeRolesRoleManageScopesManageCustomScopesRoute ext * Loads sub scopes for the current scope. * @returns {Promise<{role: RoleModel, orgScopes: [ScopeModel], projectTotals: object, totalItems: number, totalItemsCount: number}> } */ - async model({ search, page, pageSize }) { - const role = this.modelFor('scopes.scope.roles.role'); - const currentScope = this.modelFor('scopes.scope'); - const { id: scope_id } = currentScope; - const filters = { - scope_id: [{ equals: scope_id }], - }; - - const orgScopes = await this.store.query('scope', { - scope_id, - query: { search, filters }, - page, - pageSize, - recursive: true, - }); - const totalItems = orgScopes.meta?.totalItems; - const totalItemsCount = await this.getTotalItemsCount( - scope_id, - search, - totalItems, - ); - - const projectTotals = await this.getProjectTotals( - role.grantScopeProjectIDs, - ); - - return { - role, - orgScopes, - projectTotals, - totalItems, - totalItemsCount, - }; + async model(params) { + const useDebounce = + this.retrieveData?.lastPerformed?.args?.[0].search !== params.search; + return this.retrieveData.perform({ ...params, useDebounce }); } + retrieveData = restartableTask( + async ({ search, page, pageSize, useDebounce }) => { + if (useDebounce) { + await timeout(250); + } + + const role = this.modelFor('scopes.scope.roles.role'); + const currentScope = this.modelFor('scopes.scope'); + const { id: scope_id } = currentScope; + const filters = { + scope_id: [{ equals: scope_id }], + }; + + const orgScopes = await this.store.query('scope', { + scope_id, + query: { search, filters }, + page, + pageSize, + recursive: true, + }); + const totalItems = orgScopes.meta?.totalItems; + const totalItemsCount = await this.getTotalItemsCount( + scope_id, + search, + totalItems, + ); + + const projectTotals = await this.getProjectTotals( + role.grantScopeProjectIDs, + ); + + return { + role, + orgScopes, + projectTotals, + totalItems, + totalItemsCount, + }; + }, + ); + /** * Creates an object that contains the number of selected projects * and the total number of projects for each org scope. diff --git a/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-org-projects.js b/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-org-projects.js index d36bbaa03..ef2532d45 100644 --- a/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-org-projects.js +++ b/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-org-projects.js @@ -6,6 +6,7 @@ import Route from '@ember/routing/route'; import { action } from '@ember/object'; import { service } from '@ember/service'; +import { restartableTask, timeout } from 'ember-concurrency'; export default class ScopesScopeRolesRoleManageScopesManageOrgProjectsRoute extends Route { // =attributes @@ -35,39 +36,51 @@ export default class ScopesScopeRolesRoleManageScopesManageOrgProjectsRoute exte * Loads projects for current org scope. * @return {Promise<{role: RoleModel, orgScope: ScopeModel, projectScopes: [ScopeModel], totalItems: number, totalItemsCount: number, remainingProjectIDs: [string]}> } */ - async model({ org_id, search, page, pageSize }) { - const role = this.modelFor('scopes.scope.roles.role'); - const orgScope = this.store.peekRecord('scope', org_id); - const filters = { - scope_id: [{ equals: org_id }], - }; - - const projectScopes = await this.store.query('scope', { - scope_id: role.scope.id, - query: { search, filters }, - page, - pageSize, - recursive: true, - }); - const totalItems = projectScopes.meta?.totalItems; - const totalItemsCount = await this.getTotalItemsCount( - org_id, - search, - totalItems, - ); - - const remainingProjectIDs = await this.getRemainingProjects(role, org_id); - - return { - role, - orgScope, - projectScopes, - totalItems, - totalItemsCount, - remainingProjectIDs, - }; + async model(params) { + const useDebounce = + this.retrieveData?.lastPerformed?.args?.[0].search !== params.search; + return this.retrieveData.perform({ ...params, useDebounce }); } + retrieveData = restartableTask( + async ({ org_id, search, page, pageSize, useDebounce }) => { + if (useDebounce) { + await timeout(250); + } + + const role = this.modelFor('scopes.scope.roles.role'); + const orgScope = this.store.peekRecord('scope', org_id); + const filters = { + scope_id: [{ equals: org_id }], + }; + + const projectScopes = await this.store.query('scope', { + scope_id: role.scope.id, + query: { search, filters }, + page, + pageSize, + recursive: true, + }); + const totalItems = projectScopes.meta?.totalItems; + const totalItemsCount = await this.getTotalItemsCount( + org_id, + search, + totalItems, + ); + + const remainingProjectIDs = await this.getRemainingProjects(role, org_id); + + return { + role, + orgScope, + projectScopes, + totalItems, + totalItemsCount, + remainingProjectIDs, + }; + }, + ); + /** * Sets scopesExist to true if there exists any scopes and * sets totalItemsCount to the number of scopes that exist. diff --git a/ui/admin/app/routes/scopes/scope/roles/role/scopes.js b/ui/admin/app/routes/scopes/scope/roles/role/scopes.js index 8412748f6..5df64af03 100644 --- a/ui/admin/app/routes/scopes/scope/roles/role/scopes.js +++ b/ui/admin/app/routes/scopes/scope/roles/role/scopes.js @@ -5,6 +5,7 @@ import Route from '@ember/routing/route'; import { service } from '@ember/service'; +import { restartableTask, timeout } from 'ember-concurrency'; export default class ScopesScopeRolesRoleScopesRoute extends Route { // =services @@ -40,49 +41,61 @@ export default class ScopesScopeRolesRoleScopesRoute extends Route { * Loads grant scopes for current role. * @return {Promise<{role: RoleModel, grantScopes: [ScopeModel], allGrantScopes: [ScopeModel], totalItems: number}> } */ - async model({ search, parentScopes, types, page, pageSize }) { - const role = this.modelFor('scopes.scope.roles.role'); - const filters = { - scope_id: [], - type: [], - id: [], - }; - role.grant_scope_ids.forEach((id) => { - if (id.startsWith('p_') || id.startsWith('o_')) { - filters.id.push({ equals: id }); - } - }); + async model(params) { + const useDebounce = + this.retrieveData?.lastPerformed?.args?.[0].search !== params.search; + return this.retrieveData.perform({ ...params, useDebounce }); + } - let grantScopes = []; - if (!parentScopes.length && !types.length) { - role.grantScopeKeywords.forEach((keyword) => { - if (keyword.includes(search)) { - grantScopes.push({ id: keyword }); + retrieveData = restartableTask( + async ({ search, parentScopes, types, page, pageSize, useDebounce }) => { + if (useDebounce) { + await timeout(250); + } + + const role = this.modelFor('scopes.scope.roles.role'); + const filters = { + scope_id: [], + type: [], + id: [], + }; + role.grant_scope_ids.forEach((id) => { + if (id.startsWith('p_') || id.startsWith('o_')) { + filters.id.push({ equals: id }); } }); - } - let allGrantScopes = []; - let totalItems = grantScopes.length; - if (filters.id.length) { - parentScopes.forEach((id) => filters.scope_id.push({ equals: id })); - types.forEach((type) => filters.type.push({ equals: type })); + let grantScopes = []; + if (!parentScopes.length && !types.length) { + role.grantScopeKeywords.forEach((keyword) => { + if (keyword.includes(search) || !search) { + grantScopes.push({ id: keyword }); + } + }); + } - allGrantScopes = await this.getAllGrantScopes(filters.id); + let allGrantScopes = []; + let totalItems = grantScopes.length; + if (filters.id.length) { + parentScopes.forEach((id) => filters.scope_id.push({ equals: id })); + types.forEach((type) => filters.type.push({ equals: type })); - const queriedScopes = await this.store.query('scope', { - scope_id: role.scope.id, - query: { search, filters }, - page, - pageSize: pageSize - totalItems, - recursive: true, - }); - grantScopes = - page === 1 ? [...grantScopes, ...queriedScopes] : queriedScopes; - totalItems += queriedScopes.meta?.totalItems; - } - return { role, grantScopes, allGrantScopes, totalItems }; - } + allGrantScopes = await this.getAllGrantScopes(filters.id); + + const queriedScopes = await this.store.query('scope', { + scope_id: role.scope.id, + query: { search, filters }, + page, + pageSize: pageSize - totalItems, + recursive: true, + }); + grantScopes = + page === 1 ? [...grantScopes, ...queriedScopes] : queriedScopes; + totalItems += queriedScopes.meta?.totalItems; + } + return { role, grantScopes, allGrantScopes, totalItems }; + }, + ); /** * Get all the grant scopes but only load them once when entering the route. diff --git a/ui/admin/app/routes/scopes/scope/scopes/index.js b/ui/admin/app/routes/scopes/scope/scopes/index.js index 3245dfa08..940d702c2 100644 --- a/ui/admin/app/routes/scopes/scope/scopes/index.js +++ b/ui/admin/app/routes/scopes/scope/scopes/index.js @@ -5,6 +5,7 @@ import Route from '@ember/routing/route'; import { service } from '@ember/service'; +import { restartableTask, timeout } from 'ember-concurrency'; export default class ScopesScopeScopesIndexRoute extends Route { // =attributes @@ -33,30 +34,42 @@ export default class ScopesScopeScopesIndexRoute extends Route { * Loads sub scopes for the current scope. * @returns {Promise<{totalItems: number, currentScope: ScopeModel, subScopes: [ScopeModel], doScopesExist: boolean }> } */ - async model({ search, page, pageSize }) { - const currentScope = this.modelFor('scopes.scope'); - const { id: scope_id } = currentScope; - const filters = { - scope_id: [{ equals: scope_id }], - }; - - const subScopes = await this.store.query('scope', { - scope_id, - query: { search, filters }, - page, - pageSize, - }); - const totalItems = subScopes.meta?.totalItems; - const doScopesExist = await this.getDoScopesExist(scope_id, totalItems); - - return { - currentScope, - subScopes, - doScopesExist, - totalItems, - }; + async model(params) { + const useDebounce = + this.retrieveData?.lastPerformed?.args?.[0].search !== params.search; + return this.retrieveData.perform({ ...params, useDebounce }); } + retrieveData = restartableTask( + async ({ search, page, pageSize, useDebounce }) => { + if (useDebounce) { + await timeout(250); + } + + const currentScope = this.modelFor('scopes.scope'); + const { id: scope_id } = currentScope; + const filters = { + scope_id: [{ equals: scope_id }], + }; + + const subScopes = await this.store.query('scope', { + scope_id, + query: { search, filters }, + page, + pageSize, + }); + const totalItems = subScopes.meta?.totalItems; + const doScopesExist = await this.getDoScopesExist(scope_id, totalItems); + + return { + currentScope, + subScopes, + doScopesExist, + totalItems, + }; + }, + ); + /** * Sets doScopesExist to true if there exists any scopes. * @param {string} scope_id diff --git a/ui/admin/app/routes/scopes/scope/session-recordings/index.js b/ui/admin/app/routes/scopes/scope/session-recordings/index.js index 81bbeb3c7..9f3b39aea 100644 --- a/ui/admin/app/routes/scopes/scope/session-recordings/index.js +++ b/ui/admin/app/routes/scopes/scope/session-recordings/index.js @@ -6,6 +6,7 @@ import Route from '@ember/routing/route'; import { service } from '@ember/service'; import { action } from '@ember/object'; +import { restartableTask, timeout } from 'ember-concurrency'; export default class ScopesScopeSessionRecordingsIndexRoute extends Route { // =services @@ -50,68 +51,89 @@ export default class ScopesScopeSessionRecordingsIndexRoute extends Route { * Load all session recordings. * @return {Promise<{ totalItems: number, sessionRecordings: [SessionRecordingModel], doSessionRecordingsExist: boolean, doStorageBucketsExist: boolean }>} */ - async model({ search, time, users, scopes, targets, page, pageSize }) { - const scope = this.modelFor('scopes.scope'); - const { id: scope_id } = scope; - let sessionRecordings; - let totalItems = 0; - let doSessionRecordingsExist = false; - let doStorageBucketsExist = false; - const filters = { - created_time: [], - 'create_time_values.user.id': [], - 'create_time_values.target.scope.id': [], - 'create_time_values.target.id': [], - }; - if (time) filters.created_time.push({ gte: new Date(time) }); - users.forEach((user) => { - filters['create_time_values.user.id'].push({ equals: user }); - }); - scopes.forEach((scope) => { - filters['create_time_values.target.scope.id'].push({ equals: scope }); - }); - targets.forEach((target) => { - filters['create_time_values.target.id'].push({ equals: target }); - }); - - if ( - this.can.can('list scope', scope, { - collection: 'session-recordings', - }) - ) { - const queryOptions = { - scope_id, - recursive: true, - query: { search, filters }, - page, - pageSize, - }; - - sessionRecordings = await this.store.query( - 'session-recording', - queryOptions, - ); - totalItems = sessionRecordings.meta?.totalItems; - // Query all session recordings for filtering values if entering route for the first time - if (!this.allSessionRecordings) { - await this.getAllSessionRecordings(scope_id); - } - doSessionRecordingsExist = await this.getDoSessionRecordingsExist( - scope_id, - totalItems, - ); - doStorageBucketsExist = await this.getDoStorageBucketsExist(scope_id); - - return { - sessionRecordings, - doSessionRecordingsExist: doSessionRecordingsExist, - allSessionRecordings: this.allSessionRecordings, - totalItems, - doStorageBucketsExist: doStorageBucketsExist, - }; - } + async model(params) { + const useDebounce = + this.retrieveData?.lastPerformed?.args?.[0].search !== params.search; + return this.retrieveData.perform({ ...params, useDebounce }); } + retrieveData = restartableTask( + async ({ + search, + time, + users, + scopes, + targets, + page, + pageSize, + useDebounce, + }) => { + if (useDebounce) { + await timeout(250); + } + + const scope = this.modelFor('scopes.scope'); + const { id: scope_id } = scope; + let sessionRecordings; + let totalItems = 0; + let doSessionRecordingsExist = false; + let doStorageBucketsExist = false; + const filters = { + created_time: [], + 'create_time_values.user.id': [], + 'create_time_values.target.scope.id': [], + 'create_time_values.target.id': [], + }; + if (time) filters.created_time.push({ gte: new Date(time) }); + users.forEach((user) => { + filters['create_time_values.user.id'].push({ equals: user }); + }); + scopes.forEach((scope) => { + filters['create_time_values.target.scope.id'].push({ equals: scope }); + }); + targets.forEach((target) => { + filters['create_time_values.target.id'].push({ equals: target }); + }); + + if ( + this.can.can('list scope', scope, { + collection: 'session-recordings', + }) + ) { + const queryOptions = { + scope_id, + recursive: true, + query: { search, filters }, + page, + pageSize, + }; + + sessionRecordings = await this.store.query( + 'session-recording', + queryOptions, + ); + totalItems = sessionRecordings.meta?.totalItems; + // Query all session recordings for filtering values if entering route for the first time + if (!this.allSessionRecordings) { + await this.getAllSessionRecordings(scope_id); + } + doSessionRecordingsExist = await this.getDoSessionRecordingsExist( + scope_id, + totalItems, + ); + doStorageBucketsExist = await this.getDoStorageBucketsExist(scope_id); + + return { + sessionRecordings, + doSessionRecordingsExist: doSessionRecordingsExist, + allSessionRecordings: this.allSessionRecordings, + totalItems, + doStorageBucketsExist: doStorageBucketsExist, + }; + } + }, + ); + /** * Sets allSessionRecordings to all session recordings for filters * @param {string} scope_id diff --git a/ui/admin/app/routes/scopes/scope/sessions/index.js b/ui/admin/app/routes/scopes/scope/sessions/index.js index 825d449f8..d166bea1f 100644 --- a/ui/admin/app/routes/scopes/scope/sessions/index.js +++ b/ui/admin/app/routes/scopes/scope/sessions/index.js @@ -6,6 +6,7 @@ import Route from '@ember/routing/route'; import { service } from '@ember/service'; import { action } from '@ember/object'; +import { restartableTask, timeout } from 'ember-concurrency'; export default class ScopesScopeSessionsIndexRoute extends Route { // =services @@ -51,57 +52,69 @@ export default class ScopesScopeSessionsIndexRoute extends Route { * an array of objects containing their associated users and targets. * @return {Promise{[{sessions: [SessionModel], allSessions: [SessionModel], associatedUsers: [UserModel], associatedTargets: [TargetModel], totalItems: number}]}} */ - async model({ search, users, targets, status, page, pageSize }) { - const scope = this.modelFor('scopes.scope'); - const { id: scope_id } = scope; - - const filters = { - scope_id: [{ equals: scope_id }], - status: [], - user_id: [], - target_id: [], - }; - users.forEach((user) => { - filters.user_id.push({ equals: user }); - }); - targets.forEach((target) => { - filters.target_id.push({ equals: target }); - }); - status.forEach((item) => { - filters.status.push({ equals: item }); - }); - - const queryOptions = { - scope_id, - include_terminated: true, - query: { search, filters }, - page, - pageSize, - }; - - const sessions = await this.store.query('session', queryOptions); - const totalItems = sessions.meta?.totalItems; - - // Query all sessions, users, and targets for defining filtering values if entering route for the first time - if (!this.allSessions) { - await this.getAllSessions(scope_id); - } - if (this.associatedUsers.length === 0) { - await this.getAssociatedUsers(scope); - } - if (this.associatedTargets.length === 0) { - await this.getAssociatedTargets(scope); - } - - return { - sessions, - allSessions: this.allSessions, - associatedUsers: this.associatedUsers, - associatedTargets: this.associatedTargets, - totalItems, - }; + async model(params) { + const useDebounce = + this.retrieveData?.lastPerformed?.args?.[0].search !== params.search; + return this.retrieveData.perform({ ...params, useDebounce }); } + retrieveData = restartableTask( + async ({ search, users, targets, status, page, pageSize, useDebounce }) => { + if (useDebounce) { + await timeout(250); + } + + const scope = this.modelFor('scopes.scope'); + const { id: scope_id } = scope; + + const filters = { + scope_id: [{ equals: scope_id }], + status: [], + user_id: [], + target_id: [], + }; + users.forEach((user) => { + filters.user_id.push({ equals: user }); + }); + targets.forEach((target) => { + filters.target_id.push({ equals: target }); + }); + status.forEach((item) => { + filters.status.push({ equals: item }); + }); + + const queryOptions = { + scope_id, + include_terminated: true, + query: { search, filters }, + page, + pageSize, + }; + + const sessions = await this.store.query('session', queryOptions); + const totalItems = sessions.meta?.totalItems; + + // Query all sessions, users, and targets for defining filtering values if entering route for the first time + if (!this.allSessions) { + await this.getAllSessions(scope_id); + } + if (this.associatedUsers.length === 0) { + await this.getAssociatedUsers(scope); + } + if (this.associatedTargets.length === 0) { + await this.getAssociatedTargets(scope); + } + + return { + sessions, + allSessions: this.allSessions, + associatedUsers: this.associatedUsers, + associatedTargets: this.associatedTargets, + totalItems, + }; + }, + ); + /** * Get all the sessions but only load them once when entering the route. * @param scope_id diff --git a/ui/admin/app/routes/scopes/scope/targets/index.js b/ui/admin/app/routes/scopes/scope/targets/index.js index d35b80c6e..23fb3e897 100644 --- a/ui/admin/app/routes/scopes/scope/targets/index.js +++ b/ui/admin/app/routes/scopes/scope/targets/index.js @@ -9,6 +9,7 @@ import { STATUS_SESSION_ACTIVE, STATUS_SESSION_PENDING, } from 'api/models/session'; +import { restartableTask, timeout } from 'ember-concurrency'; export default class ScopesScopeTargetsIndexRoute extends Route { // =services @@ -47,52 +48,71 @@ export default class ScopesScopeTargetsIndexRoute extends Route { * active sessions filtering options. * @returns {Promise<{totalItems: number, targets: [TargetModel], doTargetsExist: boolean }> } */ - async model({ search, availableSessions, types, page, pageSize }) { - const scope = this.modelFor('scopes.scope'); - const { id: scope_id } = scope; - - const filters = { - scope_id: [{ equals: scope_id }], - id: { values: [] }, - type: [], - }; - types.forEach((type) => { - filters.type.push({ equals: type }); - }); - - if (this.can.can('list model', scope, { collection: 'sessions' })) { - const sessions = await this.store.query('session', { - scope_id, - query: { - filters: { - scope_id: [{ equals: scope_id }], - status: [ - { equals: STATUS_SESSION_ACTIVE }, - { equals: STATUS_SESSION_PENDING }, - ], - }, - }, - }); - this.addActiveSessionFilters(filters, availableSessions, sessions); - } - - let targets; - let totalItems = 0; - let doTargetsExist = false; - if (this.can.can('list model', scope, { collection: 'targets' })) { - targets = await this.store.query('target', { - scope_id, - query: { search, filters }, - page, - pageSize, - }); - totalItems = targets.meta?.totalItems; - doTargetsExist = await this.getDoTargetsExist(scope_id, totalItems); - } - - return { targets, doTargetsExist, totalItems }; + async model(params) { + const useDebounce = + this.retrieveData?.lastPerformed?.args?.[0].search !== params.search; + return this.retrieveData.perform({ ...params, useDebounce }); } + retrieveData = restartableTask( + async ({ + search, + availableSessions, + types, + page, + pageSize, + useDebounce, + }) => { + if (useDebounce) { + await timeout(250); + } + + const scope = this.modelFor('scopes.scope'); + const { id: scope_id } = scope; + + const filters = { + scope_id: [{ equals: scope_id }], + id: { values: [] }, + type: [], + }; + types.forEach((type) => { + filters.type.push({ equals: type }); + }); + + if (this.can.can('list model', scope, { collection: 'sessions' })) { + const sessions = await this.store.query('session', { + scope_id, + query: { + filters: { + scope_id: [{ equals: scope_id }], + status: [ + { equals: STATUS_SESSION_ACTIVE }, + { equals: STATUS_SESSION_PENDING }, + ], + }, + }, + }); + this.addActiveSessionFilters(filters, availableSessions, sessions); + } + + let targets; + let totalItems = 0; + let doTargetsExist = false; + if (this.can.can('list model', scope, { collection: 'targets' })) { + targets = await this.store.query('target', { + scope_id, + query: { search, filters }, + page, + pageSize, + }); + totalItems = targets.meta?.totalItems; + doTargetsExist = await this.getDoTargetsExist(scope_id, totalItems); + } + + return { targets, doTargetsExist, totalItems }; + }, + ); + /** * Sets doTargetsExist to true if there exists any targets. * @param {string} scope_id diff --git a/ui/admin/app/routes/scopes/scope/users/index.js b/ui/admin/app/routes/scopes/scope/users/index.js index f8797f3d3..4a6f5aedd 100644 --- a/ui/admin/app/routes/scopes/scope/users/index.js +++ b/ui/admin/app/routes/scopes/scope/users/index.js @@ -5,6 +5,7 @@ import Route from '@ember/routing/route'; import { service } from '@ember/service'; +import { restartableTask, timeout } from 'ember-concurrency'; export default class ScopesScopeUsersIndexRoute extends Route { // =attributes @@ -33,31 +34,43 @@ export default class ScopesScopeUsersIndexRoute extends Route { * Loads queried users and the number of users under current scope. * @returns {Promise<{totalItems: number, users: [UserModel], doUsersExist: boolean }> } */ - async model({ search, page, pageSize }) { - const scope = this.modelFor('scopes.scope'); - const { id: scope_id } = scope; - - const filters = { - scope_id: [{ equals: scope_id }], - }; - - let users; - let totalItems = 0; - let doUsersExist = false; - if (this.can.can('list model', scope, { collection: 'users' })) { - users = await this.store.query('user', { - scope_id, - query: { search, filters }, - page, - pageSize, - }); - totalItems = users.meta?.totalItems; - doUsersExist = await this.getDoUsersExist(scope_id, totalItems); - } - - return { users, doUsersExist, totalItems }; + async model(params) { + const useDebounce = + this.retrieveData?.lastPerformed?.args?.[0].search !== params.search; + return this.retrieveData.perform({ ...params, useDebounce }); } + retrieveData = restartableTask( + async ({ search, page, pageSize, useDebounce }) => { + if (useDebounce) { + await timeout(250); + } + + const scope = this.modelFor('scopes.scope'); + const { id: scope_id } = scope; + + const filters = { + scope_id: [{ equals: scope_id }], + }; + + let users; + let totalItems = 0; + let doUsersExist = false; + if (this.can.can('list model', scope, { collection: 'users' })) { + users = await this.store.query('user', { + scope_id, + query: { search, filters }, + page, + pageSize, + }); + totalItems = users.meta?.totalItems; + doUsersExist = await this.getDoUsersExist(scope_id, totalItems); + } + + return { users, doUsersExist, totalItems }; + }, + ); + /** * Sets doUsersExist to true if there exists any users. * @param {string} scope_id