Fix cannot clear attribute value (#2047)
* Always send updated values of attributes * Send only changed product attributes to API * Add change to changelog * Add tests for preparing upload attribute values
This commit is contained in:
parent
f123df7c74
commit
707c6b94a7
9 changed files with 409 additions and 22 deletions
|
@ -4,6 +4,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
- Added links instead of imperative navigation with onClick - #1969 by @taniotanio7
|
- Added links instead of imperative navigation with onClick - #1969 by @taniotanio7
|
||||||
|
- Fixed clearing attribute values - #2047 by @witoszekdev
|
||||||
|
|
||||||
## 3.1
|
## 3.1
|
||||||
### PREVIEW FEATURES
|
### PREVIEW FEATURES
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
import { createAttributeMultiChangeHandler } from "@saleor/attributes/utils/handlers";
|
import {
|
||||||
import { AttributeInputData } from "@saleor/components/Attributes";
|
createAttributeMultiChangeHandler,
|
||||||
import { AttributeInputTypeEnum } from "@saleor/graphql";
|
prepareAttributesInput
|
||||||
|
} from "@saleor/attributes/utils/handlers";
|
||||||
|
import {
|
||||||
|
AttributeInput,
|
||||||
|
AttributeInputData
|
||||||
|
} from "@saleor/components/Attributes";
|
||||||
|
import {
|
||||||
|
AttributeInputTypeEnum,
|
||||||
|
AttributeValueDetailsFragment
|
||||||
|
} from "@saleor/graphql";
|
||||||
import { FormsetData } from "@saleor/hooks/useFormset";
|
import { FormsetData } from "@saleor/hooks/useFormset";
|
||||||
|
|
||||||
const attributes: FormsetData<AttributeInputData, string[]> = [
|
const multipleValueAttributes: FormsetData<AttributeInputData, string[]> = [
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
inputType: AttributeInputTypeEnum.DROPDOWN,
|
inputType: AttributeInputTypeEnum.DROPDOWN,
|
||||||
|
@ -108,13 +117,76 @@ const attributes: FormsetData<AttributeInputData, string[]> = [
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
describe("Multiple select", () => {
|
const ATTR_ID = "123" as const;
|
||||||
|
|
||||||
|
interface CreateAttribute {
|
||||||
|
inputType: AttributeInputTypeEnum;
|
||||||
|
initialValue?: AttributeValueDetailsFragment[];
|
||||||
|
availableValues?: AttributeValueDetailsFragment[];
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createAttribute = ({
|
||||||
|
inputType,
|
||||||
|
value
|
||||||
|
}: CreateAttribute): AttributeInput => ({
|
||||||
|
data: {
|
||||||
|
entityType: null,
|
||||||
|
inputType,
|
||||||
|
isRequired: false,
|
||||||
|
// those values don't matter
|
||||||
|
selectedValues: [],
|
||||||
|
values: [],
|
||||||
|
unit: null
|
||||||
|
},
|
||||||
|
id: ATTR_ID,
|
||||||
|
label: "MyAttribute",
|
||||||
|
value: value !== null ? [value] : []
|
||||||
|
});
|
||||||
|
|
||||||
|
const createSelectAttribute = (value: string) =>
|
||||||
|
createAttribute({
|
||||||
|
inputType: AttributeInputTypeEnum.DROPDOWN,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
|
||||||
|
const createReferenceAttribute = (value: string) =>
|
||||||
|
createAttribute({
|
||||||
|
inputType: AttributeInputTypeEnum.REFERENCE,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
|
||||||
|
const createBooleanAttribute = (value: string) =>
|
||||||
|
createAttribute({
|
||||||
|
inputType: AttributeInputTypeEnum.BOOLEAN,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
|
||||||
|
const createRichTextAttribute = (value: string) =>
|
||||||
|
createAttribute({ inputType: AttributeInputTypeEnum.RICH_TEXT, value });
|
||||||
|
|
||||||
|
const createDateAttribute = (value: string) =>
|
||||||
|
createAttribute({ inputType: AttributeInputTypeEnum.DATE, value });
|
||||||
|
|
||||||
|
const createDateTimeAttribute = (value: string) =>
|
||||||
|
createAttribute({ inputType: AttributeInputTypeEnum.DATE_TIME, value });
|
||||||
|
|
||||||
|
const createSwatchAttribute = (value: string) =>
|
||||||
|
createAttribute({ inputType: AttributeInputTypeEnum.SWATCH, value });
|
||||||
|
|
||||||
|
const createNumericAttribute = (value: string) =>
|
||||||
|
createAttribute({ inputType: AttributeInputTypeEnum.NUMERIC, value });
|
||||||
|
|
||||||
|
const createFileAttribute = (value: string) =>
|
||||||
|
createAttribute({ inputType: AttributeInputTypeEnum.FILE, value });
|
||||||
|
|
||||||
|
describe("Multiple select change handler", () => {
|
||||||
it("is able to select value", () => {
|
it("is able to select value", () => {
|
||||||
const change = jest.fn();
|
const change = jest.fn();
|
||||||
const trigger = jest.fn();
|
const trigger = jest.fn();
|
||||||
const handler = createAttributeMultiChangeHandler(
|
const handler = createAttributeMultiChangeHandler(
|
||||||
change,
|
change,
|
||||||
attributes,
|
multipleValueAttributes,
|
||||||
trigger
|
trigger
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -133,7 +205,7 @@ describe("Multiple select", () => {
|
||||||
const trigger = jest.fn();
|
const trigger = jest.fn();
|
||||||
const handler = createAttributeMultiChangeHandler(
|
const handler = createAttributeMultiChangeHandler(
|
||||||
change,
|
change,
|
||||||
attributes,
|
multipleValueAttributes,
|
||||||
trigger
|
trigger
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -150,7 +222,7 @@ describe("Multiple select", () => {
|
||||||
const trigger = jest.fn();
|
const trigger = jest.fn();
|
||||||
const handler = createAttributeMultiChangeHandler(
|
const handler = createAttributeMultiChangeHandler(
|
||||||
change,
|
change,
|
||||||
attributes,
|
multipleValueAttributes,
|
||||||
trigger
|
trigger
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -164,3 +236,280 @@ describe("Multiple select", () => {
|
||||||
expect(trigger).toHaveBeenCalledTimes(1);
|
expect(trigger).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Sending only changed attributes", () => {
|
||||||
|
// null in expected = attribute not present in output
|
||||||
|
describe("works with reference attributes", () => {
|
||||||
|
test.each`
|
||||||
|
newAttr | oldAttr | expected
|
||||||
|
${null} | ${null} | ${null}
|
||||||
|
${"my value"} | ${"my value"} | ${null}
|
||||||
|
${"my value"} | ${null} | ${["my value"]}
|
||||||
|
${null} | ${"my value"} | ${[]}
|
||||||
|
`(
|
||||||
|
"$oldAttr -> $newAttr returns $expected",
|
||||||
|
({ newAttr, oldAttr, expected }) => {
|
||||||
|
const attribute = createReferenceAttribute(newAttr);
|
||||||
|
const prevAttribute = createReferenceAttribute(oldAttr);
|
||||||
|
|
||||||
|
const result = prepareAttributesInput({
|
||||||
|
attributes: [attribute],
|
||||||
|
prevAttributes: [prevAttribute],
|
||||||
|
updatedFileAttributes: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedResult =
|
||||||
|
expected !== null ? [{ id: ATTR_ID, references: expected }] : [];
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe("works with select attributes", () => {
|
||||||
|
test.each`
|
||||||
|
newAttr | oldAttr | expected
|
||||||
|
${null} | ${null} | ${null}
|
||||||
|
${"my value"} | ${"my value"} | ${null}
|
||||||
|
${"my value"} | ${null} | ${["my value"]}
|
||||||
|
${null} | ${"my value"} | ${[]}
|
||||||
|
`(
|
||||||
|
"$oldAttr -> $newAttr returns $expected",
|
||||||
|
({ newAttr, oldAttr, expected }) => {
|
||||||
|
const attribute = createSelectAttribute(newAttr);
|
||||||
|
const prevAttribute = createSelectAttribute(oldAttr);
|
||||||
|
|
||||||
|
const result = prepareAttributesInput({
|
||||||
|
attributes: [attribute],
|
||||||
|
prevAttributes: [prevAttribute],
|
||||||
|
updatedFileAttributes: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedResult =
|
||||||
|
expected !== null ? [{ id: ATTR_ID, values: expected }] : [];
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe("works with boolean attributes", () => {
|
||||||
|
test.each`
|
||||||
|
newAttr | oldAttr | expected
|
||||||
|
${null} | ${null} | ${null}
|
||||||
|
${"true"} | ${true} | ${null}
|
||||||
|
${"true"} | ${false} | ${true}
|
||||||
|
${"true"} | ${null} | ${true}
|
||||||
|
${"false"} | ${false} | ${null}
|
||||||
|
${"false"} | ${true} | ${false}
|
||||||
|
${"false"} | ${null} | ${false}
|
||||||
|
`(
|
||||||
|
"$oldAttr -> $newAttr returns $expected",
|
||||||
|
({ newAttr, oldAttr, expected }) => {
|
||||||
|
const attribute = createBooleanAttribute(newAttr);
|
||||||
|
const prevAttribute = createBooleanAttribute(oldAttr);
|
||||||
|
|
||||||
|
const result = prepareAttributesInput({
|
||||||
|
attributes: [attribute],
|
||||||
|
prevAttributes: [prevAttribute],
|
||||||
|
updatedFileAttributes: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedResult =
|
||||||
|
expected !== null ? [{ id: ATTR_ID, boolean: expected }] : [];
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe("works with rich text attributes", () => {
|
||||||
|
test.each`
|
||||||
|
newAttr | oldAttr | expected
|
||||||
|
${null} | ${null} | ${null}
|
||||||
|
${"my value"} | ${"my value"} | ${null}
|
||||||
|
${"my value"} | ${null} | ${"my value"}
|
||||||
|
${null} | ${"my value"} | ${undefined}
|
||||||
|
`(
|
||||||
|
"$oldAttr -> $newAttr returns $expected",
|
||||||
|
({ newAttr, oldAttr, expected }) => {
|
||||||
|
const attribute = createRichTextAttribute(newAttr);
|
||||||
|
const prevAttribute = createRichTextAttribute(oldAttr);
|
||||||
|
|
||||||
|
const result = prepareAttributesInput({
|
||||||
|
attributes: [attribute],
|
||||||
|
prevAttributes: [prevAttribute],
|
||||||
|
updatedFileAttributes: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedResult =
|
||||||
|
expected !== null ? [{ id: ATTR_ID, richText: expected }] : [];
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe("works with date attributes", () => {
|
||||||
|
test.each`
|
||||||
|
newAttr | oldAttr | expected
|
||||||
|
${null} | ${null} | ${null}
|
||||||
|
${"2021-01-01"} | ${"2021-01-01"} | ${null}
|
||||||
|
${"2021-01-01"} | ${null} | ${"2021-01-01"}
|
||||||
|
${null} | ${"2021-01-01"} | ${undefined}
|
||||||
|
`(
|
||||||
|
"$oldAttr -> $newAttr returns $expected",
|
||||||
|
({ newAttr, oldAttr, expected }) => {
|
||||||
|
const attribute = createDateAttribute(newAttr);
|
||||||
|
const prevAttribute = createDateAttribute(oldAttr);
|
||||||
|
|
||||||
|
const result = prepareAttributesInput({
|
||||||
|
attributes: [attribute],
|
||||||
|
prevAttributes: [prevAttribute],
|
||||||
|
updatedFileAttributes: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedResult =
|
||||||
|
expected !== null ? [{ id: ATTR_ID, date: expected }] : [];
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe("works with date time attributes", () => {
|
||||||
|
const dateTime = "2021-01-01T11:00:00+01:00";
|
||||||
|
test.each`
|
||||||
|
newAttr | oldAttr | expected
|
||||||
|
${null} | ${null} | ${null}
|
||||||
|
${dateTime} | ${dateTime} | ${null}
|
||||||
|
${dateTime} | ${null} | ${dateTime}
|
||||||
|
${null} | ${dateTime} | ${undefined}
|
||||||
|
`(
|
||||||
|
"$oldAttr -> $newAttr returns $expected",
|
||||||
|
({ newAttr, oldAttr, expected }) => {
|
||||||
|
const attribute = createDateTimeAttribute(newAttr);
|
||||||
|
const prevAttribute = createDateTimeAttribute(oldAttr);
|
||||||
|
|
||||||
|
const result = prepareAttributesInput({
|
||||||
|
attributes: [attribute],
|
||||||
|
prevAttributes: [prevAttribute],
|
||||||
|
updatedFileAttributes: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedResult =
|
||||||
|
expected !== null ? [{ id: ATTR_ID, dateTime: expected }] : [];
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe("works with swatch attributes", () => {
|
||||||
|
test.each`
|
||||||
|
newAttr | oldAttr | expected
|
||||||
|
${null} | ${null} | ${null}
|
||||||
|
${"my value"} | ${"my value"} | ${null}
|
||||||
|
${"my value"} | ${null} | ${["my value"]}
|
||||||
|
${null} | ${"my value"} | ${[]}
|
||||||
|
`(
|
||||||
|
"$oldAttr -> $newAttr returns $expected",
|
||||||
|
({ newAttr, oldAttr, expected }) => {
|
||||||
|
const attribute = createSwatchAttribute(newAttr);
|
||||||
|
const prevAttribute = createSwatchAttribute(oldAttr);
|
||||||
|
|
||||||
|
const result = prepareAttributesInput({
|
||||||
|
attributes: [attribute],
|
||||||
|
prevAttributes: [prevAttribute],
|
||||||
|
updatedFileAttributes: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedResult =
|
||||||
|
expected !== null ? [{ id: ATTR_ID, values: expected }] : [];
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe("works with numeric attributes", () => {
|
||||||
|
test.each`
|
||||||
|
newAttr | oldAttr | expected
|
||||||
|
${null} | ${null} | ${null}
|
||||||
|
${"1"} | ${"1"} | ${null}
|
||||||
|
${"1"} | ${null} | ${["1"]}
|
||||||
|
${null} | ${"1"} | ${[]}
|
||||||
|
`(
|
||||||
|
"$oldAttr -> $newAttr returns $expected",
|
||||||
|
({ newAttr, oldAttr, expected }) => {
|
||||||
|
const attribute = createNumericAttribute(newAttr);
|
||||||
|
const prevAttribute = createNumericAttribute(oldAttr);
|
||||||
|
|
||||||
|
const result = prepareAttributesInput({
|
||||||
|
attributes: [attribute],
|
||||||
|
prevAttributes: [prevAttribute],
|
||||||
|
updatedFileAttributes: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedResult =
|
||||||
|
expected !== null ? [{ id: ATTR_ID, values: expected }] : [];
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe("works with file attributes", () => {
|
||||||
|
it("removes existing image (img -> null)", () => {
|
||||||
|
const attribute = createFileAttribute(null);
|
||||||
|
const prevAttribute = createNumericAttribute("bob.jpg");
|
||||||
|
|
||||||
|
const result = prepareAttributesInput({
|
||||||
|
attributes: [attribute],
|
||||||
|
prevAttributes: [prevAttribute],
|
||||||
|
updatedFileAttributes: [
|
||||||
|
{ file: undefined, id: ATTR_ID, contentType: undefined, values: [] }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Files are deleted by using AttributeValueDetele mutation
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
it("adds new image (null -> img)", () => {
|
||||||
|
const attribute = createFileAttribute("bob.jpg");
|
||||||
|
const prevAttribute = createNumericAttribute(null);
|
||||||
|
|
||||||
|
const uploadUrl = "http://some-url.com/media/file_upload/bob.jpg";
|
||||||
|
const result = prepareAttributesInput({
|
||||||
|
attributes: [attribute],
|
||||||
|
prevAttributes: [prevAttribute],
|
||||||
|
updatedFileAttributes: [
|
||||||
|
{
|
||||||
|
file: uploadUrl,
|
||||||
|
id: ATTR_ID,
|
||||||
|
contentType: "image/jpeg",
|
||||||
|
values: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
id: ATTR_ID,
|
||||||
|
file: uploadUrl,
|
||||||
|
contentType: "image/jpeg"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it("replaces existing image (bob.jpg -> juice.png)", () => {
|
||||||
|
const attribute = createFileAttribute("bob.jpg");
|
||||||
|
const prevAttribute = createNumericAttribute("juice.png");
|
||||||
|
|
||||||
|
const uploadUrl = "http://some-url.com/media/file_upload/juice.jpg";
|
||||||
|
const result = prepareAttributesInput({
|
||||||
|
attributes: [attribute],
|
||||||
|
prevAttributes: [prevAttribute],
|
||||||
|
updatedFileAttributes: [
|
||||||
|
{
|
||||||
|
file: uploadUrl,
|
||||||
|
id: ATTR_ID,
|
||||||
|
contentType: "image/png",
|
||||||
|
values: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
id: ATTR_ID,
|
||||||
|
file: uploadUrl,
|
||||||
|
contentType: "image/png"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
} from "@saleor/hooks/useFormset";
|
} from "@saleor/hooks/useFormset";
|
||||||
import { FetchMoreProps, ReorderEvent } from "@saleor/types";
|
import { FetchMoreProps, ReorderEvent } from "@saleor/types";
|
||||||
import { move, toggle } from "@saleor/utils/lists";
|
import { move, toggle } from "@saleor/utils/lists";
|
||||||
|
import isEqual from "lodash/isEqual";
|
||||||
|
|
||||||
import { getFileValuesToUploadFromAttributes, isFileValueUnused } from "./data";
|
import { getFileValuesToUploadFromAttributes, isFileValueUnused } from "./data";
|
||||||
|
|
||||||
|
@ -168,11 +169,6 @@ export function createAttributeValueReorderHandler(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AttributesArgs {
|
|
||||||
attributes: AttributeInput[];
|
|
||||||
updatedFileAttributes: AttributeValueInput[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFileInput(
|
function getFileInput(
|
||||||
attribute: AttributeInput,
|
attribute: AttributeInput,
|
||||||
updatedFileAttributes: AttributeValueInput[]
|
updatedFileAttributes: AttributeValueInput[]
|
||||||
|
@ -202,11 +198,34 @@ function getBooleanInput(attribute: AttributeInput) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAttributesMap(attributes: AttributeInput[] | null) {
|
||||||
|
if (attributes && attributes?.length !== 0) {
|
||||||
|
return new Map(
|
||||||
|
attributes.map(attribute => [attribute.id, attribute.value])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AttributesArgs {
|
||||||
|
attributes: AttributeInput[];
|
||||||
|
prevAttributes: AttributeInput[] | null;
|
||||||
|
updatedFileAttributes: AttributeValueInput[];
|
||||||
|
}
|
||||||
|
|
||||||
export const prepareAttributesInput = ({
|
export const prepareAttributesInput = ({
|
||||||
attributes,
|
attributes,
|
||||||
|
prevAttributes,
|
||||||
updatedFileAttributes
|
updatedFileAttributes
|
||||||
}: AttributesArgs): AttributeValueInput[] =>
|
}: AttributesArgs): AttributeValueInput[] => {
|
||||||
attributes.reduce((attrInput, attr) => {
|
const prevAttributesMap = getAttributesMap(prevAttributes);
|
||||||
|
|
||||||
|
return attributes.reduce((attrInput, attr) => {
|
||||||
|
const prevAttrValue = prevAttributesMap.get(attr.id);
|
||||||
|
if (isEqual(attr.value, prevAttrValue)) {
|
||||||
|
return attrInput;
|
||||||
|
}
|
||||||
|
|
||||||
const inputType = attr.data.inputType;
|
const inputType = attr.data.inputType;
|
||||||
if (inputType === AttributeInputTypeEnum.FILE) {
|
if (inputType === AttributeInputTypeEnum.FILE) {
|
||||||
const fileInput = getFileInput(attr, updatedFileAttributes);
|
const fileInput = getFileInput(attr, updatedFileAttributes);
|
||||||
|
@ -217,6 +236,11 @@ export const prepareAttributesInput = ({
|
||||||
}
|
}
|
||||||
if (inputType === AttributeInputTypeEnum.BOOLEAN) {
|
if (inputType === AttributeInputTypeEnum.BOOLEAN) {
|
||||||
const booleanInput = getBooleanInput(attr);
|
const booleanInput = getBooleanInput(attr);
|
||||||
|
// previous comparison doesn't work because value was string
|
||||||
|
if (isEqual([booleanInput.boolean], prevAttrValue)) {
|
||||||
|
return attrInput;
|
||||||
|
}
|
||||||
|
|
||||||
attrInput.push(booleanInput);
|
attrInput.push(booleanInput);
|
||||||
return attrInput;
|
return attrInput;
|
||||||
}
|
}
|
||||||
|
@ -228,11 +252,6 @@ export const prepareAttributesInput = ({
|
||||||
return attrInput;
|
return attrInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
// for cases other than rich text, boolean and file
|
|
||||||
// we can skip attribute
|
|
||||||
if (!attr.value[0]) {
|
|
||||||
return attrInput;
|
|
||||||
}
|
|
||||||
if (inputType === AttributeInputTypeEnum.REFERENCE) {
|
if (inputType === AttributeInputTypeEnum.REFERENCE) {
|
||||||
attrInput.push({
|
attrInput.push({
|
||||||
id: attr.id,
|
id: attr.id,
|
||||||
|
@ -262,6 +281,7 @@ export const prepareAttributesInput = ({
|
||||||
|
|
||||||
return attrInput;
|
return attrInput;
|
||||||
}, []);
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
export const handleUploadMultipleFiles = async (
|
export const handleUploadMultipleFiles = async (
|
||||||
attributesWithNewFileValue: FormsetData<null, File>,
|
attributesWithNewFileValue: FormsetData<null, File>,
|
||||||
|
|
|
@ -119,6 +119,7 @@ export const PageCreate: React.FC<PageCreateProps> = ({ params }) => {
|
||||||
input: {
|
input: {
|
||||||
attributes: prepareAttributesInput({
|
attributes: prepareAttributesInput({
|
||||||
attributes: formData.attributes,
|
attributes: formData.attributes,
|
||||||
|
prevAttributes: null,
|
||||||
updatedFileAttributes
|
updatedFileAttributes
|
||||||
}),
|
}),
|
||||||
content: getParsedDataForJsonStringField(formData.content),
|
content: getParsedDataForJsonStringField(formData.content),
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
import {
|
import {
|
||||||
AttributeErrorFragment,
|
AttributeErrorFragment,
|
||||||
AttributeValueInput,
|
AttributeValueInput,
|
||||||
|
PageDetailsFragment,
|
||||||
PageErrorFragment,
|
PageErrorFragment,
|
||||||
PageInput,
|
PageInput,
|
||||||
UploadErrorFragment,
|
UploadErrorFragment,
|
||||||
|
@ -46,6 +47,7 @@ import { getStringOrPlaceholder, maybe } from "../../misc";
|
||||||
import PageDetailsPage from "../components/PageDetailsPage";
|
import PageDetailsPage from "../components/PageDetailsPage";
|
||||||
import { PageData, PageSubmitData } from "../components/PageDetailsPage/form";
|
import { PageData, PageSubmitData } from "../components/PageDetailsPage/form";
|
||||||
import { pageListUrl, pageUrl, PageUrlQueryParams } from "../urls";
|
import { pageListUrl, pageUrl, PageUrlQueryParams } from "../urls";
|
||||||
|
import { getAttributeInputFromPage } from "../utils/data";
|
||||||
|
|
||||||
export interface PageDetailsProps {
|
export interface PageDetailsProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -54,10 +56,12 @@ export interface PageDetailsProps {
|
||||||
|
|
||||||
const createPageInput = (
|
const createPageInput = (
|
||||||
data: PageData,
|
data: PageData,
|
||||||
|
page: PageDetailsFragment,
|
||||||
updatedFileAttributes: AttributeValueInput[]
|
updatedFileAttributes: AttributeValueInput[]
|
||||||
): PageInput => ({
|
): PageInput => ({
|
||||||
attributes: prepareAttributesInput({
|
attributes: prepareAttributesInput({
|
||||||
attributes: data.attributes,
|
attributes: data.attributes,
|
||||||
|
prevAttributes: getAttributeInputFromPage(page),
|
||||||
updatedFileAttributes
|
updatedFileAttributes
|
||||||
}),
|
}),
|
||||||
content: getParsedDataForJsonStringField(data.content),
|
content: getParsedDataForJsonStringField(data.content),
|
||||||
|
@ -138,7 +142,11 @@ export const PageDetails: React.FC<PageDetailsProps> = ({ id, params }) => {
|
||||||
const updateResult = await pageUpdate({
|
const updateResult = await pageUpdate({
|
||||||
variables: {
|
variables: {
|
||||||
id,
|
id,
|
||||||
input: createPageInput(data, updatedFileAttributes),
|
input: createPageInput(
|
||||||
|
data,
|
||||||
|
pageDetails?.data?.page,
|
||||||
|
updatedFileAttributes
|
||||||
|
),
|
||||||
firstValues: VALUES_PAGINATE_BY
|
firstValues: VALUES_PAGINATE_BY
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -102,6 +102,7 @@ export function createHandler(
|
||||||
input: {
|
input: {
|
||||||
attributes: prepareAttributesInput({
|
attributes: prepareAttributesInput({
|
||||||
attributes: formData.attributes,
|
attributes: formData.attributes,
|
||||||
|
prevAttributes: null,
|
||||||
updatedFileAttributes
|
updatedFileAttributes
|
||||||
}),
|
}),
|
||||||
category: formData.category,
|
category: formData.category,
|
||||||
|
|
|
@ -39,7 +39,10 @@ import {
|
||||||
VariantCreateMutationVariables
|
VariantCreateMutationVariables
|
||||||
} from "@saleor/graphql";
|
} from "@saleor/graphql";
|
||||||
import { ProductUpdatePageSubmitData } from "@saleor/products/components/ProductUpdatePage";
|
import { ProductUpdatePageSubmitData } from "@saleor/products/components/ProductUpdatePage";
|
||||||
import { mapFormsetStockToStockInput } from "@saleor/products/utils/data";
|
import {
|
||||||
|
getAttributeInputFromProduct,
|
||||||
|
mapFormsetStockToStockInput
|
||||||
|
} from "@saleor/products/utils/data";
|
||||||
import { ReorderEvent } from "@saleor/types";
|
import { ReorderEvent } from "@saleor/types";
|
||||||
import { move } from "@saleor/utils/lists";
|
import { move } from "@saleor/utils/lists";
|
||||||
import { getParsedDataForJsonStringField } from "@saleor/utils/richText/misc";
|
import { getParsedDataForJsonStringField } from "@saleor/utils/richText/misc";
|
||||||
|
@ -116,6 +119,7 @@ export function createUpdateHandler(
|
||||||
input: {
|
input: {
|
||||||
attributes: prepareAttributesInput({
|
attributes: prepareAttributesInput({
|
||||||
attributes: data.attributes,
|
attributes: data.attributes,
|
||||||
|
prevAttributes: getAttributeInputFromProduct(product),
|
||||||
updatedFileAttributes
|
updatedFileAttributes
|
||||||
}),
|
}),
|
||||||
category: data.category,
|
category: data.category,
|
||||||
|
|
|
@ -36,6 +36,7 @@ import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import useOnSetDefaultVariant from "@saleor/hooks/useOnSetDefaultVariant";
|
import useOnSetDefaultVariant from "@saleor/hooks/useOnSetDefaultVariant";
|
||||||
import useShop from "@saleor/hooks/useShop";
|
import useShop from "@saleor/hooks/useShop";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
|
import { getAttributeInputFromVariant } from "@saleor/products/utils/data";
|
||||||
import usePageSearch from "@saleor/searches/usePageSearch";
|
import usePageSearch from "@saleor/searches/usePageSearch";
|
||||||
import useProductSearch from "@saleor/searches/useProductSearch";
|
import useProductSearch from "@saleor/searches/useProductSearch";
|
||||||
import useAttributeValueSearchHandler from "@saleor/utils/handlers/attributeValueSearchHandler";
|
import useAttributeValueSearchHandler from "@saleor/utils/handlers/attributeValueSearchHandler";
|
||||||
|
@ -267,6 +268,7 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
||||||
addStocks: data.addStocks.map(mapFormsetStockToStockInput),
|
addStocks: data.addStocks.map(mapFormsetStockToStockInput),
|
||||||
attributes: prepareAttributesInput({
|
attributes: prepareAttributesInput({
|
||||||
attributes: data.attributes,
|
attributes: data.attributes,
|
||||||
|
prevAttributes: getAttributeInputFromVariant(variant),
|
||||||
updatedFileAttributes
|
updatedFileAttributes
|
||||||
}),
|
}),
|
||||||
id: variantId,
|
id: variantId,
|
||||||
|
|
|
@ -121,6 +121,7 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
|
||||||
attributes: formData.attributes.filter(
|
attributes: formData.attributes.filter(
|
||||||
attribute => attribute.value?.length && attribute.value[0] !== ""
|
attribute => attribute.value?.length && attribute.value[0] !== ""
|
||||||
),
|
),
|
||||||
|
prevAttributes: null,
|
||||||
updatedFileAttributes
|
updatedFileAttributes
|
||||||
}),
|
}),
|
||||||
product: productId,
|
product: productId,
|
||||||
|
|
Loading…
Reference in a new issue