Browse Source

fix(core): Fix renaming of product with readonly custom field (#2684)

Andrii Baran 1 year ago
parent
commit
2075d6dc63

+ 2 - 4
packages/admin-ui/src/lib/core/src/data/providers/base-data.service.ts

@@ -11,11 +11,9 @@ import { CustomFields } from '../../common/generated-types';
 import { QueryResult } from '../query-result';
 import { ServerConfigService } from '../server-config';
 import { addCustomFields } from '../utils/add-custom-fields';
-import {
-    isEntityCreateOrUpdateMutation,
-    removeReadonlyCustomFields,
-} from '../utils/remove-readonly-custom-fields';
+import { removeReadonlyCustomFields } from '../utils/remove-readonly-custom-fields';
 import { transformRelationCustomFieldInputs } from '../utils/transform-relation-custom-field-inputs';
+import { isEntityCreateOrUpdateMutation } from '../utils/is-entity-create-or-update-mutation';
 
 @Injectable()
 export class BaseDataService {

+ 51 - 0
packages/admin-ui/src/lib/core/src/data/utils/is-entity-create-or-update-mutation.ts

@@ -0,0 +1,51 @@
+import { DocumentNode, getOperationAST, NamedTypeNode, TypeNode } from 'graphql';
+
+const CREATE_ENTITY_REGEX = /Create([A-Za-z]+)Input/;
+const UPDATE_ENTITY_REGEX = /Update([A-Za-z]+)Input/;
+
+/**
+ * Checks the current documentNode for an operation with a variable named "Create<Entity>Input" or "Update<Entity>Input"
+ * and if a match is found, returns the <Entity> name.
+ */
+export function isEntityCreateOrUpdateMutation(documentNode: DocumentNode): string | undefined {
+    const operationDef = getOperationAST(documentNode, null);
+    if (operationDef && operationDef.variableDefinitions) {
+        for (const variableDef of operationDef.variableDefinitions) {
+            const namedType = extractInputType(variableDef.type);
+            const inputTypeName = namedType.name.value;
+
+            // special cases which don't follow the usual pattern
+            if (inputTypeName === 'UpdateActiveAdministratorInput') {
+                return 'Administrator';
+            }
+            if (inputTypeName === 'ModifyOrderInput') {
+                return 'Order';
+            }
+            if (
+                inputTypeName === 'AddItemToDraftOrderInput' ||
+                inputTypeName === 'AdjustDraftOrderLineInput'
+            ) {
+                return 'OrderLine';
+            }
+
+            const createMatch = inputTypeName.match(CREATE_ENTITY_REGEX);
+            if (createMatch) {
+                return createMatch[1];
+            }
+            const updateMatch = inputTypeName.match(UPDATE_ENTITY_REGEX);
+            if (updateMatch) {
+                return updateMatch[1];
+            }
+        }
+    }
+}
+
+function extractInputType(type: TypeNode): NamedTypeNode {
+    if (type.kind === 'NonNullType') {
+        return extractInputType(type.type);
+    }
+    if (type.kind === 'ListType') {
+        return extractInputType(type.type);
+    }
+    return type;
+}

+ 35 - 0
packages/admin-ui/src/lib/core/src/data/utils/remove-readonly-custom-fields.spec.ts

@@ -45,6 +45,23 @@ describe('removeReadonlyCustomFields', () => {
         } as any);
     });
 
