From 5aa7ef9dea63f87b1bea66acc47b420f24a878f1 Mon Sep 17 00:00:00 2001 From: Bharathidasan Elangovan Date: Wed, 11 Dec 2024 12:08:21 +0530 Subject: [PATCH 1/2] Changed filters of aggregations query in PLP --- .../Category/categoryContent.gql.js | 4 +- .../Category/useCategoryContent.js | 44 +++++++++++++++++-- .../Category/categoryContent.js | 3 +- .../components/FilterSidebar/filterSidebar.js | 10 ++++- 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/packages/peregrine/lib/talons/RootComponents/Category/categoryContent.gql.js b/packages/peregrine/lib/talons/RootComponents/Category/categoryContent.gql.js index 16e42557c4..12bc33fd42 100644 --- a/packages/peregrine/lib/talons/RootComponents/Category/categoryContent.gql.js +++ b/packages/peregrine/lib/talons/RootComponents/Category/categoryContent.gql.js @@ -2,9 +2,9 @@ import { gql } from '@apollo/client'; export const GET_PRODUCT_FILTERS_BY_CATEGORY = gql` query getProductFiltersByCategory( - $categoryIdFilter: FilterEqualTypeInput! + $filters: ProductAttributeFilterInput! ) { - products(filter: { category_uid: $categoryIdFilter }) { + products(filter: $filters) { aggregations { label count diff --git a/packages/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js b/packages/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js index fde9369c5a..03527a110b 100644 --- a/packages/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js +++ b/packages/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useLazyQuery, useQuery } from '@apollo/client'; import mergeOperations from '../../../util/shallowMerge'; @@ -39,6 +39,30 @@ export const useCategoryContent = props => { } ); + const [filterOptions, setFilterOptions] = useState(); + + const availableFilterData = filterData + ? filterData.products?.aggregations + : null; + const availableFilters = availableFilterData + ?.map(eachitem => eachitem.attribute_code) + ?.sort(); + const selectedFilters = {}; + + if (filterOptions) { + for (const [group, items] of filterOptions) { + availableFilters?.map(eachitem => { + if (eachitem === group) { + const sampleArray = []; + for (const item of items) { + sampleArray.push(item.value); + } + selectedFilters[group] = sampleArray; + } + }); + } + } + const [getSortMethods, { data: sortData }] = useLazyQuery( getCategoryAvailableSortMethodsQuery, { @@ -63,11 +87,22 @@ export const useCategoryContent = props => { useEffect(() => { if (categoryId) { + let dynamicFilters = { + category_uid: { eq: categoryId } + }; + Object.keys(selectedFilters).forEach(key => { + let filter = {}; + if (key === 'price') { + let [from, to] = selectedFilters[key][0].split('_'); + filter = { [key]: { from: from, to: to }}; + } else { + filter = { [key]: { in: selectedFilters[key]}}; + } + dynamicFilters = { ...dynamicFilters, ...filter }; + }); getFilters({ variables: { - categoryIdFilter: { - eq: categoryId - } + filters: dynamicFilters } }); } @@ -122,6 +157,7 @@ export const useCategoryContent = props => { categoryName, categoryDescription, filters, + setFilterOptions, items, totalCount, totalPagesFromData diff --git a/packages/venia-ui/lib/RootComponents/Category/categoryContent.js b/packages/venia-ui/lib/RootComponents/Category/categoryContent.js index c3cbd4eb2e..d47c763499 100644 --- a/packages/venia-ui/lib/RootComponents/Category/categoryContent.js +++ b/packages/venia-ui/lib/RootComponents/Category/categoryContent.js @@ -50,6 +50,7 @@ const CategoryContent = props => { categoryName, categoryDescription, filters, + setFilterOptions, items, totalCount, totalPagesFromData @@ -79,7 +80,7 @@ const CategoryContent = props => { ) : null; const sidebar = shouldShowFilterButtons ? ( - + ) : shouldShowFilterShimmer ? ( ) : null; diff --git a/packages/venia-ui/lib/components/FilterSidebar/filterSidebar.js b/packages/venia-ui/lib/components/FilterSidebar/filterSidebar.js index 4fa266eb8a..59a564a2d8 100644 --- a/packages/venia-ui/lib/components/FilterSidebar/filterSidebar.js +++ b/packages/venia-ui/lib/components/FilterSidebar/filterSidebar.js @@ -1,4 +1,4 @@ -import React, { useMemo, useCallback, useRef } from 'react'; +import React, { useMemo, useCallback, useRef, useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; import { array, arrayOf, shape, string, number } from 'prop-types'; import { useFilterSidebar } from '@magento/peregrine/lib/talons/FilterSidebar'; @@ -17,7 +17,7 @@ const SCROLL_OFFSET = 150; * @param {Object} props.filters - filters to display */ const FilterSidebar = props => { - const { filters, filterCountToOpen } = props; + const { filters, filterCountToOpen, setFilterOptions } = props; const talonProps = useFilterSidebar({ filters }); const { filterApi, @@ -50,6 +50,12 @@ const FilterSidebar = props => { [handleApply, filterRef] ); + useEffect(() => { + if (filterState) { + setFilterOptions(filterState); + } + }, [filterState, setFilterOptions]); + const filtersList = useMemo( () => Array.from(filterItems, ([group, items], iteration) => { From a52f25981ddeb8b57fbac22493641bdf3f9d3393 Mon Sep 17 00:00:00 2001 From: Bharathidasan Elangovan Date: Thu, 19 Dec 2024 15:05:14 +0530 Subject: [PATCH 2/2] Refactored code to fix lint,prettier and test script issues --- .../useCategoryContent.spec.js.snap | 4 + .../__tests__/useCategoryContent.spec.js | 16 +- .../Category/categoryContent.gql.js | 4 +- .../Category/useCategoryContent.js | 144 +++++++++++++----- .../__tests__/filterSidebar.spec.js | 14 +- 5 files changed, 132 insertions(+), 50 deletions(-) diff --git a/packages/peregrine/lib/talons/RootComponents/Category/__tests__/__snapshots__/useCategoryContent.spec.js.snap b/packages/peregrine/lib/talons/RootComponents/Category/__tests__/__snapshots__/useCategoryContent.spec.js.snap index 8aa379def7..9ddebf6b30 100644 --- a/packages/peregrine/lib/talons/RootComponents/Category/__tests__/__snapshots__/useCategoryContent.spec.js.snap +++ b/packages/peregrine/lib/talons/RootComponents/Category/__tests__/__snapshots__/useCategoryContent.spec.js.snap @@ -5,6 +5,7 @@ Object { "availableSortMethods": null, "categoryDescription": "Jewelry category", "categoryName": "Jewelry", + "filterOptions": undefined, "filters": null, "items": Array [ null, @@ -17,6 +18,7 @@ Object { null, null, ], + "setFilterOptions": [Function], "totalCount": null, "totalPagesFromData": null, } @@ -32,6 +34,7 @@ Object { ], "categoryDescription": "Jewelry category", "categoryName": "Jewelry", + "filterOptions": undefined, "filters": Array [ Object { "label": "Label", @@ -47,6 +50,7 @@ Object { "name": "Necklace", }, ], + "setFilterOptions": [Function], "totalCount": 2, "totalPagesFromData": 1, } diff --git a/packages/peregrine/lib/talons/RootComponents/Category/__tests__/useCategoryContent.spec.js b/packages/peregrine/lib/talons/RootComponents/Category/__tests__/useCategoryContent.spec.js index d22cfd685a..7f3fd22ac6 100644 --- a/packages/peregrine/lib/talons/RootComponents/Category/__tests__/useCategoryContent.spec.js +++ b/packages/peregrine/lib/talons/RootComponents/Category/__tests__/useCategoryContent.spec.js @@ -26,6 +26,18 @@ jest.mock('@apollo/client', () => { }; }); +const mockGetFiltersAttributeCode = { + data: { + products: { + aggregations: [ + { + label: 'Label' + } + ] + } + } +}; + const mockProductFiltersByCategoryData = { data: { products: { @@ -91,6 +103,7 @@ const mockCategoryData = { const mockGetSortMethods = jest.fn(); const mockGetFilters = jest.fn(); +const mockfilterData = jest.fn(); jest.mock('@magento/peregrine/lib/context/eventing', () => ({ useEventingContext: jest.fn().mockReturnValue([{}, { dispatch: jest.fn() }]) @@ -98,7 +111,6 @@ jest.mock('@magento/peregrine/lib/context/eventing', () => ({ const Component = props => { const talonprops = useCategoryContent(props); - return ; }; @@ -106,6 +118,7 @@ useQuery.mockReturnValue({ data: mockCategoryData }); describe('useCategoryContent tests', () => { it('returns the proper shape', () => { useLazyQuery + .mockReturnValueOnce([mockfilterData, mockGetFiltersAttributeCode]) .mockReturnValueOnce([ mockGetFilters, mockProductFiltersByCategoryData @@ -124,6 +137,7 @@ describe('useCategoryContent tests', () => { it('handles default category id', () => { useLazyQuery + .mockReturnValueOnce([mockfilterData, mockGetFiltersAttributeCode]) .mockReturnValueOnce([ mockGetFilters, mockProductFiltersByCategoryData diff --git a/packages/peregrine/lib/talons/RootComponents/Category/categoryContent.gql.js b/packages/peregrine/lib/talons/RootComponents/Category/categoryContent.gql.js index 12bc33fd42..eb7a7e36fa 100644 --- a/packages/peregrine/lib/talons/RootComponents/Category/categoryContent.gql.js +++ b/packages/peregrine/lib/talons/RootComponents/Category/categoryContent.gql.js @@ -1,9 +1,7 @@ import { gql } from '@apollo/client'; export const GET_PRODUCT_FILTERS_BY_CATEGORY = gql` - query getProductFiltersByCategory( - $filters: ProductAttributeFilterInput! - ) { + query getProductFiltersByCategory($filters: ProductAttributeFilterInput!) { products(filter: $filters) { aggregations { label diff --git a/packages/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js b/packages/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js index 03527a110b..659fdb400a 100644 --- a/packages/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js +++ b/packages/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useMemo } from 'react'; import { useLazyQuery, useQuery } from '@apollo/client'; import mergeOperations from '../../../util/shallowMerge'; @@ -29,39 +29,101 @@ export const useCategoryContent = props => { getCategoryAvailableSortMethodsQuery } = operations; - const placeholderItems = Array.from({ length: pageSize }).fill(null); + const [ + getFiltersAttributeCode, + { data: filterAttributeData } + ] = useLazyQuery(getProductFiltersByCategoryQuery, { + fetchPolicy: 'cache-and-network', + nextFetchPolicy: 'cache-first' + }); - const [getFilters, { data: filterData }] = useLazyQuery( - getProductFiltersByCategoryQuery, - { - fetchPolicy: 'cache-and-network', - nextFetchPolicy: 'cache-first' + useEffect(() => { + if (categoryId) { + getFiltersAttributeCode({ + variables: { + filters: { + category_uid: { eq: categoryId } + } + } + }); } - ); + }, [categoryId, getFiltersAttributeCode]); - const [filterOptions, setFilterOptions] = useState(); - - const availableFilterData = filterData - ? filterData.products?.aggregations + const availableFilterData = filterAttributeData + ? filterAttributeData.products?.aggregations : null; const availableFilters = availableFilterData ?.map(eachitem => eachitem.attribute_code) ?.sort(); - const selectedFilters = {}; - - if (filterOptions) { - for (const [group, items] of filterOptions) { - availableFilters?.map(eachitem => { - if (eachitem === group) { - const sampleArray = []; - for (const item of items) { - sampleArray.push(item.value); + + const handlePriceFilter = priceFilter => { + if (priceFilter && priceFilter.size > 0) { + for (const price of priceFilter) { + const [from, to] = price.value.split('_'); + return { price: { from, to } }; + } + } + return {}; + }; + + const [filterOptions, setFilterOptions] = useState(); + + const selectedFilters = useMemo(() => { + const filters = {}; + if (filterOptions) { + for (const [group, items] of filterOptions.entries()) { + availableFilters?.map(eachitem => { + if (eachitem === group && group !== 'price') { + const sampleArray = []; + for (const item of items) { + sampleArray.push(item.value); + } + filters[group] = sampleArray; } - selectedFilters[group] = sampleArray; + }); + } + } + + if (filterOptions && filterOptions.has('price')) { + const priceFilter = filterOptions.get('price'); + const priceRange = handlePriceFilter(priceFilter); + if (priceRange.price) { + filters.price = priceRange.price; + } + } + + return filters; + }, [filterOptions, availableFilters]); + + const dynamicQueryVariables = useMemo(() => { + const generateDynamicFiltersQuery = filterParams => { + let filterConditions = { + category_uid: { eq: categoryId } + }; + + Object.keys(filterParams).forEach(key => { + let filter = {}; + if (key !== 'price') { + filter = { [key]: { in: filterParams[key] } }; } + filterConditions = { ...filterConditions, ...filter }; }); + + return filterConditions; + }; + + return generateDynamicFiltersQuery(selectedFilters); + }, [selectedFilters, categoryId]); + + const placeholderItems = Array.from({ length: pageSize }).fill(null); + + const [getFilters, { data: filterData }] = useLazyQuery( + getProductFiltersByCategoryQuery, + { + fetchPolicy: 'cache-and-network', + nextFetchPolicy: 'cache-first' } - } + ); const [getSortMethods, { data: sortData }] = useLazyQuery( getCategoryAvailableSortMethodsQuery, @@ -84,29 +146,26 @@ export const useCategoryContent = props => { ); const [, { dispatch }] = useEventingContext(); - + const [previousFilters, setPreviousFilters] = useState(null); useEffect(() => { - if (categoryId) { - let dynamicFilters = { - category_uid: { eq: categoryId } - }; - Object.keys(selectedFilters).forEach(key => { - let filter = {}; - if (key === 'price') { - let [from, to] = selectedFilters[key][0].split('_'); - filter = { [key]: { from: from, to: to }}; - } else { - filter = { [key]: { in: selectedFilters[key]}}; - } - dynamicFilters = { ...dynamicFilters, ...filter }; - }); + if ( + categoryId && + JSON.stringify(selectedFilters) !== JSON.stringify(previousFilters) + ) { getFilters({ variables: { - filters: dynamicFilters + filters: dynamicQueryVariables } }); + setPreviousFilters(selectedFilters); } - }, [categoryId, getFilters]); + }, [ + categoryId, + selectedFilters, + dynamicQueryVariables, + previousFilters, + getFilters + ]); useEffect(() => { if (categoryId) { @@ -120,7 +179,7 @@ export const useCategoryContent = props => { } }, [categoryId, getSortMethods]); - const filters = filterData ? filterData.products.aggregations : null; + const filters = filterData ? filterData.products?.aggregations : null; const items = data ? data.products.items : placeholderItems; const totalPagesFromData = data ? data.products.page_info.total_pages @@ -139,7 +198,7 @@ export const useCategoryContent = props => { : null; useEffect(() => { - if (!categoryLoading && categoryData.categories.items.length > 0) { + if (!categoryLoading && categoryData?.categories.items.length > 0) { dispatch({ type: 'CATEGORY_PAGE_VIEW', payload: { @@ -157,6 +216,7 @@ export const useCategoryContent = props => { categoryName, categoryDescription, filters, + filterOptions, setFilterOptions, items, totalCount, diff --git a/packages/venia-ui/lib/components/FilterSidebar/__tests__/filterSidebar.spec.js b/packages/venia-ui/lib/components/FilterSidebar/__tests__/filterSidebar.spec.js index 9fafd2bbf0..2c4e635dad 100644 --- a/packages/venia-ui/lib/components/FilterSidebar/__tests__/filterSidebar.spec.js +++ b/packages/venia-ui/lib/components/FilterSidebar/__tests__/filterSidebar.spec.js @@ -51,6 +51,8 @@ const mockHandleApply = jest.fn(); const mockScrollTo = jest.fn(); +const mockFilterOptions = jest.fn(); + const mockGetBoundingClientRect = jest.fn(); let mockFilterState; @@ -125,7 +127,8 @@ const Component = () => { const givenDefaultValues = () => { inputProps = { - filters: [] + filters: [], + setFilterOptions: mockFilterOptions }; mockFilterState = new Map(); @@ -133,13 +136,15 @@ const givenDefaultValues = () => { const givenFilters = () => { inputProps = { - filters: mockFilters + filters: mockFilters, + setFilterOptions: mockFilterOptions }; }; const givenSelectedFilters = () => { inputProps = { - filters: mockFilters + filters: mockFilters, + setFilterOptions: mockFilterOptions }; mockFilterState = new Map([['group', 'item']]); @@ -148,7 +153,8 @@ const givenSelectedFilters = () => { const givenFiltersAndAmountToShow = () => { inputProps = { filters: mockFilters, - filterCountToOpen: mockFiltersOpenCount + filterCountToOpen: mockFiltersOpenCount, + setFilterOptions: mockFilterOptions }; };