Skip to content

Commit caf2158

Browse files
committed
numeric embedded fields
1 parent 9423067 commit caf2158

8 files changed

Lines changed: 833 additions & 2 deletions

packages/visual-editor/src/editor/EmbeddedFieldStringInput.test.tsx

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,3 +361,125 @@ describe("EmbeddedFieldStringInput", () => {
361361
expect(toast.warning).not.toHaveBeenCalled();
362362
});
363363
});
364+
365+
describe("numeric embedded entity fields", () => {
366+
it("offers root and linked numeric fields and inserts a linked decimal path", () => {
367+
render(
368+
<TemplatePropsContext.Provider
369+
value={{
370+
document: {
371+
yearEstablished: 2006,
372+
rating: 4.5,
373+
c_linkedProducts: [
374+
{
375+
price: {
376+
value: 19.95,
377+
},
378+
},
379+
],
380+
},
381+
}}
382+
>
383+
<TemplateMetadataContext.Provider value={generateTemplateMetadata()}>
384+
<EntityFieldsContext.Provider
385+
value={{
386+
fields: [
387+
{
388+
name: "yearEstablished",
389+
displayName: "Year Established",
390+
definition: {
391+
name: "yearEstablished",
392+
typeRegistryId: "type.integer",
393+
type: {
394+
numberType: "NUMBER_TYPE_INT",
395+
},
396+
},
397+
},
398+
{
399+
name: "rating",
400+
displayName: "Rating",
401+
definition: {
402+
name: "rating",
403+
typeRegistryId: "type.float",
404+
type: {
405+
numberType: "NUMBER_TYPE_FLOAT",
406+
},
407+
},
408+
},
409+
{
410+
name: "c_linkedProducts",
411+
displayName: "Linked Products",
412+
definition: {
413+
name: "c_linkedProducts",
414+
typeRegistryId: "type.entity_reference",
415+
type: {
416+
documentType: "DOCUMENT_TYPE_ENTITY",
417+
},
418+
isList: true,
419+
},
420+
children: {
421+
fields: [
422+
{
423+
name: "price",
424+
displayName: "Price",
425+
definition: {
426+
name: "price",
427+
typeRegistryId: "type.price",
428+
type: {
429+
objectType: "OBJECT_TYPE_DEFAULT",
430+
},
431+
},
432+
children: {
433+
fields: [
434+
{
435+
name: "value",
436+
displayName: "Value",
437+
definition: {
438+
name: "value",
439+
typeRegistryId: "type.decimal",
440+
type: {
441+
stringType: "STRING_TYPE_DECIMAL",
442+
},
443+
},
444+
},
445+
],
446+
},
447+
},
448+
],
449+
},
450+
},
451+
],
452+
displayNames: {
453+
yearEstablished: "Year Established",
454+
rating: "Rating",
455+
c_linkedProducts: "Linked Products",
456+
"c_linkedProducts.price": "Linked Products > Price",
457+
"c_linkedProducts.price.value":
458+
"Linked Products > Price > Value",
459+
},
460+
}}
461+
>
462+
<EmbeddedFieldStringInputFromEntity
463+
filter={{ types: ["type.string"] }}
464+
onChange={() => undefined}
465+
showFieldSelector={true}
466+
value=""
467+
/>
468+
</EntityFieldsContext.Provider>
469+
</TemplateMetadataContext.Provider>
470+
</TemplatePropsContext.Provider>
471+
);
472+
473+
fireEvent.click(screen.getByLabelText("Add entity field"));
474+
475+
expect(screen.getByText("Year Established")).toBeDefined();
476+
expect(screen.getByText("Rating")).toBeDefined();
477+
expect(screen.getByText("Linked Products > Price > Value")).toBeDefined();
478+
479+
fireEvent.click(screen.getByText("Linked Products > Price > Value"));
480+
481+
expect(
482+
screen.getByDisplayValue("[[c_linkedProducts.price.value]]")
483+
).toBeDefined();
484+
});
485+
});