+    it('readonly field and customFields is undefined', () => {
+        const config: CustomFieldConfig[] = [{ name: 'alias', type: 'string', readonly: true, list: false }];
+
+        const entity = {
+            id: 1,
+            name: 'test',
+            customFields: undefined,
+        };
+
+        const result = removeReadonlyCustomFields(entity, config);
+        expect(result).toEqual({
+            id: 1,
+            name: 'test',
+            customFields: undefined,
+        } as any);
+    });
+
     it('readonly field in translation', () => {
         const config: CustomFieldConfig[] = [
             { name: 'alias', type: 'localeString', readonly: true, list: false },
@@ -63,6 +80,24 @@ describe('removeReadonlyCustomFields', () => {
         } as any);
     });
 
+    it('readonly field and customFields is undefined in translation', () => {
+        const config: CustomFieldConfig[] = [
+            { name: 'alias', type: 'localeString', readonly: true, list: false },
+        ];
+        const entity = {
+            id: 1,
+            name: 'test',
+            translations: [{ id: 1, languageCode: LanguageCode.en, customFields: undefined }],
+        };
+
+        const result = removeReadonlyCustomFields(entity, config);
+        expect(result).toEqual({
+            id: 1,
+            name: 'test',
+            translations: [{ id: 1, languageCode: LanguageCode.en, customFields: undefined }],
+        } as any);
+    });
+
     it('wrapped in an input object', () => {
         const config: CustomFieldConfig[] = [
             { name: 'weight', type: 'int', list: false },

+ 25 - 93
packages/admin-ui/src/lib/core/src/data/utils/remove-readonly-custom-fields.ts

@@ -1,121 +1,53 @@
-import { simpleDeepClone } from '@vendure/common/lib/simple-deep-clone';
-import { DocumentNode, getOperationAST, NamedTypeNode, TypeNode } from 'graphql';
-
 import { CustomFieldConfig } from '../../common/generated-types';
 
-const CREATE_ENTITY_REGEX = /Create([A-Za-z]+)Input/;
-const UPDATE_ENTITY_REGEX = /Update([A-Za-z]+)Input/;
-
 type InputWithOptionalCustomFields = Record<string, any> & {
     customFields?: Record<string, any>;
 };
-type InputWithCustomFields = Record<string, any> & {
-    customFields: Record<string, any>;
-};
 
 type EntityInput = InputWithOptionalCustomFields & {
     translations?: InputWithOptionalCustomFields[];
 };
 
-/**
- * Checks the current documentNode for an operation with a variable named "Create<Entity>Input" or "Update<Entity>Input"
- * and if a match is found, returns the <Entity> name.
- */
-export function isEntityCreateOrUpdateMutation(documentNode: DocumentNode): string | undefined {
-    const operationDef = getOperationAST(documentNode, null);
-    if (operationDef && operationDef.variableDefinitions) {
-        for (const variableDef of operationDef.variableDefinitions) {
-            const namedType = extractInputType(variableDef.type);
-            const inputTypeName = namedType.name.value;
-
-            // special cases which don't follow the usual pattern
-            if (inputTypeName === 'UpdateActiveAdministratorInput') {
-                return 'Administrator';
-            }
-            if (inputTypeName === 'ModifyOrderInput') {
-                return 'Order';
-            }
-            if (
-                inputTypeName === 'AddItemToDraftOrderInput' ||
-                inputTypeName === 'AdjustDraftOrderLineInput'
-            ) {
-                return 'OrderLine';
-            }
+type Variable = EntityInput | EntityInput[];
 
-            const createMatch = inputTypeName.match(CREATE_ENTITY_REGEX);
-            if (createMatch) {
-                return createMatch[1];
-            }
-            const updateMatch = inputTypeName.match(UPDATE_ENTITY_REGEX);
-            if (updateMatch) {
-                return updateMatch[1];
-            }
-        }
-    }
-}
-
-function extractInputType(type: TypeNode): NamedTypeNode {
-    if (type.kind === 'NonNullType') {
-        return extractInputType(type.type);
-    }
-    if (type.kind === 'ListType') {
-        return extractInputType(type.type);
-    }
-    return type;
-}
+type WrappedVariable = {
+    input: Variable;
+};
 
 /**
  * Removes any `readonly` custom fields from an entity (including its translations).
  * To be used before submitting the entity for a create or update request.
  */
 export function removeReadonlyCustomFields(
-    variables: { input?: EntityInput | EntityInput[] } | EntityInput | EntityInput[],
+    variables: Variable | WrappedVariable | WrappedVariable[],
     customFieldConfig: CustomFieldConfig[],
-): { input?: EntityInput | EntityInput[] } | EntityInput | EntityInput[] {
-    if (!Array.isArray(variables)) {
+) {
+    if (Array.isArray(variables)) {
+        return variables.map(variable => removeReadonlyCustomFields(variable, customFieldConfig));
+    }
+
+    if ('input' in variables && variables.input) {
         if (Array.isArray(variables.input)) {
-            for (const input of variables.input) {
-                removeReadonly(input, customFieldConfig);
-            }
+            variables.input = variables.input.map(variable => removeReadonly(variable, customFieldConfig));
         } else {
-            removeReadonly(variables.input, customFieldConfig);
-        }
-    } else {
-        for (const input of variables) {
-            removeReadonly(input, customFieldConfig);
+            variables.input = removeReadonly(variables.input, customFieldConfig);
         }
+        return variables;
     }
+
     return removeReadonly(variables, customFieldConfig);
 }
 
-function removeReadonly(input: InputWithOptionalCustomFields, customFieldConfig: CustomFieldConfig[]) {
-    for (const field of customFieldConfig) {
-        if (field.readonly) {
-            if (field.type === 'localeString') {
-                if (hasTranslations(input)) {
-                    for (const translation of input.translations) {
-                        if (
-                            hasCustomFields(translation) &&
-                            translation.customFields[field.name] !== undefined
-                        ) {
-                            delete translation.customFields[field.name];
-                        }
-                    }
-                }
-            } else {
-                if (hasCustomFields(input) && input.customFields[field.name] !== undefined) {
-                    delete input.customFields[field.name];
-                }
-            }
-        }
-    }
-    return input;
-}
+function removeReadonly(input: EntityInput, customFieldConfig: CustomFieldConfig[]) {
+    const readonlyConfigs = customFieldConfig.filter(({ readonly }) => readonly);
 
-function hasCustomFields(input: any): input is InputWithCustomFields {
-    return input != null && input.hasOwnProperty('customFields');
-}
+    readonlyConfigs.forEach(({ name }) => {
+        input.translations?.forEach(translation => {
+            delete translation.customFields?.[name];
+        });
+
+        delete input.customFields?.[name];
+    });
 
-function hasTranslations(input: any): input is { translations: InputWithOptionalCustomFields[] } {
-    return input != null && input.hasOwnProperty('translations');
+    return input;
 }