Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PWA-1101] Add support for Configurable Product Image Setting #2909

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a3571bd
PWA-1101: Add support for Configurable Product Image Setting
eug123 Nov 18, 2020
9dd3440
Merge remote-tracking branch 'mainline/develop' into PWA-1101
eug123 Dec 7, 2020
e3473b3
Merge remote-tracking branch 'mainline/develop' into PWA-1101
eug123 Dec 8, 2020
62c5f31
Merge remote-tracking branch 'mainline/develop' into PWA-1101
eug123 Dec 9, 2020
9761eec
PWA-1101: Add support for Configurable Product Image Setting
eug123 Dec 10, 2020
c177fdf
PWA-1101: Add support for Configurable Product Image Setting
eug123 Dec 15, 2020
a13ee69
PWA-1101: Add support for Configurable Product Image Setting
eug123 Dec 16, 2020
9ed01f9
PWA-1101: Add support for Configurable Product Image Setting
eug123 Dec 17, 2020
df03d74
Merge remote-tracking branch 'mainline/develop' into PWA-1101-alt
eug123 Dec 17, 2020
3e4a315
PWA-1101: Add support for Configurable Product Image Setting
eug123 Dec 18, 2020
e5d0883
Merge remote-tracking branch 'mainline/develop' into PWA-1101-alt
eug123 Dec 18, 2020
2e47d29
PWA-1101: Add support for Configurable Product Image Setting
eug123 Dec 18, 2020
4832fec
PWA-1101: Add support for Configurable Product Image Setting
eug123 Dec 21, 2020
4f42be9
Merge remote-tracking branch 'mainline/develop' into PWA-1101-alt
eug123 Dec 21, 2020
fbde172
PWA-1101: Add support for Configurable Product Image Setting
eug123 Dec 22, 2020
c87f224
PWA-1101: Add support for Configurable Product Image Setting
eug123 Dec 23, 2020
54690fb
Merge remote-tracking branch 'mainline/develop' into PWA-1101-alt
eug123 Dec 23, 2020
684e775
PWA-1101: Add support for Configurable Product Image Setting
eug123 Jan 7, 2021
94ed636
Merge remote-tracking branch 'mainline/develop' into PWA-1101-alt
eug123 Jan 7, 2021
d86361c
PWA-1101: Add support for Configurable Product Image Setting
eug123 Jan 7, 2021
2fa663a
Merge branch 'develop' into PWA-1101-alt
dpatil-magento Jan 19, 2021
6da2ab0
Merge branch 'develop' into PWA-1101-alt
dpatil-magento Jan 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions packages/peregrine/lib/Apollo/policies/temporary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Custom type policies that allow us to have more granular control
* over how ApolloClient reads from and writes to the cache.
*
* https://www.apollographql.com/docs/react/caching/cache-configuration/#typepolicy-fields
* https://www.apollographql.com/docs/react/caching/cache-field-behavior/
*/
const temporaryTypePolicies = {
StoreConfig: {
fields: {
// This field is available in Magento 2.4.2 we need to support older versions, so env var is used here
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we support n now, instead of n-1, do we need to mock this? By the time this code is released in PWA Studio, 2.4.2 should be released so I think this is fine to remove.

configurable_thumbnail_source: {
read() {
return process.env.CONFIGURABLE_THUMBNAIL_SOURCE;
}
}
}
},
ConfigurableCartItem: {
fields: {
// This field is proposed in the https://github.com/magento/magento2/pull/30817
configured_variant: {
read(_, { readField, toReference }) {
const product = readField('product');
const optionUids = readField('configurable_options')
.map(option => {
const id = readField(
'id',
toReference(option.__ref)
);
const value_id = readField(
'value_id',
toReference(option.__ref)
);
return new Buffer(
`configurable/${id}/${value_id}`
).toString('base64');
})
.sort()
.toString();

return readField('variants', product)
.map(variant => {
const variantUids = variant.attributes
.map(attribute => attribute.uid)
.sort()
.toString();
return (
variantUids === optionUids && variant.product
);
})
.filter(Boolean)[0];
}
}
}
}
};

export default temporaryTypePolicies;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { gql } from '@apollo/client';

// We disable linting for local fields because there is no way to add them to
// the fetched schema.
// https://github.com/apollographql/eslint-plugin-graphql/issues/99
/* eslint-disable graphql/template-strings */
export const GET_CONFIGURABLE_THUMBNAIL_SOURCE = gql`
query getConfigurableThumbnailSource {
storeConfig {
id
configurable_thumbnail_source @client
}
}
`;

export default {
getConfigurableThumbnailSource: GET_CONFIGURABLE_THUMBNAIL_SOURCE
};
46 changes: 35 additions & 11 deletions packages/peregrine/lib/talons/CartPage/ProductListing/useProduct.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useMutation } from '@apollo/client';
import { useMutation, useQuery } from '@apollo/client';
import { useCartContext } from '@magento/peregrine/lib/context/cart';
import { deriveErrorMessage } from '../../../util/deriveErrorMessage';
import mergeOperations from '../../../util/shallowMerge';
import DEFAULT_OPERATIONS from './product.gql';

