From 3f11019ef0ecc8ed8ea26eabd2f1edc568b19d7c Mon Sep 17 00:00:00 2001 From: Zhihe Li Date: Thu, 30 Nov 2023 14:09:00 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Add=20pagination=20suppo?= =?UTF-8?q?rt=20to=20client=20daemon=20handler=20(#2016)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-11743 --- .../addon/handlers/client-daemon-handler.js | 45 ++++++++++++++++--- .../scopes/scope/projects/targets/index.js | 4 +- .../scopes/scope/projects/targets/index.js | 7 +-- .../scopes/scope/projects/targets/index.hbs | 6 +-- 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/addons/api/addon/handlers/client-daemon-handler.js b/addons/api/addon/handlers/client-daemon-handler.js index 43ff35de6..51a0ca1b6 100644 --- a/addons/api/addon/handlers/client-daemon-handler.js +++ b/addons/api/addon/handlers/client-daemon-handler.js @@ -1,5 +1,6 @@ import { pluralize } from 'ember-inflector'; import { generateMQLExpression } from '../utils/mqlQuery'; +import ArrayProxy from '@ember/array/proxy'; /** * Not all types are yet supported by the client daemon so we'll @@ -21,7 +22,7 @@ const ClientDaemonHandler = { switch (context.request.op) { case 'query': { const { store, data } = context.request; - const { type, query } = data; + const { type, query, options: { pushToStore = true } = {} } = data; const isClientDaemonRunning = await this.ipc.invoke( 'isClientDaemonRunning', ); @@ -36,7 +37,7 @@ const ClientDaemonHandler = { // TODO: Remove usages of recursive from callers and scope_id since // all calls to daemon are recursive, scope_id can be part of search query instead // eslint-disable-next-line no-unused-vars - let { recursive, scope_id, ...remainingQuery } = query; + let { recursive, scope_id, page, pageSize, ...remainingQuery } = query; let searchQuery = ''; if (remainingQuery.query) { const { search, filters } = remainingQuery.query; @@ -63,7 +64,7 @@ const ClientDaemonHandler = { const [results] = Object.values( await this.ipc.invoke('searchClientDaemon', remainingQuery), ); - const payload = { items: results ?? [] }; + const payload = { items: paginateResults(results, page, pageSize) }; const schema = store.modelFor(type); const serializer = store.serializerFor(type); @@ -75,9 +76,18 @@ const ClientDaemonHandler = { 'query', ); - // TODO: Add some pagination + // Return the raw data if we don't push to the store. + let records = normalizedPayload.data; + if (pushToStore) { + records = store.push(normalizedPayload); + } - return store.push(normalizedPayload); + // Use a proxy array to store the metadata so the store's `query` + // array requirement is satisfied. + return ArrayProxy.create({ + content: records, + meta: { totalItems: results?.length ?? 0 }, + }); } default: return next(context.request); @@ -85,4 +95,29 @@ const ClientDaemonHandler = { }, }; +/** + * Takes an array and the current page and pagesize to calculate the correct + * number of results to return to the caller. + * + * @param array + * @param page + * @param pageSize + * @returns {[*]} + */ +const paginateResults = (array, page, pageSize) => { + const length = array?.length; + if (!array || length === 0) { + return []; + } + if (!page || !pageSize) { + return array; + } + + const offset = (page - 1) * pageSize; + const start = Math.min(length - 1, offset); + const end = Math.min(length, offset + pageSize); + + return array.slice(start, end); +}; + export default ClientDaemonHandler; diff --git a/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js b/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js index f09d900ff..e9eb2313d 100644 --- a/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js +++ b/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js @@ -33,7 +33,7 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle * @returns {boolean} */ get noResults() { - return this.model.length === 0 && this.search; + return this.model.targets.length === 0 && this.search; } /** @@ -41,7 +41,7 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle * @returns {boolean} */ get noTargets() { - return this.model.length === 0 && !this.search; + return this.model.targets.length === 0 && !this.search; } /** diff --git a/ui/desktop/app/routes/scopes/scope/projects/targets/index.js b/ui/desktop/app/routes/scopes/scope/projects/targets/index.js index 2a8018b6e..f3e70524f 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/targets/index.js +++ b/ui/desktop/app/routes/scopes/scope/projects/targets/index.js @@ -35,9 +35,9 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route { } /** - * Loads all targets under current scope. + * Loads all targets and the number of targets under current scope. * - * @return {Promise{[TargetModel]}} + * @returns {Promise<{totalItems: number, targets: [TargetModel]}>} */ async model({ search = '' }) { // TODO: Filter targets by scope we're in manually @@ -45,12 +45,13 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route { const queryOptions = { query: { search } }; let targets = await this.store.query('target', queryOptions); + const totalItems = targets.meta?.totalItems; // Filter out targets to which users do not have the connect ability targets = targets.filter((target) => this.can.can('connect target', target), ); - return targets; + return { targets: targets, totalItems }; } } 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 2d116446b..ff9a06d43 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs @@ -14,7 +14,7 @@ - {{#if (or @model this.search)}} + {{#if (or @model.targets this.search)}}
- {{#if @model}} + {{#if @model.targets}}