Skip to content

Commit

Permalink
[Content | Schema] VQA updates for the one-to-one/many revamp (#3194)
Browse files Browse the repository at this point in the history
  • Loading branch information
finnar-bin authored Feb 11, 2025
1 parent f01caf2 commit f2c9a95
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 186 deletions.
12 changes: 7 additions & 5 deletions cypress/e2e/schema/field.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ const SELECTORS = {
MAX_CHARACTER_LIMIT_INPUT: "MaxCharacterLimitInput",
MIN_CHARACTER_ERROR_MSG: "MinCharacterErrorMsg",
MAX_CHARACTER_ERROR_MSG: "MaxCharacterErrorMsg",
ADD_NEW_RELATED_ITEM: "add-relational-item-button",
CONFIRM_NEW_RELATED_ITEM: "done-selecting-item-button",
ACTIVE_RELATED_ITEM: "active-relational-item",
};

/**
Expand Down Expand Up @@ -341,13 +344,12 @@ describe("Schema: Fields", () => {
// click on the default value checkbox
cy.getBySelector(SELECTORS.DEFAULT_VALUE_CHECKBOX).click();
// enter a default value
cy.getBySelector(SELECTORS.DEFAULT_VALUE_INPUT).click();
cy.getBySelector(SELECTORS.ADD_NEW_RELATED_ITEM).click();
// Select the option
cy.get("[role=listbox] [role=option]").first().click();
cy.get(".MuiDataGrid-row").first().find("input").click();
cy.getBySelector(SELECTORS.CONFIRM_NEW_RELATED_ITEM).click();
// verify that the default value is set
cy.getBySelector(SELECTORS.DEFAULT_VALUE_INPUT)
.find("input")
.should("have.value", "- None -");
cy.getBySelector(SELECTORS.ACTIVE_RELATED_ITEM).should("have.length", 1);
cy.getBySelector(SELECTORS.DEFAULT_VALUE_CHECKBOX).click();

// Click done
Expand Down
192 changes: 18 additions & 174 deletions src/apps/schema/src/app/components/AddFieldModal/DefaultValueInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
Menu,
MenuItem,
Button,
Chip,
ToggleButtonGroup,
ToggleButton,
Select,
Expand All @@ -24,23 +23,13 @@ import {
} from "../../../../../content-editor/src/app/components/Editor/Field/FieldShell";
import KeyboardArrowDownRoundedIcon from "@mui/icons-material/KeyboardArrowDownRounded";
import { FieldTypeMedia } from "../../../../../content-editor/src/app/components/FieldTypeMedia";
import { AppLink } from "@zesty-io/core/AppLink";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faEdit } from "@fortawesome/free-solid-svg-icons";

import { useDispatch, useSelector } from "react-redux";
import { fetchItems, searchItems } from "../../../../../../shell/store/content";
import {
FieldTypeOneToMany,
OneToManyOptions,
} from "../../../../../../shell/components/FieldTypeOneToMany";
import { searchItems } from "../../../../../../shell/store/content";

import { AppState } from "../../../../../../shell/store/types";
import { FieldSettingsOptions } from "../../../../../../shell/services/types";
import { FieldTypeOneToOne } from "../../../../../../shell/components/FieldTypeOneToOne";
import {
resolveRelatedOptions,
sortHTML,
} from "../../../../../content-editor/src/app/components/Editor/Field/Field";
import { fetchFields } from "../../../../../../shell/store/fields";
import { sortHTML } from "../../../../../content-editor/src/app/components/Editor/Field/Field";
import { FieldTypeInternalLink } from "../../../../../../shell/components/FieldTypeInternalLink";
import { LinkOption } from "../../../../../content-editor/src/app/components/Editor/Field/LinkOption";
import { FieldTypeNumber } from "../../../../../../shell/components/FieldTypeNumber";
Expand All @@ -49,6 +38,7 @@ import { FieldTypeDate } from "../../../../../../shell/components/FieldTypeDate"
import { FieldTypeDateTime } from "../../../../../../shell/components/FieldTypeDateTime";
import { FieldTypeColor } from "../../../../../../shell/components/FieldTypeColor";
import { FieldTypeSort } from "../../../../../../shell/components/FieldTypeSort";
import { RelationalFieldBase } from "../../../../../../shell/components/RelationalFieldBase";
import moment from "moment";

type DefaultValueInputProps = {
Expand Down Expand Up @@ -311,170 +301,24 @@ export const DefaultValueInput = ({
</>
);
case "one_to_one":
const onOneToOneOpen = () => {
return dispatch(
fetchItems(relatedModelZUID, {
lang: "en-US",
})
);
};

let oneToOneOptions: OneToManyOptions[] = useMemo(() => {
const filterValidItems = (items: any) => {
// remove items that are only saved in memory
const filteredValidItems = Object.entries<any>(items).filter(
([, value]) => value.web.version
);
// Reshape the array back into an object
let options = Object.fromEntries(filteredValidItems);

return options;
};
const options = filterValidItems(allItems);

return [
{
inputLabel: "- None -",
value: null,
component: "- None -",
},
...resolveRelatedOptions(
allFields,
options,
relatedFieldZUID,
relatedModelZUID,
1,
value
),
];
}, [
Object.keys(allFields).length,
Object.keys(allItems).length,
relatedModelZUID,
relatedFieldZUID,
value,
]);

if (value && !oneToOneOptions.find((opt) => opt.value === value)) {
//the related option is not in the array, we need to insert it
oneToOneOptions.unshift({
value: value as string,
inputLabel: `Selected item not found: ${value}`,
component: (
<span>
<span onClick={(evt) => evt.stopPropagation()}>
<AppLink
style={{
textShadow: "none",
}}
to={`/content/${relatedModelZUID}/${value}`}
>
<FontAwesomeIcon icon={faEdit} />
</AppLink>
</span>
&nbsp;Selected item not found: {value}
</span>
),
});
}

return (
<FieldTypeOneToOne
data-cy="DefaultValueInput"
name={"defaultValue"}
value={
oneToOneOptions?.find((options) => options.value === value) || null
}
onChange={(_, option) => onChange(option.value)}
options={oneToOneOptions}
// @ts-ignore
onOpen={onOneToOneOpen}
startAdornment={
value && (
<AppLink to={`/content/${relatedModelZUID}/${value}`}>
<FontAwesomeIcon icon={faEdit} />
</AppLink>
)
}
endAdornment={value && <em>en-US</em>}
error={error}
<RelationalFieldBase
name="defaultValue"
value={!!value ? String(value) : null}
relatedModelZUID={relatedModelZUID}
relatedFieldZUID={relatedFieldZUID}
onChange={(value) => onChange(value)}
/>
);
case "one_to_many":
const oneToManyOptions: OneToManyOptions[] = useMemo(() => {
const filterValidItems = (items: any) => {
// remove items that are only saved in memory
const filteredValidItems = Object.entries<any>(items).filter(
([, value]) => value.web.version
);
// Reshape the array back into an object
let options = Object.fromEntries(filteredValidItems);

return options;
};
const options = filterValidItems(allItems);

return resolveRelatedOptions(
allFields,
options,
relatedFieldZUID,
relatedModelZUID,
1,
value
);
}, [
Object.keys(allFields).length,
Object.keys(allItems).length,
relatedModelZUID,
relatedFieldZUID,
1,
value,
]);
const onOneToManyOpen = useCallback(() => {
return Promise.all([
dispatch(fetchFields(relatedModelZUID)),
dispatch(
fetchItems(relatedModelZUID, {
lang: "en-US",
})
),
]);
}, [relatedModelZUID]);

return (
<FieldTypeOneToMany
data-cy="DefaultValueInput"
name={"defaultValue"}
value={
(value &&
(value as string)
?.split(",")
?.map(
(value: any) =>
oneToManyOptions?.find(
(options) => options.value === value
) || { value, inputLabel: value, component: value }
)) ||
[]
}
onChange={(_, options: OneToManyOptions[]) => {
const selectedOptions = options?.length
? options.map((option) => option.value).join(",")
: null;
onChange(selectedOptions);
}}
options={oneToManyOptions}
onOpen={onOneToManyOpen}
renderTags={(tags, getTagProps) =>
tags.map((tag, index) => (
<Chip
size="small"
label={tag.component}
{...getTagProps({ index })}
/>
))
}
error={error}
<RelationalFieldBase
name="defaultValue"
multiselect
value={!!value ? String(value) : null}
relatedModelZUID={relatedModelZUID}
relatedFieldZUID={relatedFieldZUID}
onChange={(value) => onChange(value)}
/>
);
case "link":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,12 +338,18 @@ export const FieldForm = ({
let newErrorsObj: Errors = {};

Object.keys(formData).map((inputName) => {
if (
inputName === "defaultValue" &&
isDefaultValueEnabled &&
(formData.defaultValue === "" || formData.defaultValue === null)
) {
newErrorsObj[inputName] = "Required Field. Please enter a value.";
if (inputName === "defaultValue" && isDefaultValueEnabled) {
if (formData.defaultValue === "" || formData.defaultValue === null) {
newErrorsObj[inputName] = "Required Field. Please enter a value.";
} else if (
type === "one_to_many" &&
(formData.defaultValue as string)?.length > 255
) {
// Value is stored as string in DB with max char limit of 255.
// This means users can only add up to 12 item zuids
newErrorsObj[inputName] =
"Cannot save field. Please reduce the total number of items selected.";
}
}

if (type === "text" || type === "textarea") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ export const FieldSelectorDialog = ({
sx: {
width: 800,
maxWidth: 800,
minHeight: 680,
maxHeight: "min(1240px, calc(100% - 64px))",
},
}}
Expand Down
2 changes: 1 addition & 1 deletion src/shell/components/RelationalFieldBase/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export const RelationalFieldBase = ({
sx={{
mt: 1,
}}
disabled={isLoadingModelData || isLoadingModelFields}
disabled={isLoadingModelData || isLoadingModelFields || !modelData}
>
Add Existing {modelData?.label}
</Button>
Expand Down

0 comments on commit f2c9a95

Please sign in to comment.