packages/visual-editor/src/editor/EmbeddedFieldStringInput.tsx

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import React from "react";
2-
import { RenderEntityFieldFilter } from "../internal/utils/getFilteredEntityFields.ts";
2+
import {
3+
type EntityFieldTypes,
4+
type RenderEntityFieldFilter,
5+
} from "../internal/utils/getFilteredEntityFields.ts";
36
import {
47
getEntityFieldScopeDisplayName,
58
getScopedEntityFieldDisplayName,
@@ -40,6 +43,31 @@ export type EmbeddedStringOption = {
4043
value: string;
4144
};
4245

46+
const NUMERIC_EMBEDDED_FIELD_TYPES: EntityFieldTypes[] = [
47+
"type.decimal",
48+
"type.float",
49+
"type.integer",
50+
];
51+
52+
/**
53+
* Numeric fields are valid in embedded text because their values are converted
54+
* to strings. Avoid passings numbers directly to components expecting strings.
55+
*/
56+
const getEmbeddedStringFilter = <T extends Record<string, any>>(
57+
filter: RenderEntityFieldFilter<T>
58+
): RenderEntityFieldFilter<T> => {
59+
if (!filter.types?.includes("type.string")) {
60+
return filter;
61+
}
62+
63+
return {
64+
...filter,
65+
types: Array.from(
66+
new Set([...filter.types, ...NUMERIC_EMBEDDED_FIELD_TYPES])
67+
),
68+
};
69+
};
70+
4371
/**
4472
* Resolves the object scope that embedded linked-field previews should read
4573
* from.
@@ -115,7 +143,7 @@ export const EmbeddedFieldStringInputFromEntity = <
115143
const entityFieldOptions = React.useMemo(() => {
116144
const filteredEntityFields = getFieldsForSelector(
117145
entityFields,
118-
filter,
146+
getEmbeddedStringFilter(filter),
119147
streamDocument,
120148
sourceField || undefined
121149
);

packages/visual-editor/src/internal/utils/getFilteredEntityFields.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1679,3 +1679,70 @@ const mockEntityFields: YextSchemaField[] = [
16791679
const mockStreamFields: StreamFields = {
16801680
fields: mockEntityFields,
16811681
};
1682+
1683+
const numericStreamFields: StreamFields = {
1684+
fields: [
1685+
{
1686+
name: "name",
1687+
definition: {
1688+
name: "name",
1689+
typeRegistryId: "type.string",
1690+
type: {
1691+
stringType: "STRING_TYPE_DEFAULT",
1692+
},
1693+
},
1694+
},
1695+
{
1696+
name: "price",
1697+
definition: {
1698+
name: "price",
1699+
typeRegistryId: "type.decimal",
1700+
type: {
1701+
stringType: "STRING_TYPE_DECIMAL",
1702+
},
1703+
},
1704+
},
1705+
{
1706+
name: "rating",
1707+
definition: {
1708+
name: "rating",
1709+
typeRegistryId: "type.float",
1710+
type: {
1711+
numberType: "NUMBER_TYPE_FLOAT",
1712+
},
1713+
},
1714+
},
1715+
{
1716+
name: "yearEstablished",
1717+
definition: {
1718+
name: "yearEstablished",
1719+
typeRegistryId: "type.integer",
1720+
type: {
1721+
numberType: "NUMBER_TYPE_INT",
1722+
},
1723+
},
1724+
},
1725+
],
1726+
};
1727+
1728+
describe("numeric entity field filtering", () => {
1729+
test("recognizes decimal, float, and integer schema types", () => {
1730+
const fields = getFilteredEntityFields(numericStreamFields, {
1731+
types: ["type.decimal", "type.float", "type.integer"],
1732+
});
1733+
1734+
expect(fields.map((field) => field.name)).toEqual([
1735+
"price",
1736+
"rating",
1737+
"yearEstablished",
1738+
]);
1739+
});
1740+
1741+
test("keeps generic string filters string-only", () => {
1742+
const fields = getFilteredEntityFields(numericStreamFields, {
1743+
types: ["type.string"],
1744+
});
1745+
1746+
expect(fields.map((field) => field.name)).toEqual(["name"]);
1747+
});
1748+
});

packages/visual-editor/src/internal/utils/getFilteredEntityFields.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ export type RenderEntityFieldFilter<T extends Record<string, any>> =
3838

3939
export type EntityFieldTypes =
4040
| "type.string"
41+
| "type.decimal"
42+
| "type.float"
43+
| "type.integer"
4144
| "type.image"
4245
| "type.hours"
4346
| "type.address"

packages/visual-editor/src/utils/resolveYextEntityField.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { assert, describe, it } from "vitest";
22
import {
3+
resolveEmbeddedFieldsInString,
34
resolveField,
45
resolveYextEntityField,
56
} from "./resolveYextEntityField.ts";
@@ -349,3 +350,41 @@ describe("resolveYextEntityField with embedded fields", () => {
349350
);
350351
});
351352
});
353+
354+
describe("numeric embedded field resolution", () => {
355+
it("resolves root numeric primitives as unformatted text", () => {
356+
const resolved = resolveEmbeddedFieldsInString(
357+
"[[zero]] [[negative]] [[integer]] [[decimal]]",
358+
{
359+
zero: 0,
360+
negative: -12,
361+
integer: 42,
362+
decimal: 19.95,
363+
}
364+
);
365+
366+
assert.equal(resolved, "0 -12 42 19.95");
367+
});
368+
369+
it("resolves a decimal through the first linked entity", () => {
370+
const resolved = resolveEmbeddedFieldsInString(
371+
"[[c_linkedProducts.price.value]]",
372+
{
373+
c_linkedProducts: [
374+
{
375+
price: {
376+
value: 19.95,
377+
},
378+
},
379+
{
380+
price: {
381+
value: 29.95,
382+
},
383+
},
384+
],
385+
}
386+
);
387+
388+
assert.equal(resolved, "19.95");
389+
});
390+
});

packages/visual-editor/src/utils/resolveYextEntityField.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ export const resolveEmbeddedFieldsInString = (
8686
return "";
8787
}
8888

89+
if (typeof resolvedValue === "number") {
90+
return resolvedValue.toString();
91+
}
92+
8993
// If the resolved value is an object, stringify it.
9094
if (typeof resolvedValue === "object") {
9195
return JSON.stringify(resolvedValue);

starter/localData/dev-location-stream__en__cbafb9cd1c3e63d9814e236ba9181377.json

Lines changed: 49 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)