* feat: 🎸 Add pagination component to targets * test: 💍 Fix tests and add helper for client daemon search * refactor: 💡 Update the returns from client daemon ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-11905
133 lines
4.1 KiB
JavaScript
133 lines
4.1 KiB
JavaScript
import { inject as service } from '@ember/service';
|
|
import { getOwner, setOwner } from '@ember/application';
|
|
import { pluralize } from 'ember-inflector';
|
|
import { generateMQLExpression } from '../utils/mqlQuery';
|
|
|
|
/**
|
|
* Not all types are yet supported by the client daemon so we'll
|
|
* just whitelist the ones we need to start with. Keys are the resource
|
|
* types and the values are the fields that are searchable by the client.
|
|
* @type {{session: string[], target: string[]}}
|
|
*/
|
|
const supportedTypes = {
|
|
target: ['id', 'name', 'description', 'address', 'scope_id'],
|
|
session: ['id', 'type', 'status', 'endpoint', 'scope_id', 'target_id'],
|
|
};
|
|
|
|
/**
|
|
* 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);
|
|
};
|
|
|
|
/**
|
|
* Handler to sit in front of the API request layer
|
|
* so we can request from the daemon first
|
|
*/
|
|
export default class ClientDaemonHandler {
|
|
@service session;
|
|
@service ipc;
|
|
|
|
constructor(context) {
|
|
setOwner(this, getOwner(context));
|
|
}
|
|
|
|
async request(context, next) {
|
|
switch (context.request.op) {
|
|
case 'query': {
|
|
const { store, data } = context.request;
|
|
const { type, query, options: { pushToStore = true } = {} } = data;
|
|
const isClientDaemonRunning = await this.ipc.invoke(
|
|
'isClientDaemonRunning',
|
|
);
|
|
|
|
if (
|
|
!Object.keys(supportedTypes).includes(type) ||
|
|
!isClientDaemonRunning
|
|
) {
|
|
return next(context.request);
|
|
}
|
|
|
|
// 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, page, pageSize, ...remainingQuery } = query;
|
|
let searchQuery = '';
|
|
if (remainingQuery.query) {
|
|
const { search, filters } = remainingQuery.query;
|
|
|
|
searchQuery = generateMQLExpression({
|
|
search: {
|
|
text: search,
|
|
fields: supportedTypes[type],
|
|
},
|
|
filters,
|
|
});
|
|
}
|
|
const auth_token_id = this.session.data?.authenticated?.id;
|
|
remainingQuery = {
|
|
...remainingQuery,
|
|
query: searchQuery,
|
|
auth_token_id,
|
|
resource: pluralize(type),
|
|
};
|
|
|
|
// Currently returns with a singular top level field with resource name
|
|
// e.g. { targets: [...] } or { sessions: [..] }
|
|
// So this just unwraps to the array, or undefined
|
|
const [results] = Object.values(
|
|
await this.ipc.invoke('searchClientDaemon', remainingQuery),
|
|
);
|
|
const payload = { items: paginateResults(results, page, pageSize) };
|
|
|
|
const schema = store.modelFor(type);
|
|
const serializer = store.serializerFor(type);
|
|
const normalizedPayload = serializer.normalizeResponse(
|
|
store,
|
|
schema,
|
|
payload,
|
|
null,
|
|
'query',
|
|
);
|
|
|
|
// Return the raw data if we don't push to the store.
|
|
let records = normalizedPayload.data.map((record) => ({
|
|
...record.attributes,
|
|
id: record.id,
|
|
}));
|
|
if (pushToStore) {
|
|
records = store.push(normalizedPayload);
|
|
}
|
|
|
|
// Set a meta property on the array to store the total items of the results.
|
|
// This isn't conventional but is better than returning an ArrayProxy
|
|
// or EmberArray since the ember store query method asserts it has to be an array
|
|
// so we can't just return an object.
|
|
records.meta = { totalItems: results?.length ?? 0 };
|
|
return records;
|
|
}
|
|
default:
|
|
return next(context.request);
|
|
}
|
|
}
|
|
}
|