Skip to content

Commit b85afc2

Browse files
committed
add the searchFields option to api-client-core and react
1 parent 1f933b1 commit b85afc2

File tree

11 files changed

+197
-13
lines changed

11 files changed

+197
-13
lines changed

packages/api-client-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@gadgetinc/api-client-core",
3-
"version": "0.15.46",
3+
"version": "0.15.47",
44
"files": [
55
"Readme.md",
66
"dist/**/*"

packages/api-client-core/spec/InternalModelManager.spec.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,33 @@ describe("InternalModelManager", () => {
292292
expect(plan.variables).toEqual({ search: "term" });
293293
});
294294

295+
test("should build a find many query with searchFields", () => {
296+
const plan = internalFindManyQuery("widget", [], { search: "term", searchFields: { name: true, state: false, id: { weight: 10 } } });
297+
expect(plan.query).toMatchInlineSnapshot(`
298+
"query InternalFindManyWidget($search: String, $searchFields: WidgetSearchFields) {
299+
internal {
300+
listWidget(search: $search, searchFields: $searchFields) {
301+
pageInfo {
302+
hasNextPage
303+
hasPreviousPage
304+
startCursor
305+
endCursor
306+
}
307+
edges {
308+
cursor
309+
node
310+
}
311+
}
312+
}
313+
gadgetMeta {
314+
hydrations(modelName: "widget")
315+
}
316+
}"
317+
`);
318+
expectValidGraphQLQuery(plan.query);
319+
expect(plan.variables).toEqual({ search: "term", searchFields: { name: {}, id: { weight: 10 } } });
320+
});
321+
295322
test("should build a find many query with filter", () => {
296323
const plan = internalFindManyQuery("widget", [], { filter: [{ id: { equals: "1" } }] });
297324
expect(plan.query).toMatchInlineSnapshot(`
@@ -460,6 +487,26 @@ describe("InternalModelManager", () => {
460487
expect(plan.variables).toEqual({ first: 1, search: "term" });
461488
});
462489

490+
test("should build a find first query with searchFields", () => {
491+
const plan = internalFindFirstQuery("widget", [], { search: "term", searchFields: { name: true, state: false, id: { weight: 10 } } });
492+
expect(plan.query).toMatchInlineSnapshot(`
493+
"query InternalFindFirstWidget($search: String, $searchFields: WidgetSearchFields, $first: Int) {
494+
internal {
495+
listWidget(search: $search, searchFields: $searchFields, first: $first) {
496+
edges {
497+
node
498+
}
499+
}
500+
}
501+
gadgetMeta {
502+
hydrations(modelName: "widget")
503+
}
504+
}"
505+
`);
506+
expectValidGraphQLQuery(plan.query);
507+
expect(plan.variables).toEqual({ first: 1, search: "term", searchFields: { name: {}, id: { weight: 10 } } });
508+
});
509+
463510
test("should build a find first query with filter", () => {
464511
const plan = internalFindFirstQuery("widget", [], { filter: [{ id: { equals: "1" } }] });
465512

@@ -578,6 +625,40 @@ describe("InternalModelManager", () => {
578625
expectValidGraphQLQuery(plan.query);
579626
});
580627

628+
test("should build a find first query with searchFilter", () => {
629+
const plan = internalFindFirstQuery("widget_model", [], {
630+
search: "term",
631+
searchFields: { name: true, state: false, id: { weight: 10 } },
632+
});
633+
expect(plan).toMatchInlineSnapshot(`
634+
{
635+
"query": "query InternalFindFirstWidgetModel($search: String, $searchFields: WidgetModelSearchFields, $first: Int) {
636+
internal {
637+
listWidgetModel(search: $search, searchFields: $searchFields, first: $first) {
638+
edges {
639+
node
640+
}
641+
}
642+
}
643+
gadgetMeta {
644+
hydrations(modelName: "widget_model")
645+
}
646+
}",
647+
"variables": {
648+
"first": 1,
649+
"search": "term",
650+
"searchFields": {
651+
"id": {
652+
"weight": 10,
653+
},
654+
"name": {},
655+
},
656+
},
657+
}
658+
`);
659+
expectValidGraphQLQuery(plan.query);
660+
});
661+
581662
test("should build a find first query with filter", () => {
582663
const plan = internalFindFirstQuery("widget_model", [], { filter: [{ id: { equals: "1" } }] });
583664
expect(plan).toMatchInlineSnapshot(`

packages/api-client-core/spec/operationBuilders.spec.ts

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,48 @@ describe("operation builders", () => {
224224
`);
225225
});
226226

227+
test("findManyOperation should build a findMany query with searchFields if option provided", () => {
228+
expect(
229+
findManyOperation("widgets", { __typename: true, id: true, state: true }, "widget", {
230+
search: "Search Term",
231+
searchFields: { name: true, state: false, id: { weight: 10 } },
232+
})
233+
).toMatchInlineSnapshot(`
234+
{
235+
"query": "query widgets($after: String, $first: Int, $before: String, $last: Int, $search: String, $searchFields: WidgetSearchFields) {
236+
widgets(after: $after, first: $first, before: $before, last: $last, search: $search, searchFields: $searchFields) {
237+
pageInfo {
238+
hasNextPage
239+
hasPreviousPage
240+
startCursor
241+
endCursor
242+
}
243+
edges {
244+
cursor
245+
node {
246+
__typename
247+
id
248+
state
249+
}
250+
}
251+
}
252+
gadgetMeta {
253+
hydrations(modelName: "widget")
254+
}
255+
}",
256+
"variables": {
257+
"search": "Search Term",
258+
"searchFields": {
259+
"id": {
260+
"weight": 10,
261+
},
262+
"name": {},
263+
},
264+
},
265+
}
266+
`);
267+
});
268+
227269
test("findManyOperation should build a findMany query with sort if option provided", () => {
228270
expect(findManyOperation("widgets", { __typename: true, id: true, state: true }, "widget", { sort: [{ id: "Ascending" }] }))
229271
.toMatchInlineSnapshot(`
@@ -304,15 +346,21 @@ describe("operation builders", () => {
304346
"widgets",
305347
{ __typename: true, id: true, state: true },
306348
"widget",
307-
{ sort: [{ id: "Ascending" }], filter: [{ foo: { equals: "bar" } }] },
349+
{
350+
sort: [{ id: "Ascending" }],
351+
filter: [{ foo: { equals: "bar" } }],
352+
searchFields: { name: true, state: false, id: { weight: 10 } },
353+
search: "Search Term",
354+
},
355+
308356
["outer", "inner"]
309357
)
310358
).toMatchInlineSnapshot(`
311359
{
312-
"query": "query widgets($after: String, $first: Int, $before: String, $last: Int, $sort: [OuterInnerWidgetSort!], $filter: [OuterInnerWidgetFilter!]) {
360+
"query": "query widgets($after: String, $first: Int, $before: String, $last: Int, $sort: [OuterInnerWidgetSort!], $filter: [OuterInnerWidgetFilter!], $search: String, $searchFields: OuterInnerWidgetSearchFields) {
313361
outer {
314362
inner {
315-
widgets(after: $after, first: $first, before: $before, last: $last, sort: $sort, filter: $filter) {
363+
widgets(after: $after, first: $first, before: $before, last: $last, sort: $sort, filter: $filter, search: $search, searchFields: $searchFields) {
316364
pageInfo {
317365
hasNextPage
318366
hasPreviousPage
@@ -342,6 +390,13 @@ describe("operation builders", () => {
342390
},
343391
},
344392
],
393+
"search": "Search Term",
394+
"searchFields": {
395+
"id": {
396+
"weight": 10,
397+
},
398+
"name": {},
399+
},
345400
"sort": [
346401
{
347402
"id": "Ascending",

packages/api-client-core/src/InternalModelManager.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ import {
1717
hydrateRecord,
1818
hydrateRecordArray,
1919
hydrationSelection,
20+
jsSearchFieldsToGqlSearchFields,
2021
namespaceDataPath,
2122
namespacedGraphQLTypeName,
2223
namespacify,
24+
searchableFieldTypeName,
2325
sortTypeName,
2426
} from "./support.js";
2527
import type {
@@ -51,6 +53,12 @@ export const internalFindOneQuery = (apiIdentifier: string, id: string, namespac
5153
const internalFindListVariables = (apiIdentifier: string, namespace: string[], options?: InternalFindListOptions) => {
5254
return {
5355
search: options?.search ? Var({ value: options?.search, type: "String" }) : undefined,
56+
searchFields: options?.searchFields
57+
? Var({
58+
value: jsSearchFieldsToGqlSearchFields(options.searchFields),
59+
type: `${searchableFieldTypeName(apiIdentifier, namespace)}`,
60+
})
61+
: undefined,
5462
sort: options?.sort ? Var({ value: options?.sort, type: `[${sortTypeName(apiIdentifier, namespace)}!]` }) : undefined,
5563
filter: options?.filter ? Var({ value: options?.filter, type: `[${filterTypeName(apiIdentifier, namespace)}!]` }) : undefined,
5664
select: options?.select ? Var({ value: formatInternalSelectVariable(options?.select), type: `[String!]` }) : undefined,

packages/api-client-core/src/operationBuilders.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import {
88
capitalizeIdentifier,
99
filterTypeName,
1010
hydrationSelection,
11+
jsSearchFieldsToGqlSearchFields,
1112
namespacify,
13+
searchableFieldTypeName,
1214
sortTypeName,
1315
} from "./support.js";
1416
import type { ActionFunctionOptions, BaseFindOptions, EnqueueBackgroundActionOptions, FindManyOptions, VariablesOptions } from "./types.js";
@@ -100,6 +102,12 @@ export const findManyOperation = (
100102
sort: options?.sort ? Var({ value: options.sort, type: `[${sortTypeName(modelApiIdentifier, namespace)}!]` }) : undefined,
101103
filter: options?.filter ? Var({ value: options.filter, type: `[${filterTypeName(modelApiIdentifier, namespace)}!]` }) : undefined,
102104
search: options?.search ? Var({ value: options.search, type: "String" }) : undefined,
105+
searchFields: options?.searchFields
106+
? Var({
107+
value: jsSearchFieldsToGqlSearchFields(options.searchFields),
108+
type: `${searchableFieldTypeName(modelApiIdentifier, namespace)}`,
109+
})
110+
: undefined,
103111
},
104112
{
105113
pageInfo: { hasNextPage: true, hasPreviousPage: true, startCursor: true, endCursor: true },

packages/api-client-core/src/support.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import type { OperationResult } from "@urql/core";
22
import { CombinedError } from "@urql/core";
3+
import { isObject } from "lodash-es";
34
import { Call, type FieldSelection as BuilderFieldSelection } from "tiny-graphql-query-compiler";
45
import { DataHydrator } from "./DataHydrator.js";
56
import type { ActionFunctionMetadata, AnyActionFunction } from "./GadgetFunctions.js";
67
import type { RecordShape } from "./GadgetRecord.js";
78
import { GadgetRecord } from "./GadgetRecord.js";
8-
import type { VariablesOptions } from "./types.js";
9+
import type { AnySearchableFieldConfig, SearchableFieldConfig, VariablesOptions } from "./types.js";
910

1011
/**
1112
* Generic type of the state of any record of a Gadget model
@@ -295,6 +296,9 @@ export const sortTypeName = (modelApiIdentifier: string, namespace: string | str
295296
export const filterTypeName = (modelApiIdentifier: string, namespace: string | string[] | null | undefined) =>
296297
`${namespacedGraphQLTypeName(modelApiIdentifier, namespace)}Filter`;
297298

299+
export const searchableFieldTypeName = (modelApiIdentifier: string, namespace: string | string[] | null | undefined) =>
300+
`${namespacedGraphQLTypeName(modelApiIdentifier, namespace)}SearchFields`;
301+
298302
export const getNonUniqueDataError = (modelApiIdentifier: string, fieldName: string, fieldValue: string) =>
299303
new GadgetNonUniqueDataError(
300304
`More than one record found for ${modelApiIdentifier}.${fieldName} = ${fieldValue}. Please confirm your unique validation is not reporting an error.`
@@ -758,3 +762,17 @@ export const formatErrorMessages = (error: Error) => {
758762

759763
return result;
760764
};
765+
766+
export const jsSearchFieldsToGqlSearchFields = (searchFields: AnySearchableFieldConfig) => {
767+
const result: Record<string, SearchableFieldConfig> = {};
768+
769+
for (const [field, config] of Object.entries(searchFields)) {
770+
if (isObject(config)) {
771+
result[field] = config;
772+
} else if (config) {
773+
result[field] = {};
774+
}
775+
}
776+
777+
return result;
778+
};

packages/api-client-core/src/types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,14 @@ export type FilterElement =
602602
**/
603603
export type AnyFilter = FilterElement | FilterElement[];
604604

605+
export type SearchableFieldConfig = {
606+
weight?: number;
607+
};
608+
609+
export type AnySearchableFieldConfig = {
610+
[key: string]: SearchableFieldConfig | boolean | null | undefined;
611+
};
612+
605613
/**
606614
* A list of fields to return from the backend
607615
* Is not specific to any backend model. Look for the backend specific types in the generated API client if you need strong type safety.
@@ -621,6 +629,8 @@ export interface FindManyOptions extends BaseFindOptions {
621629
filter?: AnyFilter | AnyFilter[] | null;
622630
/** Only return records which match this given search string */
623631
search?: string | null;
632+
/** Which fields on a record to search. If not specified, all searchable fields will be searched. */
633+
searchFields?: AnySearchableFieldConfig | null;
624634

625635
/**
626636
* Return records after the given cursor for pagination. Useful in tandem with the `first` count option for pagination.
@@ -684,6 +694,10 @@ export interface InternalFindListOptions {
684694
* Matches the behavior of the Public API `search` option
685695
**/
686696
search?: string | null;
697+
/**
698+
* Which fields on a record to search. If not specified, all searchable fields will be searched.
699+
*/
700+
searchFields?: AnySearchableFieldConfig | null;
687701
/**
688702
* How to sort the returned records
689703
* Matches the format and behavior of the Public API `sort` option

packages/react-bigcommerce/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@gadgetinc/react-bigcommerce",
3-
"version": "0.4.1",
3+
"version": "0.4.2",
44
"files": [
55
"README.md",
66
"dist/**/*"
@@ -27,7 +27,7 @@
2727
"prerelease": "gitpkg publish"
2828
},
2929
"dependencies": {
30-
"@gadgetinc/api-client-core": "^0.15.46"
30+
"@gadgetinc/api-client-core": "^0.15.47"
3131
},
3232
"devDependencies": {
3333
"@gadgetinc/api-client-core": "workspace:*",

packages/react-shopify-app-bridge/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@gadgetinc/react-shopify-app-bridge",
3-
"version": "0.19.1",
3+
"version": "0.19.2",
44
"files": [
55
"README.md",
66
"dist/**/*"
@@ -27,7 +27,7 @@
2727
"prerelease": "gitpkg publish"
2828
},
2929
"dependencies": {
30-
"@gadgetinc/api-client-core": "^0.15.46",
30+
"@gadgetinc/api-client-core": "^0.15.47",
3131
"crypto-js": "^4.2.0",
3232
"tslib": "^2.6.2"
3333
},

packages/react/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@gadgetinc/react",
3-
"version": "0.22.1",
3+
"version": "0.22.2",
44
"files": [
55
"README.md",
66
"dist/**/*",
@@ -50,7 +50,7 @@
5050
},
5151
"dependencies": {
5252
"@0no-co/graphql.web": "^1.0.4",
53-
"@gadgetinc/api-client-core": "^0.15.46",
53+
"@gadgetinc/api-client-core": "^0.15.47 ",
5454
"@hookform/resolvers": "^5.2.1",
5555
"filesize": "^10.1.2",
5656
"pluralize": "^8.0.0",

0 commit comments

Comments
 (0)