Skip to content

Commit d93b575

Browse files
authored
Merge pull request #429 from internxt/PB-6649-advanced-search-filters
[PB-6649]: feat/add advanced search filters to global search
2 parents 5cfd9a7 + 29a35d7 commit d93b575

5 files changed

Lines changed: 136 additions & 22 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@internxt/sdk",
33
"author": "Internxt <hello@internxt.com>",
4-
"version": "1.19.0",
4+
"version": "1.20.0",
55
"description": "An sdk for interacting with Internxt's services",
66
"repository": {
77
"type": "git",

src/drive/storage/index.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
FileVersion,
3232
FolderAncestor,
3333
FolderAncestorWorkspace,
34+
GlobalSearchOptions,
3435
FolderMeta,
3536
FolderStatsResponse,
3637
FolderTreeResponse,
@@ -660,23 +661,27 @@ export class Storage {
660661
*
661662
* @param {string} search - The name of the item.
662663
* @param {string} workspaceId - The ID of the workspace (optional).
663-
* @param {number} offset - The position of the first item to return (optional).
664+
* @param {number | GlobalSearchOptions} offsetOrOptions - The position of the first item to return, or an options
665+
* object (optional) with the offset and the search filters: `type` (file categories, combined with OR),
666+
* `minSize`/`maxSize` (bytes, files only) and `modifiedAfter`/`modifiedBefore` (ISO 8601 dates). Filters are
667+
* combined with AND.
664668
* @returns {[Promise<SearchResultData>, RequestCanceler]} An array containing a promise to get the API response and a function to cancel the request.
665669
*/
666670
public getGlobalSearchItems(
667671
search: string,
668672
workspaceId?: string,
669-
offset?: number,
673+
offsetOrOptions?: number | GlobalSearchOptions,
670674
): [Promise<SearchResultData>, RequestCanceler] {
671-
const query = new URLSearchParams();
672-
if (offset !== undefined) query.set('offset', String(offset));
675+
const options: GlobalSearchOptions =
676+
typeof offsetOrOptions === 'number' ? { offset: offsetOrOptions } : (offsetOrOptions ?? {});
673677

674678
const { promise, requestCanceler } = workspaceId
675-
? this.client.getCancellable<SearchResultData>(
676-
`workspaces/${workspaceId}/fuzzy/${search}?${query}`,
679+
? this.client.postCancellable<SearchResultData>(
680+
`workspaces/${workspaceId}/fuzzy/${search}`,
681+
options,
677682
this.headers(),
678683
)
679-
: this.client.getCancellable<SearchResultData>(`fuzzy/${search}?${query}`, this.headers());
684+
: this.client.postCancellable<SearchResultData>(`fuzzy/${search}`, options, this.headers());
680685

681686
return [promise, requestCanceler];
682687
}

src/drive/storage/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,8 @@ export interface SearchResultData {
363363
data: [SearchResult];
364364
}
365365

366+
export type GlobalSearchOptions = paths['/fuzzy/{search}']['post']['requestBody']['content']['application/json'];
367+
366368
export interface FolderAncestor {
367369
bucket: null | string;
368370
createdAt: string;

src/schema.ts

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2305,10 +2305,10 @@ export interface paths {
23052305
path?: never;
23062306
cookie?: never;
23072307
};
2308-
/** Search by name inside workspace */
2309-
get: operations['WorkspacesController_searchWorkspace'];
2308+
get?: never;
23102309
put?: never;
2311-
post?: never;
2310+
/** Search by name inside workspace */
2311+
post: operations['WorkspacesController_searchWorkspace'];
23122312
delete?: never;
23132313
options?: never;
23142314
head?: never;
@@ -2376,10 +2376,10 @@ export interface paths {
23762376
path?: never;
23772377
cookie?: never;
23782378
};
2379-
/** Search for items from a part of the name */
2380-
get: operations['FuzzySearchController_fuzzySearch'];
2379+
get?: never;
23812380
put?: never;
2382-
post?: never;
2381+
/** Search for items from a part of the name */
2382+
post: operations['FuzzySearchController_fuzzySearch'];
23832383
delete?: never;
23842384
options?: never;
23852385
head?: never;
@@ -4798,6 +4798,42 @@ export interface components {
47984798
*/
47994799
phoneNumber: Record<string, never>;
48004800
};
4801+
FuzzySearchQueryDto: {
4802+
/**
4803+
* @description Offset for pagination
4804+
* @example 0
4805+
*/
4806+
offset?: number;
4807+
/**
4808+
* @description File extensions to filter by, or the reserved value "folder" to include folders (a single string is also accepted)
4809+
* @example [
4810+
* "jpg",
4811+
* "pdf",
4812+
* "folder"
4813+
* ]
4814+
*/
4815+
type?: string[];
4816+
/**
4817+
* @description Minimum file size in bytes (folders are excluded)
4818+
* @example 5242880
4819+
*/
4820+
minSize?: number;
4821+
/**
4822+
* @description Maximum file size in bytes (folders are excluded)
4823+
* @example 1073741824
4824+
*/
4825+
maxSize?: number;
4826+
/**
4827+
* @description Filter items modified after this date
4828+
* @example 2026-01-01T00:00:00.000Z
4829+
*/
4830+
modifiedAfter?: string;
4831+
/**
4832+
* @description Filter items modified before this date
4833+
* @example 2026-06-30T23:59:59.999Z
4834+
*/
4835+
modifiedBefore?: string;
4836+
};
48014837
FuzzySearchResult: {
48024838
id: string;
48034839
itemId: string;
@@ -8946,17 +8982,19 @@ export interface operations {
89468982
};
89478983
WorkspacesController_searchWorkspace: {
89488984
parameters: {
8949-
query: {
8950-
offset: number;
8951-
};
8985+
query?: never;
89528986
header?: never;
89538987
path: {
89548988
search: string;
89558989
workspaceId: string;
89568990
};
89578991
cookie?: never;
89588992
};
8959-
requestBody?: never;
8993+
requestBody: {
8994+
content: {
8995+
'application/json': components['schemas']['FuzzySearchQueryDto'];
8996+
};
8997+
};
89608998
responses: {
89618999
/** @description Search results */
89629000
200: {
@@ -9032,16 +9070,18 @@ export interface operations {
90329070
};
90339071
FuzzySearchController_fuzzySearch: {
90349072
parameters: {
9035-
query: {
9036-
offset: number;
9037-
};
9073+
query?: never;
90389074
header?: never;
90399075
path: {
90409076
search: string;
90419077
};
90429078
cookie?: never;
90439079
};
9044-
requestBody?: never;
9080+
requestBody: {
9081+
content: {
9082+
'application/json': components['schemas']['FuzzySearchQueryDto'];
9083+
};
9084+
};
90459085
responses: {
90469086
/** @description Elements found */
90479087
200: {

test/drive/storage/index.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
FolderAncestorWorkspace,
1111
MoveFolderPayload,
1212
MoveFolderResponse,
13+
SearchResultData,
1314
UpdateFilePayload,
1415
} from '../../../src/drive/storage/types';
1516
import { ApiSecurity, AppDetails } from '../../../src/shared';
@@ -1027,6 +1028,72 @@ describe('# storage service tests', () => {
10271028
});
10281029
});
10291030
});
1031+
1032+
describe('-> global search', () => {
1033+
const emptySearchResponse: SearchResultData = { data: [] as unknown as SearchResultData['data'] };
1034+
1035+
it('Should post to the personal fuzzy endpoint with an empty body, when no options are passed', async () => {
1036+
const { client, headers } = clientAndHeaders({});
1037+
const postCancellableStub = vi.spyOn(HttpClient.prototype, 'postCancellable').mockReturnValue({
1038+
promise: Promise.resolve(emptySearchResponse),
1039+
requestCanceler: { cancel: () => null },
1040+
});
1041+
1042+
const [promise] = client.getGlobalSearchItems('report');
1043+
await promise;
1044+
1045+
expect(postCancellableStub).toHaveBeenCalledWith('fuzzy/report', {}, headers);
1046+
});
1047+
1048+
it('Should post to the workspace fuzzy endpoint, when a workspaceId is passed', async () => {
1049+
const workspaceId = 'workspace-id';
1050+
const { client, headers } = clientAndHeaders({});
1051+
const postCancellableStub = vi.spyOn(HttpClient.prototype, 'postCancellable').mockReturnValue({
1052+
promise: Promise.resolve(emptySearchResponse),
1053+
requestCanceler: { cancel: () => null },
1054+
});
1055+
1056+
const [promise] = client.getGlobalSearchItems('report', workspaceId);
1057+
await promise;
1058+
1059+
expect(postCancellableStub).toHaveBeenCalledWith(`workspaces/${workspaceId}/fuzzy/report`, {}, headers);
1060+
});
1061+
1062+
it('Should keep supporting a numeric offset as third argument', async () => {
1063+
const { client, headers } = clientAndHeaders({});
1064+
const postCancellableStub = vi.spyOn(HttpClient.prototype, 'postCancellable').mockReturnValue({
1065+
promise: Promise.resolve(emptySearchResponse),
1066+
requestCanceler: { cancel: () => null },
1067+
});
1068+
1069+
const [promise] = client.getGlobalSearchItems('report', undefined, 10);
1070+
await promise;
1071+
1072+
expect(postCancellableStub).toHaveBeenCalledWith('fuzzy/report', { offset: 10 }, headers);
1073+
});
1074+
1075+
it('Should send all search filters in the request body, when options are passed', async () => {
1076+
const { client, headers } = clientAndHeaders({});
1077+
const postCancellableStub = vi.spyOn(HttpClient.prototype, 'postCancellable').mockReturnValue({
1078+
promise: Promise.resolve(emptySearchResponse),
1079+
requestCanceler: { cancel: () => null },
1080+
});
1081+
1082+
const filters = {
1083+
offset: 0,
1084+
type: ['pdf', 'image'],
1085+
minSize: 1024,
1086+
maxSize: 5242880,
1087+
modifiedAfter: '2026-01-01T00:00:00.000Z',
1088+
modifiedBefore: '2026-06-30T23:59:59.999Z',
1089+
};
1090+
1091+
const [promise] = client.getGlobalSearchItems('report', undefined, filters);
1092+
await promise;
1093+
1094+
expect(postCancellableStub).toHaveBeenCalledWith('fuzzy/report', filters, headers);
1095+
});
1096+
});
10301097
});
10311098

10321099
function clientAndHeaders({

0 commit comments

Comments
 (0)