/**
* This talon contains logic for a product component used in a product listing component.
Expand All @@ -15,7 +17,7 @@ import { deriveErrorMessage } from '../../../util/deriveErrorMessage';
*
* @param {Object} props
* @param {ProductItem} props.item Product item data
* @param {ProductMutations} props.mutations GraphQL mutations for a product in a cart
* @param {ProductMutations} props.operations GraphQL mutations for a product in a cart
* @param {function} props.setActiveEditItem Function for setting the actively editing item
* @param {function} props.setIsCartUpdating Function for setting the updating state of the cart
*
Expand All @@ -24,15 +26,32 @@ import { deriveErrorMessage } from '../../../util/deriveErrorMessage';
* @example <caption>Importing into your project</caption>
* import { useProduct } from '@magento/peregrine/lib/talons/CartPage/ProductListing/useProduct';
*/

export const useProduct = props => {
const { item, setActiveEditItem, setIsCartUpdating } = props;

const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
const {
item,
mutations: { removeItemMutation, updateItemQuantityMutation },
setActiveEditItem,
setIsCartUpdating
} = props;
removeItemMutation,
updateItemQuantityMutation,
getConfigurableThumbnailSource
} = operations;

const flatProduct = flattenProduct(item);
const { data: getConfigurableThumbnailSourceData } = useQuery(
eug123 marked this conversation as resolved.
Show resolved Hide resolved
getConfigurableThumbnailSource,
{
fetchPolicy: 'cache-and-network'
}
);

const configurableThumbnailSource = useMemo(() => {
if (getConfigurableThumbnailSourceData) {
return getConfigurableThumbnailSourceData.storeConfig
.configurable_thumbnail_source;
}
}, [getConfigurableThumbnailSourceData]);

const flatProduct = flattenProduct(item, configurableThumbnailSource);

const [
removeItem,
Expand Down Expand Up @@ -141,12 +160,13 @@ export const useProduct = props => {
};
};

