diff --git a/design-documents/graph-ql/coverage/custom-attributes/attributes-metadata.graphqls b/design-documents/graph-ql/coverage/custom-attributes/attributes-metadata.graphqls new file mode 100644 index 000000000..19c857376 --- /dev/null +++ b/design-documents/graph-ql/coverage/custom-attributes/attributes-metadata.graphqls @@ -0,0 +1,214 @@ +type Query { + customAttributeMetadataV2(attributes: [AttributeMetadataInput!]!): CustomAttributeMetadata + customAttributesLists(listType: CustomAttributesListsEnum): CustomAttributeMetadata +} + +type AttributeMetadataInput { + attribute_uid: ID +} + +type CustomAttributeMetadata { # this replaces existing Attribute type + items: [AttributeMetadataInterface] +} + +interface AttributeMetadataInterface { # base metadata common to all attributes + uid: ID # base64Encode(entityID/codeID) + label: String + data_type: ObjectDataTypeEnum # string, int, float, boolean etc + sort_order: Int + entity_type: EntityTypeEnum + ui_input: UiInputTypeInterface! +} + +type CustomerAttributeMetadata implements AttributeMetadataInterface { + forms_to_use_in: [CustomAttributesListsEnum] +} + +type CustomerAddressAttributeMetadata implements AttributeMetadataInterface { +} + +type ProductAttributeMetadata implements AttributeMetadataInterface { + lists_to_use_in: [CustomAttributesListsEnum] +} + +# interfaces for different types used in inputs -------------- + +interface UiInputTypeInterface { + ui_input_type: EntityTypeEnum + is_value_required: Boolean! +} + +interface ValidationTextInputTypeInterface { + filter: InputValidationFilterEnum +} + +interface FilterableTextInputTypeInterface { + input_validation: FilterableText +} + +interface AttributeOptionsInterface { + attribute_options: [AttributeOptionInterface] +} + +interface AttributeOptionInterface { + uid: ID! + is_default: Boolean + label: String +} + +interface SelectableInputTypeInterface { + +} + +interface TextInputTypeInterface { + default_value: String +} + +type FilterableText { + input_validation_type: InputValidationTypeEnum + minimum_text_length: Int + maximum_text_length: Int +} + +type TextUiInputType implements UiInputTypeInterface, TextInputTypeInterface, FilterableTextInputTypeInterface, ValidationTextInputTypeInterface { + +} + +type TextAreaUiInputType implements UiInputTypeInterface, TextInputTypeInterface, FilterableTextInputTypeInterface, ValidationTextInputTypeInterface { + +} + +type MultipleLineUiInputType implements UiInputTypeInterface, TextInputTypeInterface, FilterableTextInputTypeInterface, ValidationTextInputTypeInterface { + lines_count: Int +} + +type DateUiInputType implements UiInputTypeInterface, TextInputTypeInterface, FilterableTextInputTypeInterface { + minimum_date_allowed: String + maximum_date_allowed: String +} + +type FileUiInputType implements UiInputTypeInterface, TextInputTypeInterface, FilterableTextInputTypeInterface { + maximum_file_size: Int # bytes + allowed_file_extensions: [String] +} + +type ImageUiInputType implements UiInputTypeInterface, TextInputTypeInterface, FilterableTextInputTypeInterface { + maximum_file_size: Int # bytes + allowed_file_extensions: [String] + maximum_image_width: Int # in pixels + maximum_image_height: Int # in pixels +} + +type DropDownUiInputType implements UiInputTypeInterface, SelectableInputTypeInterface, AttributeOptionsInterface { +} + +type MultipleSelectUiInputType implements UiInputTypeInterface, SelectableInputTypeInterface, AttributeOptionsInterface { +} + +interface SwatchInputTypeInterface { + update_product_preview_image: Boolean +} + +type VisualSwatchUiInputType implements UiInputTypeInterface, SelectableInputTypeInterface, AttributeOptionsInterface, SwatchInputTypeInterface { + use_product_image_for_swatch_if_possible: Boolean +} + +type TextSwatchUiInputType implements UiInputTypeInterface, SelectableInputTypeInterface, AttributeOptionsInterface, SwatchInputTypeInterface { + +} + +type AttributeOption implements AttributeOptionInterface { + value: String @deprecated(reason: "use `uid` instead") + label: String +} + +interface SelectableInputTypeInterface { + +} + +type ColorSwatchAttributeOption implements AttributeOptionInterface { + color: String # html hex code format +} + +type ImageSwatchAttributeOption implements AttributeOptionInterface { + image_path: String # relative path +} + +type TextSwatchAttributeOption implements AttributeOptionInterface { + description: String +} + +#enums to support above queries +enum ObjectDataTypeEnum { + STRING + FLOAT + INT + BOOLEAN +} + +enum EntityTypeEnum { + CUSTOMER + CUSTOMER_ADDRESS + CATALOG_CATEGORY + CATALOG_PRODUCT + ORDER + INVOICE + CREDITMEMO + SHIPMENT + RMA_ITEM + GENERIC +} + +enum UiInputTypeEnum { + TEXT + TEXTAREA + MULTILINE + DATE + DATETIME + SELECT + MULTISELECT + BOOLEAN + FILE + IMAGE + SWATCH_VISUAL + SWATCH_TEXT + PRICE + MEDIA_IMAGE + WEEE +} + +enum InputValidationTypeEnum { + ALPHANUMERIC + ALPHANUMERIC_WITH_SPACES + NUMERIC_ONLY + ALLPHA_ONLY + DATE + URL + EMAIL + LENGTH_ONLY +} + +enum InputValidationFilterEnum { + STRIPTAGS + ESCAPEHTML + DATE +} + +enum CustomerAttributeFormsEnum { + CUSTOMER_ACCOUNT_CREATE + CUSTOMER_ACCOUNT_EDIT + ADMINHTML_CHECKOUT +} + +enum CustomAttributesListsEnum { + PRODUCTS_COMPARE + PRODUCTS_LISTING + ADVANCED_CATALOG_SEARCH + PRODUCT_SORT + PRODUCT_FILTER + PRODUCT_SEARCH_RESULTS + PRODUCT_AGGREGATIONS + RMA_FORM + CUSTOMER_REGISTRATION_FORM + CUSTOMER_ADDRESS_FORM +} diff --git a/design-documents/graph-ql/coverage/custom-attributes/attributes-metadata.md b/design-documents/graph-ql/coverage/custom-attributes/attributes-metadata.md index 9d4b868c5..3a1d64047 100644 --- a/design-documents/graph-ql/coverage/custom-attributes/attributes-metadata.md +++ b/design-documents/graph-ql/coverage/custom-attributes/attributes-metadata.md @@ -11,49 +11,137 @@ Additionally, there should be a way to retrieve metadata for all storefront cust # Proposed solution -Relaxing signature of existing `customAttributeMetadata` query by making its `attriubtes` argument optional will allow to fetch all storefront attributes metadata. +Relaxing signature of existing `customAttributeMetadata` query by making its `attributes` argument optional will allow to fetch all storefront attributes metadata. Existing schema: ```graphql -Query.customAttributeMetadata( +Query.customAttributesMetadata( attributes: [AttributeInput!]! ): CustomAttributeMetadata +``` + +Added schema: + +```graphql +Query.customAttributesMetadataV2( + attributes: [AttributeMetadataInput!] +): CustomAttributeMetadata + +#adding to existing type a choice of uid or code and +input AttributeMetadataInput { + attribute_uid: ID +} + +type CustomAttributeMetadata { # this replaces existing Attribute type + items: [AttributeMetadataInterface] +} + +interface AttributeMetadataInterface { # base metadata common to all attributes + uid: ID # base64Encode(entityID/codeID) + label: String + data_type: ObjectDataTypeEnum # string, int, float, boolean etc + sort_order: Int + entity_type: EntityTypeEnum + ui_input: UiInputTypeInterface! +} + +type CustomerAttributeMetadata implements AttributeMetadataInterface { + forms_to_use_in: [CustomAttributesListsEnum] +} + +type CustomerAddressAttributeMetadata implements AttributeMetadataInterface { +} + +type ProductAttributeMetadata implements AttributeMetadataInterface { + lists_to_use_in: [CustomAttributesListsEnum] +} + +type TextUiInputType implements UiInputTypeInterface, TextInputTypeInterface, FilterableTextInputTypeInterface, ValidationTextInputTypeInterface { + +} + +type TextAreaUiInputType implements UiInputTypeInterface, TextInputTypeInterface, FilterableTextInputTypeInterface, ValidationTextInputTypeInterface { -type AttributeInput { - attribute_code: String - entity_type: String } -type CustomAttributeMetadata { - items: [Attribute] +type MultipleLineUiInputType implements UiInputTypeInterface, TextInputTypeInterface, FilterableTextInputTypeInterface, ValidationTextInputTypeInterface { + lines_count: Int } -type Attribute { - attribute_code: String - attribute_options: [AttributeOption] - attribute_type: String - entity_type: String - input_type: String +type DateUiInputType implements UiInputTypeInterface, TextInputTypeInterface, FilterableTextInputTypeInterface { + minimum_date_allowed: String + maximum_date_allowed: String } -type AttributeOption { +type FileUiInputType implements UiInputTypeInterface, TextInputTypeInterface, FilterableTextInputTypeInterface { + maximum_file_size: Int # bytes + allowed_file_extensions: [String] +} + +type ImageUiInputType implements UiInputTypeInterface, TextInputTypeInterface, FilterableTextInputTypeInterface { + maximum_file_size: Int # bytes + allowed_file_extensions: [String] + maximum_image_width: Int # in pixels + maximum_image_height: Int # in pixels +} + +type DropDownUiInputType implements UiInputTypeInterface, SelectableInputTypeInterface, AttributeOptionsInterface { +} + +type MultipleSelectUiInputType implements UiInputTypeInterface, SelectableInputTypeInterface, AttributeOptionsInterface { +} + +interface SwatchInputTypeInterface { + update_product_preview_image: Boolean +} + +type VisualSwatchUiInputType implements UiInputTypeInterface, SelectableInputTypeInterface, AttributeOptionsInterface, SwatchInputTypeInterface { + use_product_image_for_swatch_if_possible: Boolean +} + +type TextSwatchUiInputType implements UiInputTypeInterface, SelectableInputTypeInterface, AttributeOptionsInterface, SwatchInputTypeInterface { + +} + +type AttributeOption implements AttributeOptionInterface { + value: String @deprecated(reason: "use `uid` instead") label: String - value: String +} + +type ColorSwatchAttributeOption implements AttributeOptionInterface { + color: String # html hex code format +} + +type ImageSwatchAttributeOption implements AttributeOptionInterface { + image_path: String # relative path +} + +type TextSwatchAttributeOption implements AttributeOptionInterface { + description: String } ``` Additional fields should be added to the metadata response (`Attribute` type), for example `is_dynamic`, `use_in_compare_products`, `display_in_product_listing`, `use_in_advanced_search`, `advanced_search_input_type`. The exact list of fields must be discussed and approved separately. -Introduction of the following query will allow fetching lists of attributes applicable to specific pages: +See full schema [attributes-metadata.graphqls](attributes-metadata.graphqls) + +Introduction of the following query will allow fetching lists of attributes applicable to specific artifacts/listings: ```graphql -pageSpecificCustomAttributes( - page_type: CustomAttributesPageEnum +customAttributesLists( + listType: CustomAttributesListingsEnum ): CustomAttributeMetadata -enum CustomAttributesPageEnum { +enum CustomAttributesListingsEnum { PRODUCTS_COMPARE PRODUCTS_LISTING ADVANCED_CATALOG_SEARCH + PRODUCT_SORT + PRODUCT_FILTER + PRODUCT_SEARCH_RESULTS + PRODUCT_AGGREGATIONS + RMA_FORM + CUSTOMER_REGISTRATION_FORM + CUSTOMER_ADDRESS_FORM } ``` diff --git a/design-documents/graph-ql/coverage/custom-attributes/custom-attributes-container.md b/design-documents/graph-ql/coverage/custom-attributes/custom-attributes-container.md index 3b22cf763..afd20409d 100644 --- a/design-documents/graph-ql/coverage/custom-attributes/custom-attributes-container.md +++ b/design-documents/graph-ql/coverage/custom-attributes/custom-attributes-container.md @@ -8,13 +8,156 @@ One workaround for "getting all fields" is based on schema introspection, it all # Proposed solution -To account for dynamic nature of EAV attributes and the need of "getting all fields" in product search queries, `custom_attributes: [CustomAttribute]!` container will be introduced. +To account for dynamic nature of EAV attributes and the need of "getting all fields" in product search queries, we can introduce `custom_attributes: [CustomAttribute]!` container (recommended approach). +The container will return the list of attributes plus the actual values stored for each entity. ```graphql type CustomAttribute { + selected_attribute_options: [SelectedAttributeOption] # used to store unique options values + entered_attribute_value: EnteredAttributeValue # used to store the freetype entered values like texts + attribute_metadata: AttributeMetadataInterface # metadata of the actual attribute not related to the stored Entity-Attribute value +} + +type SelectedAttributeOption { + attribute_option: AttributeOptionInterface + attribute_metadata: AttributeMetadataInterface +} + +type EnteredAttributeValue { + attribute_metadata: AttributeMetadataInterface + value: String +} +``` + +We could also make value complex type to be able add more complex fields values in the future, but this doesn't seem necessary at this point. +This is also aligned with the Selected, Entered values from customizable options, or configurable product. + +As for the attribute we would define a concrete type for each entity, because each entity's attributes are different and values of attributes are also complext +For this reason interfaces on multiple levels are the best approach for composability purposes and satisfying values needs like swatches. + +```graphql +type EntityAttributeMetadata implements AttributeMetadataInterface, AttributeMetadataEntityTypeInterface, AttributeMetadataUiTypeInterface { + ui_input_type: UiInputTypeInterface! + forms_to_use_in: [CustomAttributesListingsEnum] +} +# -------- +interface AttributeMetadataInterface { # base metadata common to all attributes + uid: ID # base64Encode(entityID/codeID) + label: String + data_type: ObjectDataTypeEnum # string, int, float, boolean etc + sort_order: Int + entity_type: EntityTypeEnum + ui_input: UiInputTypeInterface! +} +``` +#### Sample queries for this alternative +```graphql +{ + customner { + custom_attributes { + selected_attribute_options { + attribute_option { + uid + is_default + ... on AttributeOption { + label + is_default + } + } + attribute_metadata { + uid + code + label + } + } + entered_attribute_value { + attribute_metadata { + uid + code + label + } + value + } + attribute_metadata { + uid + code + label # all attributes have a label per store + data_type # enum : string, int, float etc + sort_order # needed for ordering + + entity_type # enum + ... on CustomerAttributeMetadata { + forms_to_use_in # only in CustomerAttributeMetadata + } + ... on ProductAttributeMetadata { + lists_to_use_in # only in ProductAttributeMetadata. As opposed to Customer which only had forms + } + ui_input { + __typename + ui_input_type + is_value_required + ... on SelectableInputTypeInterface { + ... on SwatchInputTypeInterface { + update_product_preview_image + } + ... on VisualSwatchInputType { + use_product_image_for_swatch_if_possible + } + attribute_options { + uid + is_default + label + ... on ColorSwatchAttributeOption { + color + } + ... on ImageSwatchAttributeOption { + image_path + } + ... on TextSwatchAttributeOption { + description + } + } + } + ... on TextInputTypeInterface { + default_value # string + ... on FilterableTextInputTypeInterface { + filter # enum + } + ... on ValidationTextInputTypeInterface { + input_validation { + input_validation_type + minimum_text_length + maximum_text_length + } + } + } + } + } + } + } +} +``` + +### Alternatives considered +```graphql +type CustomAttribute { + code: String! + values: [String]! # We want to account fo attributes that have single (text, dropdown) and multiple values (checkbox, multiselect) +} +``` + +```graphql +interface CustomAttributeInterface { code: String! +} + +type SingleValueCustomAttribute implements CustomAttributeInterface { value: String! } + +type MultipleValuesCustomAttribute implements CustomAttributeInterface { + values: [String]! +} ``` Here `value` is JSON-encoded value of the custom attribute. @@ -23,7 +166,7 @@ Flat representation of custom attributes in `ProductInterface` (and other EAV en It is necessary to keep in mind that with `custom_attributes` it is not possible to query product EAV attributes selectively, which may lead to performance degradation. -### Sample queries +#### Sample queries for this alternative Current implementation allows the following query ```graphql @@ -42,7 +185,7 @@ Current implementation allows the following query Let's assume the response will be -```graphql +```json { "data": { "products": { @@ -51,14 +194,14 @@ Let's assume the response will be "name": "Test Simple Product", "sku": "testSimpleProduct", "color": "Red", - "manufacturer": "Company A" + "manufacturer": "Company A", "size": null }, { "name": "Test Configurable Product", "sku": "testConfigProduct", "color": null, - "manufacturer": "Company B" + "manufacturer": "Company B", "size": "XXL" } ] @@ -85,7 +228,7 @@ With the proposed changes the above mentioned queries will still be supported. I ``` Note that color and size are not applicable to some products in the search result. In the previous example they were returned as `null`. In the following example they are not returned at all -```graphql +```json { "data": { "products": { @@ -95,11 +238,11 @@ Note that color and size are not applicable to some products in the search resul "sku": "testSimpleProduct", "custom_attributes": [ { - "code": "color" + "code": "color", "value": "Red" }, { - "code": "manufacturer" + "code": "manufacturer", "value": "Company A" } ] @@ -109,22 +252,22 @@ Note that color and size are not applicable to some products in the search resul "sku": "testConfigProduct", "custom_attributes": [ { - "code": "manufacturer" + "code": "manufacturer", "value": "Company B" }, { - "code": "size" + "code": "size", "value": "XXL" } ] - }, + } ] } } } ``` -# Alternatives considered +### Other Alternatives considered 1. [Persisted queries](https://github.com/magento/graphql-ce/issues/781) can be leveraged to mitigate described issue with the increased size of the request. 1. To improve flexibility and allow support of complex structures, `type` can be added to the definition of `CustomAttribute` in the future, if there are valid use cases.