Skip to content

Commit e5b0d8d

Browse files
authored
Merge pull request #177 from zendesk/victor.piolin/filter-records-custom-ojbects
Implement the filtering on custom object records
2 parents 15264b1 + 2fa2ee5 commit e5b0d8d

5 files changed

Lines changed: 130 additions & 38 deletions

File tree

__tests__/services/custom-objects.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,36 @@ describe("CustomObjectService", () => {
350350
expect(requestMock).toHaveBeenCalledTimes(1);
351351
});
352352

353+
it("should call Zendesk API with filter correctly", async () => {
354+
requestMock.mockResolvedValueOnce({
355+
custom_object_records: [customObjectRecord],
356+
meta: {
357+
has_more: false
358+
}
359+
});
360+
361+
const filter = {
362+
filter: {
363+
"$and": [
364+
{
365+
"custom_object_fields.color": {
366+
"$eq": "Red"
367+
}
368+
}
369+
]
370+
}
371+
};
372+
await service.filterRecords("foo", filter);
373+
374+
expect(requestMock).toHaveBeenNthCalledWith(1, {
375+
url: `/api/v2/custom_objects/foo/records/search`,
376+
type: "POST",
377+
contentType: "application/json",
378+
data: JSON.stringify(filter)
379+
});
380+
expect(requestMock).toHaveBeenCalledTimes(1);
381+
});
382+
353383
it("should keep sort threw all pages ", async () => {
354384
requestMock
355385
.mockResolvedValueOnce({

diff.txt

Lines changed: 0 additions & 17 deletions
This file was deleted.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@zendesk/zaf-toolbox",
3-
"version": "0.10.3",
3+
"version": "0.11.0",
44
"description": "A toolbox for ZAF application built with 🩷 by Zendesk Labs",
55
"main": "lib/src/index.js",
66
"types": "lib/src/index.d.ts",

src/models/custom-objects.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -111,17 +111,7 @@ export enum ListCutomObjectRecordsSortingOptions {
111111
MINUS_UPDATED_AT = "-updated_at"
112112
}
113113

114-
export interface IListFilter {
115-
filter?: {
116-
/**
117-
* Comma separated list of external ids of records
118-
*/
119-
external_ids?: string;
120-
/**
121-
* Comma separated list of records ids
122-
*/
123-
ids?: string;
124-
};
114+
interface IPaginationAndSortCursor {
125115
page?: {
126116
/**
127117
* A pagination cursor that tells the endpoint which page to start on. Note: page[before] and page[after] can't be used together in the same request.
@@ -141,7 +131,30 @@ export interface IListFilter {
141131
*/
142132
sort?: ListCutomObjectRecordsSortingOptions;
143133
}
144-
134+
export interface IListFilter extends IPaginationAndSortCursor {
135+
filter?: {
136+
/**
137+
* Comma separated list of external ids of records
138+
*/
139+
external_ids?: string;
140+
/**
141+
* Comma separated list of records ids
142+
*/
143+
ids?: string;
144+
};
145+
}
146+
type ISearchFilterComparaison = Record<
147+
string,
148+
{
149+
"$eq"?: string | number | boolean;
150+
}
151+
>;
152+
export interface ISearchFilterCustomObjectRecords extends IPaginationAndSortCursor {
153+
filter?: {
154+
"$or"?: ISearchFilterComparaison[];
155+
"$and"?: ISearchFilterComparaison[];
156+
};
157+
}
145158
export interface ISearchCustomObjectRecordsFilter extends IListFilter {
146159
query: string;
147160
}

src/services/custom-object-service.ts

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import {
1414
ICustomObjectRecordField,
1515
ISearchCustomObjectRecordsFilter,
1616
IBulkJobResponse,
17-
IBulkJobBody
17+
IBulkJobBody,
18+
ISearchFilterCustomObjectRecords
1819
} from "@models/index";
1920
import { Client } from "@zendesk/sell-zaf-app-toolbox";
2021

@@ -235,6 +236,56 @@ export class CustomObjectService {
235236
} as ISearchCustomObjectRecordsFilter);
236237
}
237238

239+
/**
240+
* Filter custom object records using advanced search criteria
241+
*
242+
* This method performs a filtered search of custom object records using POST request with complex filter conditions.
243+
* Filters can contain individual comparison objects or arrays of comparison objects within logical namespaces ($and, $or).
244+
*
245+
* @param key - The custom object key identifier
246+
* @param filter - The search filter object containing comparison conditions
247+
* @param filter.filter - A JSON object with the following properties:
248+
* - ATTRIBUTE: A comparison object specifying an attribute value condition
249+
* - $and: Array of conjunctive filter objects (logical AND)
250+
* - $or: Array of disjunctive filter objects (logical OR)
251+
*
252+
* @returns Promise that resolves to an array of custom object records matching the filter criteria
253+
*
254+
* @example
255+
* ```typescript
256+
* // Simple attribute filter
257+
* const records = await service.filterRecords('my_object', {
258+
* filter: {
259+
* "custom_object_fields.field_key": { "$eq": "value" }
260+
* }
261+
* });
262+
*
263+
* // Using logical OR
264+
* const records = await service.filterRecords('my_object', {
265+
* filter: {
266+
* "$or": [
267+
* { "custom_object_fields.field_key": { "$eq": "value" } },
268+
* { "external_id": { "$eq": "Record123" } }
269+
* ]
270+
* }
271+
* });
272+
* ```
273+
*
274+
* @remarks
275+
* - Custom fields must be namespaced with `custom_object_fields.` (e.g., `custom_object_fields.field_key`)
276+
* - Standard fields (created_at, updated_at, created_by_user, updated_by_user, name, external_id) require no namespace
277+
* - Supported operators vary by the value's data type
278+
* - This method uses pagination internally to fetch all matching records
279+
*
280+
* @see {@link https://developer.zendesk.com/api-reference/custom-data/custom-objects/custom_object_records/#filtered-search-of-custom-object-records | Zendesk API Documentation}
281+
*/
282+
public async filterRecords<T extends ICustomObjectRecordField>(
283+
key: string,
284+
filter: ISearchFilterCustomObjectRecords
285+
): Promise<ICustomObjectRecord<T>[]> {
286+
return this.fetchAllPaginatedRecords<T>(`/api/v2/custom_objects/${key}/records/search`, filter, "POST");
287+
}
288+
238289
/**
239290
* Bulk create or update custom object records
240291
* This endpoint allows you to create or update multiple custom object records in a single request.
@@ -263,25 +314,40 @@ export class CustomObjectService {
263314
*/
264315
private async fetchAllPaginatedRecords<T extends ICustomObjectRecordField>(
265316
url: string,
266-
initialData: IListFilter
317+
initialData: IListFilter | ISearchFilterCustomObjectRecords,
318+
method: "GET" | "POST" | "PATCH" | "DELETE" = "GET"
267319
): Promise<ICustomObjectRecord<T>[]> {
268320
let hasMore = true;
269-
let data = initialData;
321+
let d = initialData;
270322
let objects: ICustomObjectRecord<T>[] = [];
271323

272324
do {
273-
const response = await this.client.request<any, IListCustomObjectRecordsResponse<T>>({
325+
let options: {
326+
url: string;
327+
type: "GET" | "POST" | "PATCH" | "DELETE";
328+
contentType: string;
329+
data?: unknown;
330+
} = {
274331
url,
275-
type: "GET",
332+
type: method,
276333
contentType: CONTENT_TYPE,
277-
data
278-
});
334+
data: d
335+
};
336+
337+
if (method === "POST") {
338+
options = {
339+
...options,
340+
data: JSON.stringify(d)
341+
};
342+
}
343+
344+
const response = await this.client.request<any, IListCustomObjectRecordsResponse<T>>(options);
279345

280346
objects = [...objects, ...response.custom_object_records];
281347

282348
hasMore = response.meta.has_more;
283349
if (hasMore) {
284-
data = {
350+
d = {
285351
page: {
286352
after: response.meta.after_cursor
287353
},

0 commit comments

Comments
 (0)