diff --git a/src/adapters/task.ts b/src/adapters/task.ts index c4a4155..5b1439b 100644 --- a/src/adapters/task.ts +++ b/src/adapters/task.ts @@ -1,7 +1,8 @@ -import type { RoutingOptions } from '../utils/router'; -import type { GenericScope, Address } from '../utils/constants'; +import type { RoutingOptions, Page } from '../utils/router'; +import type { GenericScope, GenericSearchOptions, Address } from '../utils/constants'; import Router from '../utils/router'; +import { parseFilterInput } from '../utils/filter-parser'; export enum RETRY_POLICY { DO_NOTHING = 'DO_NOTHING', // If the task fails, do nothing (this is the default) @@ -326,3 +327,56 @@ export async function getTaskIn< ) .then(({ body }) => body); } + + +/** + * Queries for tasks + * Base URL: GET `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/task/search` + * + * Requires facilitator (or higher) privileges. Filterable/sortable fields include + * `task.taskKey`, `task.name`, `task.status`, `task.scopeBoundary`, `task.scopeKey`, + * `task.userKey`, `task.groupName`, `task.episodeName`, `task.nextExecution`, + * `task.failSafeExecution`, and `task.created`. + * + * @example + * import { taskAdapter } from 'epicenter-libs'; + * const page = await taskAdapter.query({ + * filter: [ + * 'task.scopeKey=0000017dd3bf540e5ada5b1e058f08f20461', // tasks scoped to this group + * 'task.status=INITIALIZED', // that have not yet fired + * ], + * sort: ['-task.created'], // newest first + * max: 10, // page should only include the first 10 items + * }); + * + * @param searchOptions Search options for the query + * @param [searchOptions.filter] Filters for searching + * @param [searchOptions.sort] Sorting criteria + * @param [searchOptions.first] The starting index of the page returned + * @param [searchOptions.max] The number of entries per page + * @param [optionals] Optional arguments; pass network call options overrides here. + * @returns promise that resolves to a page of tasks + */ +export async function query< + B extends object = TaskPayloadBody, + H extends object = TaskPayloadHeaders, +>( + searchOptions: GenericSearchOptions, + optionals: RoutingOptions = {}, +): Promise>> { + const { filter, sort = [], first, max } = searchOptions; + + const searchParams = { + filter: parseFilterInput(filter), + sort: sort.join(';') || undefined, + first, max, + }; + + return await new Router() + .withSearchParams(searchParams) + .get('/task/search', { + paginated: true, + ...optionals, + }) + .then(({ body }) => body); +} diff --git a/tests/task.spec.js b/tests/task.spec.js index bb96bfb..b712c48 100644 --- a/tests/task.spec.js +++ b/tests/task.spec.js @@ -269,6 +269,58 @@ describe('taskAdapter', () => { testedMethods.add('getTaskIn'); }); + describe('taskAdapter.query', () => { + const OPTIONS = { + filter: [ + 'task.scopeKey=0000017dd3bf540e5ada5b1e058f08f20461', + 'task.status=INITIALIZED', + ], + sort: ['-task.created'], + first: 0, + max: 10, + }; + + it('Should do a GET', async () => { + await taskAdapter.query(OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('GET'); + }); + + it('Should have authorization', async () => { + await taskAdapter.query(OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the task search URL', async () => { + await taskAdapter.query(OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const url = req.url.split('?')[0]; + expect(url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/task/search`); + }); + + it('Should support generic URL options', async () => { + await taskAdapter.query(OPTIONS, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const url = req.url.split('?')[0]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/task/search`); + }); + + it('Should pass in query options as a part of the search parameters (query string)', async () => { + await taskAdapter.query(OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const search = req.url.split('?')[1]; + const searchParams = new URLSearchParams(search); + expect(searchParams.get('filter')).toBe(OPTIONS.filter.join(';')); + expect(searchParams.get('sort')).toBe(OPTIONS.sort.join(';')); + expect(searchParams.get('first')).toBe(OPTIONS.first.toString()); + expect(searchParams.get('max')).toBe(OPTIONS.max.toString()); + }); + + testedMethods.add('query'); + }); + it('Should not have any untested methods', () => { const actualMethods = getFunctionKeys(taskAdapter); expect(actualMethods).toEqual(testedMethods);