Skip to content

Commit

Permalink
fix(api-headless-cms): gracefully fall back to applicable fields (#4203)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pavel910 authored Jul 23, 2024
1 parent 1c02a0b commit fb1a87e
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 283 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ describe("content model test", () => {
});
});

test("error when assigning titleFieldId on non existing field", async () => {
test("when assigning `titleFieldId` to a non-existing field, fall back to the first applicable field", async () => {
const { createContentModelMutation, updateContentModelMutation } =
useGraphQLHandler(manageHandlerOpts);
const [createResponse] = await createContentModelMutation({
Expand Down Expand Up @@ -649,15 +649,10 @@ describe("content model test", () => {
expect(response).toMatchObject({
data: {
updateContentModel: {
data: null,
error: {
code: "VALIDATION_ERROR",
message: `Field selected for the title field does not exist in the model.`,
data: {
fieldId: "nonExistingTitleFieldId",
fields: expect.any(Array)
}
}
data: {
titleFieldId: "field1"
},
error: null
}
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,20 +135,13 @@ describe("multiple values in field", () => {
}
});

expect(response).toEqual({
expect(response).toMatchObject({
data: {
updateContentModel: {
data: null,
error: {
code: "ENTRY_TITLE_FIELD_TYPE",
message:
"Fields that accept multiple values cannot be used as the entry title.",
data: {
storageId: expect.stringMatching("text@"),
fieldId: "availableSizes",
type: "text"
}
}
data: {
titleFieldId: "title"
},
error: null
}
}
});
Expand Down
124 changes: 10 additions & 114 deletions packages/api-headless-cms/__tests__/contentAPI/predefinedValues.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe("predefined values", () => {

const setupBugModel = async (
contentModelGroup: CmsGroup,
overrides: Record<string, any> = {}
overrides: (model: CmsModel) => Partial<CmsModel> = () => ({})
): Promise<CmsModel> => {
const model = models.find(m => m.modelId === "bug");
if (!model) {
Expand All @@ -53,15 +53,15 @@ describe("predefined values", () => {
data: {
fields: model.fields,
layout: model.layout,
...overrides
...(overrides ? overrides(model) : {})
}
});
return update.data.updateContentModel.data;
};

test("should create an entry with predefined values selected", async () => {
const contentModelGroup = await setupContentModelGroup();
await setupBugModel(contentModelGroup, {});
await setupBugModel(contentModelGroup);

const { createBug } = useBugManageHandler({
...manageOpts
Expand Down Expand Up @@ -111,7 +111,7 @@ describe("predefined values", () => {

test("should fail creating an entry with wrong predefined text value selected", async () => {
const contentModelGroup = await setupContentModelGroup();
await setupBugModel(contentModelGroup, {});
await setupBugModel(contentModelGroup);

const { createBug } = useBugManageHandler({
...manageOpts
Expand Down Expand Up @@ -150,7 +150,7 @@ describe("predefined values", () => {

test("should fail creating an entry with wrong predefined number value selected", async () => {
const contentModelGroup = await setupContentModelGroup();
await setupBugModel(contentModelGroup, {});
await setupBugModel(contentModelGroup);

const { createBug } = useBugManageHandler({
...manageOpts
Expand Down Expand Up @@ -189,7 +189,7 @@ describe("predefined values", () => {

test("should fail creating an entry with wrong predefined number and text values selected", async () => {
const contentModelGroup = await setupContentModelGroup();
await setupBugModel(contentModelGroup, {});
await setupBugModel(contentModelGroup);

const { createBug } = useBugManageHandler({
...manageOpts
Expand Down Expand Up @@ -233,115 +233,11 @@ describe("predefined values", () => {
});
});

test("title should be a selected predefined text value label", async () => {
const contentModelGroup = await setupContentModelGroup();
await setupBugModel(contentModelGroup, {
titleFieldId: "bugType"
});

const { createBug } = useBugManageHandler({
...manageOpts
});

const [response] = await createBug({
data: {
name: "A hard debuggable bug",
bugType: "critical",
bugValue: 2,
bugFixed: 3
}
});

expect(response).toEqual({
data: {
createBug: {
data: {
id: expect.any(String),
createdOn: expect.stringMatching(/^20/),
modifiedOn: null,
savedOn: expect.stringMatching(/^20/),
createdBy: {
id: "id-12345678",
displayName: "John Doe",
type: "admin"
},
lastPublishedOn: null,
firstPublishedOn: null,
meta: {
locked: false,
modelId: "bug",
status: "draft",
title: "Critical bug!",
version: 1
},
name: "A hard debuggable bug",
bugType: "critical",
bugValue: 2,
bugFixed: 3
},
error: null
}
}
});
});

test("title should be a selected predefined number value label", async () => {
const contentModelGroup = await setupContentModelGroup();
await setupBugModel(contentModelGroup, {
titleFieldId: "bugValue"
});

const { createBug } = useBugManageHandler({
...manageOpts
});

const [response] = await createBug({
data: {
name: "A hard debuggable bug",
bugType: "critical",
bugValue: 3,
bugFixed: 3
}
});

expect(response).toEqual({
data: {
createBug: {
data: {
id: expect.any(String),
createdOn: expect.stringMatching(/^20/),
modifiedOn: null,
savedOn: expect.stringMatching(/^20/),
createdBy: {
id: "id-12345678",
displayName: "John Doe",
type: "admin"
},
lastPublishedOn: null,
firstPublishedOn: null,
meta: {
locked: false,
modelId: "bug",
status: "draft",
title: "High bug value",
version: 1
},
name: "A hard debuggable bug",
bugType: "critical",
bugValue: 3,
bugFixed: 3
},
error: null
}
}
});
});

it("should be able to create an entry with default bug type value", async () => {
const contentModelGroup = await setupContentModelGroup();
const bugModel = await setupBugModel(contentModelGroup, {
const bugModel = await setupBugModel(contentModelGroup, () => ({
titleFieldId: "bugValue"
});
}));

const { createBug } = useBugManageHandler({
...manageOpts
Expand Down Expand Up @@ -377,7 +273,7 @@ describe("predefined values", () => {
locked: false,
modelId: "bug",
status: "draft",
title: "High bug value",
title: "A hard debuggable bug - none",
version: 1
},
name: "A hard debuggable bug - none",
Expand Down Expand Up @@ -456,7 +352,7 @@ describe("predefined values", () => {
locked: false,
modelId: "bug",
status: "draft",
title: "High bug value",
title: "A hard debuggable bug - undefined",
version: 1
},
name: "A hard debuggable bug - undefined",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,29 @@
import { CmsModelField } from "~/types";
import { getBaseFieldType } from "~/utils/getBaseFieldType";
import WebinyError from "@webiny/error";
import { getApplicableFieldById } from "./getApplicableFieldById";

const isFieldApplicable = (field: CmsModelField) => {
return getBaseFieldType(field) === "long-text" && !field.multipleValues;
};

/**
* Try finding the requested field, and return its `fieldId`.
* If not defined, or not applicable, fall back to the first applicable field.
*/
export const getContentModelDescriptionFieldId = (
fields: CmsModelField[],
descriptionFieldId?: string | null
): string | null | undefined => {
/**
* If there are no fields defined, we will just set as null.
*/
) => {
if (fields.length === 0) {
return null;
}
/**
* If description field is not defined, let us find possible one.
*/
if (!descriptionFieldId) {
const descriptionField = fields.find(field => {
return getBaseFieldType(field) === "long-text" && !field.multipleValues;
});
return descriptionField?.fieldId || null;
}
const target = fields.find(
field => field.fieldId === descriptionFieldId && getBaseFieldType(field) === "long-text"
);
if (!target) {
throw new WebinyError(
`Field selected for the description field does not exist in the model.`,
"VALIDATION_ERROR",
{
fieldId: descriptionFieldId,
fields
}
);
}
if (target.multipleValues) {
throw new WebinyError(
`Fields that accept multiple values cannot be used as the entry description.`,
"ENTRY_TITLE_FIELD_TYPE",
{
storageId: target.storageId,
fieldId: target.fieldId,
type: target.type
}
);

const target = getApplicableFieldById(fields, descriptionFieldId, isFieldApplicable);

if (target) {
return target.fieldId;
}

return target.fieldId;
const descriptionField = fields.find(isFieldApplicable);
return descriptionField ? descriptionField.fieldId : null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { CmsModelField } from "~/types";

export const getApplicableFieldById = (
fields: CmsModelField[],
id: string | null | undefined,
isApplicable: (field: CmsModelField) => boolean
) => {
if (!id) {
return undefined;
}

return fields.find(field => field.fieldId === id && isApplicable(field));
};
Original file line number Diff line number Diff line change
@@ -1,57 +1,27 @@
import { CmsModelField } from "~/types";
import { getBaseFieldType } from "~/utils/getBaseFieldType";
import WebinyError from "@webiny/error";
import { getApplicableFieldById } from "./getApplicableFieldById";

const isFieldApplicable = (field: CmsModelField) => {
return Boolean(
getBaseFieldType(field) === "file" && !field.multipleValues && field.settings?.imagesOnly
);
};

export const getContentModelImageFieldId = (
fields: CmsModelField[],
imageFieldId?: string | null
): string | null | undefined => {
/**
* If there are no fields defined, we will just set as null.
*/
) => {
if (fields.length === 0) {
return null;
}
/**
* If image field is not defined, let us find possible one.
*/
if (!imageFieldId) {
const imageField = fields.find(field => {
return (
getBaseFieldType(field) === "file" &&
!field.multipleValues &&
field.settings?.imagesOnly
);
});
return imageField?.fieldId || null;
}
const target = fields.find(
field =>
field.fieldId === imageFieldId &&
getBaseFieldType(field) === "file" &&
field.settings?.imagesOnly
);
if (!target) {
throw new WebinyError(
`Field selected for the image field does not exist in the model.`,
"VALIDATION_ERROR",
{
fieldId: imageFieldId,
fields
}
);
}
if (target.multipleValues) {
throw new WebinyError(
`Fields that accept multiple values cannot be used as the entry image.`,
"ENTRY_TITLE_FIELD_TYPE",
{
storageId: target.storageId,
fieldId: target.fieldId,
type: target.type
}
);

const target = getApplicableFieldById(fields, imageFieldId, isFieldApplicable);

if (target) {
return target.fieldId;
}

return target.fieldId;
const imageField = fields.find(isFieldApplicable);
return imageField ? imageField.fieldId : null;
};
Loading

0 comments on commit fb1a87e

Please sign in to comment.