const flattenProduct = item => {
const flattenProduct = (item, configurableThumbnailSource) => {
const {
configurable_options: options = [],
prices,
product,
quantity
quantity,
configured_variant
} = item;

const { price } = prices;
Expand All @@ -159,7 +179,11 @@ const flattenProduct = item => {
url_key: urlKey,
url_suffix: urlSuffix
} = product;
const { url: image } = small_image;
const { url: image } =
(configurableThumbnailSource === 'itself' &&
configured_variant &&
configured_variant.small_image) ||
eug123 marked this conversation as resolved.
Show resolved Hide resolved
small_image;

return {
currency,
Expand Down
18 changes: 18 additions & 0 deletions packages/peregrine/lib/talons/MiniCart/miniCart.gql.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { gql } from '@apollo/client';

// We disable linting for local fields because there is no way to add them to
// the fetched schema.
// https://github.com/apollographql/eslint-plugin-graphql/issues/99
/* eslint-disable graphql/template-strings */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here re: re-enabling this lint rule, although you may be able to just remove @client.

export const GET_CONFIGURABLE_THUMBNAIL_SOURCE = gql`
query getConfigurableThumbnailSource {
storeConfig {
id
configurable_thumbnail_source @client
}
}
`;

export default {
getConfigurableThumbnailSource: GET_CONFIGURABLE_THUMBNAIL_SOURCE
};
35 changes: 29 additions & 6 deletions packages/peregrine/lib/talons/MiniCart/useMiniCart.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { useQuery, useMutation } from '@apollo/client';

import { useCartContext } from '../../context/cart';
import { deriveErrorMessage } from '../../util/deriveErrorMessage';
import mergeOperations from '../../util/shallowMerge';
import DEFAULT_OPERATIONS from './miniCart.gql';

/**
*
* @param {Function} props.setIsOpen - Function to toggle the mini cart
* @param {DocumentNode} props.queries.miniCartQuery - Query to fetch mini cart data
* @param {DocumentNode} props.mutations.removeItemMutation - Mutation to remove an item from cart
* @param {DocumentNode} props.operations.miniCartQuery - Query to fetch mini cart data
* @param {DocumentNode} props.operations.removeItemMutation - Mutation to remove an item from cart
*
* @returns {
* closeMiniCart: Function,
Expand All @@ -21,12 +23,18 @@ import { deriveErrorMessage } from '../../util/deriveErrorMessage';
* productList: Array<>,
* subTotal: Number,
* totalQuantity: Number
* configurableThumbnailSource: String
* }
*/
export const useMiniCart = props => {
const { setIsOpen, queries, mutations } = props;
const { miniCartQuery } = queries;
const { removeItemMutation } = mutations;
const { setIsOpen } = props;

const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
const {
removeItemMutation,
miniCartQuery,
getConfigurableThumbnailSource
} = operations;

const [{ cartId }] = useCartContext();
const history = useHistory();
Expand All @@ -41,6 +49,20 @@ export const useMiniCart = props => {
}
);

const { data: getConfigurableThumbnailSourceData } = useQuery(
eug123 marked this conversation as resolved.
Show resolved Hide resolved
getConfigurableThumbnailSource,
{
fetchPolicy: 'cache-and-network'
}
);

const configurableThumbnailSource = useMemo(() => {
if (getConfigurableThumbnailSourceData) {
return getConfigurableThumbnailSourceData.storeConfig
.configurable_thumbnail_source;
}
}, [getConfigurableThumbnailSourceData]);

const [
removeItem,
{
Expand Down Expand Up @@ -112,6 +134,7 @@ export const useMiniCart = props => {
loading: miniCartLoading || (removeItemCalled && removeItemLoading),
productList,
subTotal,
totalQuantity
totalQuantity,
configurableThumbnailSource
};
};
31 changes: 30 additions & 1 deletion packages/peregrine/lib/talons/OrderHistoryPage/orderRow.gql.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { gql } from '@apollo/client';

// We disable linting for local fields because there is no way to add them to
// the fetched schema.
// https://github.com/apollographql/eslint-plugin-graphql/issues/99
/* eslint-disable graphql/template-strings */
export const GET_CONFIGURABLE_THUMBNAIL_SOURCE = gql`
query getConfigurableThumbnailSource {
storeConfig {
id
configurable_thumbnail_source @client
}
}
`;

sirugh marked this conversation as resolved.
Show resolved Hide resolved
export const GET_PRODUCT_THUMBNAILS_BY_URL_KEY = gql`
query GetProductThumbnailsByURLKey($urlKeys: [String!]!) {
products(filter: { url_key: { in: $urlKeys } }) {
Expand All @@ -12,11 +25,27 @@ export const GET_PRODUCT_THUMBNAILS_BY_URL_KEY = gql`
}
url_key
url_suffix
... on ConfigurableProduct {
variants {
attributes {
uid
}
product {
sku
id
thumbnail {
label
url
}
}
}
}
}
}
}
`;

export default {
getProductThumbnailsQuery: GET_PRODUCT_THUMBNAILS_BY_URL_KEY
getProductThumbnailsQuery: GET_PRODUCT_THUMBNAILS_BY_URL_KEY,
getConfigurableThumbnailSource: GET_CONFIGURABLE_THUMBNAIL_SOURCE
};
49 changes: 42 additions & 7 deletions packages/peregrine/lib/talons/OrderHistoryPage/useOrderRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import DEFAULT_OPERATIONS from './orderRow.gql';
export const useOrderRow = props => {
const { items } = props;
const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
const { getProductThumbnailsQuery } = operations;
const {
getProductThumbnailsQuery,
getConfigurableThumbnailSource
} = operations;

const urlKeys = useMemo(() => {
return items.map(item => item.product_url_key).sort();
Expand All @@ -29,17 +32,49 @@ export const useOrderRow = props => {
urlKeys
}
});

const { data: getConfigurableThumbnailSourceData } = useQuery(
eug123 marked this conversation as resolved.
Show resolved Hide resolved
getConfigurableThumbnailSource,
{
fetchPolicy: 'cache-and-network'
}
);

const configurableThumbnailSource = useMemo(() => {
if (getConfigurableThumbnailSourceData) {
return getConfigurableThumbnailSourceData.storeConfig
.configurable_thumbnail_source;
}
}, [getConfigurableThumbnailSourceData]);

const imagesData = useMemo(() => {
if (data) {
// filter out items returned that we didn't query for
const filteredItems = data.products.items.filter(item =>
urlKeys.includes(item.url_key)
);
return filteredItems;
// Images data is taken from simple product or from configured variant and assigned to item sku
const mappedImagesData = [];
items.forEach(item => {
const product = data.products.items.find(
element => item.product_url_key === element.url_key
);
if (
configurableThumbnailSource === 'itself' &&
product.variants &&
product.variants.length > 0
) {
const foundVariant = product.variants.find(variant => {
return variant.product.sku === item.product_sku;
});
mappedImagesData[item.product_sku] =
foundVariant && foundVariant.product;
eug123 marked this conversation as resolved.
Show resolved Hide resolved
} else {
mappedImagesData[item.product_sku] = product;
}
});

return mappedImagesData;
} else {
return [];
}
}, [data, urlKeys]);
}, [data, items, configurableThumbnailSource]);

const [isOpen, setIsOpen] = useState(false);

Expand Down
6 changes: 6 additions & 0 deletions packages/pwa-buildpack/envVarDefinitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
"type": "bool",
"desc": "Includes the store code in the store URLs if value is true.",
"default": false
},
{
"name": "CONFIGURABLE_THUMBNAIL_SOURCE",
"type": "str",
"desc": "Configurable Product Image to determine the thumbnail that is used in the cart for configurable products. Possible values: `parent`, `itself`",
"default": "itself"
}
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const Product = props => {
const { formatMessage } = useIntl();
const talonProps = useProduct({
item,
mutations: {
operations: {
removeItemMutation: REMOVE_ITEM_MUTATION,
updateItemQuantityMutation: UPDATE_QUANTITY_MUTATION
},
Expand Down
Loading