forked from vendure-ecommerce/vendure
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(core): Fix renaming of product with readonly custom field (vendur…
- Loading branch information
Showing
4 changed files
with
113 additions
and
97 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
packages/admin-ui/src/lib/core/src/data/utils/is-entity-create-or-update-mutation.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 25 additions & 93 deletions
118
packages/admin-ui/src/lib/core/src/data/utils/remove-readonly-custom-fields.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |