From fec476b7e2bdcf390c3bee5fd30f804fe6ef82aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=BBuraw?= <9116238+krzysztofzuraw@users.noreply.github.com> Date: Wed, 5 Jul 2023 09:21:32 +0200 Subject: [PATCH] Experimental filters: Add API types and handlers (#3841) --- .changeset/silent-hornets-sell.md | 5 + .../ConditionalFilter/API/Handler.ts | 168 +++++++++++++ .../ConditionalFilter/API/getAPIOptions.tsx | 226 ++++-------------- .../FilterElement/ConditionOptions.ts | 6 +- .../FilterElement/ConditionSelected.ts | 4 - src/components/ConditionalFilter/index.tsx | 10 +- .../ConditionalFilter/useLeftOperands.ts | 8 +- 7 files changed, 236 insertions(+), 191 deletions(-) create mode 100644 .changeset/silent-hornets-sell.md create mode 100644 src/components/ConditionalFilter/API/Handler.ts diff --git a/.changeset/silent-hornets-sell.md b/.changeset/silent-hornets-sell.md new file mode 100644 index 000000000..26fe90f98 --- /dev/null +++ b/.changeset/silent-hornets-sell.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": minor +--- + +Add API handlers to expermiental filters diff --git a/src/components/ConditionalFilter/API/Handler.ts b/src/components/ConditionalFilter/API/Handler.ts new file mode 100644 index 000000000..fefba6597 --- /dev/null +++ b/src/components/ConditionalFilter/API/Handler.ts @@ -0,0 +1,168 @@ +import { ApolloClient } from "@apollo/client"; +import { + _GetAttributeChoicesDocument, + _GetAttributeChoicesQuery, + _GetAttributeChoicesQueryVariables, + _GetCategoriesChoicesDocument, + _GetCategoriesChoicesQuery, + _GetCategoriesChoicesQueryVariables, + _GetChannelOperandsDocument, + _GetChannelOperandsQuery, + _GetChannelOperandsQueryVariables, + _GetCollectionsChoicesDocument, + _GetCollectionsChoicesQuery, + _GetCollectionsChoicesQueryVariables, + _GetDynamicLeftOperandsDocument, + _GetDynamicLeftOperandsQuery, + _GetDynamicLeftOperandsQueryVariables, + _GetProductTypesChoicesDocument, + _GetProductTypesChoicesQuery, + _GetProductTypesChoicesQueryVariables, +} from "@dashboard/graphql"; + +import { ItemOption } from "../FilterElement/ConditionSelected"; + +export interface Handler { + client: ApolloClient; + query: string; + fetch: () => Promise; +} + +const createOptionsFromAPI = ( + // TODO: try to use type from graphql + data: Array<{ node: { name: string | null; id: string; slug: string } }>, +): ItemOption[] => + data.map(({ node }) => ({ + label: node.name ?? "", + value: node.id, + slug: node.slug, + })); + +export class AttributeChoicesHandler implements Handler { + constructor( + public client: ApolloClient, + public attributeSlug: string, + public query: string, + ) {} + + fetch = async () => { + const { client, attributeSlug, query } = this; + const { data } = await client.query< + _GetAttributeChoicesQuery, + _GetAttributeChoicesQueryVariables + >({ + query: _GetAttributeChoicesDocument, + variables: { + slug: attributeSlug, + first: 5, + query, + }, + }); + return createOptionsFromAPI(data.attribute?.choices?.edges ?? []); + }; +} + +export class CollectionHandler implements Handler { + constructor(public client: ApolloClient, public query: string) {} + + fetch = async () => { + const { data } = await this.client.query< + _GetCollectionsChoicesQuery, + _GetCollectionsChoicesQueryVariables + >({ + query: _GetCollectionsChoicesDocument, + variables: { + first: 5, + query: this.query, + }, + }); + + return createOptionsFromAPI(data.collections?.edges ?? []); + }; +} + +export class CategoryHandler implements Handler { + constructor(public client: ApolloClient, public query: string) {} + + fetch = async () => { + const { data } = await this.client.query< + _GetCategoriesChoicesQuery, + _GetCategoriesChoicesQueryVariables + >({ + query: _GetCategoriesChoicesDocument, + variables: { + first: 5, + query: this.query, + }, + }); + + return createOptionsFromAPI(data.categories?.edges ?? []); + }; +} + +export class ProductTypeHandler implements Handler { + constructor(public client: ApolloClient, public query: string) {} + + fetch = async () => { + const { data } = await this.client.query< + _GetProductTypesChoicesQuery, + _GetProductTypesChoicesQueryVariables + >({ + query: _GetProductTypesChoicesDocument, + variables: { + first: 5, + query: this.query, + }, + }); + + return createOptionsFromAPI(data.productTypes?.edges ?? []); + }; +} + +export class ChannelHandler implements Handler { + constructor(public client: ApolloClient, public query: string) {} + + fetch = async () => { + const { data } = await this.client.query< + _GetChannelOperandsQuery, + _GetChannelOperandsQueryVariables + >({ + query: _GetChannelOperandsDocument, + }); + const options = + data.channels?.map(({ id, name, slug }) => ({ + label: name, + value: id, + slug, + })) ?? []; + + return options.filter(({ label }) => + label.toLowerCase().includes(this.query.toLowerCase()), + ); + }; +} + +export class AttributesHandler implements Handler { + constructor(public client: ApolloClient, public query: string) {} + + fetch = async () => { + const { data } = await this.client.query< + _GetDynamicLeftOperandsQuery, + _GetDynamicLeftOperandsQueryVariables + >({ + query: _GetDynamicLeftOperandsDocument, + variables: { + first: 5, + query: this.query, + }, + }); + return ( + data.attributes?.edges.map(({ node }) => ({ + label: node.name ?? "", + value: node.id, + type: node.inputType, + slug: node.slug ?? "", + })) ?? [] + ); + }; +} diff --git a/src/components/ConditionalFilter/API/getAPIOptions.tsx b/src/components/ConditionalFilter/API/getAPIOptions.tsx index 42101f08d..87c03b666 100644 --- a/src/components/ConditionalFilter/API/getAPIOptions.tsx +++ b/src/components/ConditionalFilter/API/getAPIOptions.tsx @@ -1,16 +1,16 @@ -// @ts-strict-ignore import { ApolloClient } from "@apollo/client"; -import { - _GetAttributeChoicesDocument, - _GetCategoriesChoicesDocument, - _GetChannelOperandsDocument, - _GetCollectionsChoicesDocument, - _GetDynamicLeftOperandsDocument, - _GetProductTypesChoicesDocument, -} from "@dashboard/graphql"; import { FilterElement } from "../FilterElement"; import { FilterContainer } from "../useFilterContainer"; +import { + AttributeChoicesHandler, + AttributesHandler, + CategoryHandler, + ChannelHandler, + CollectionHandler, + Handler, + ProductTypeHandler, +} from "./Handler"; const getFilterElement = (value: any, index: number): FilterElement => { const possibleFilterElement = value[index]; @@ -19,193 +19,67 @@ const getFilterElement = (value: any, index: number): FilterElement => { : null; }; +const createAPIHandler = ( + selectedRow: FilterElement, + client: ApolloClient, + inputValue: string, +): Handler => { + if (selectedRow.isAttribute()) { + return new AttributeChoicesHandler( + client, + selectedRow.value.value, + inputValue, + ); + } + + if (selectedRow.isCollection()) { + return new CollectionHandler(client, inputValue); + } + + if (selectedRow.isCategory()) { + return new CategoryHandler(client, inputValue); + } + + if (selectedRow.isProductType()) { + return new ProductTypeHandler(client, inputValue); + } + + if (selectedRow.isChannel()) { + return new ChannelHandler(client, inputValue); + } + + throw new Error("Unknown filter element"); +}; + export const getInitialRightOperatorOptions = async ( - client: ApolloClient, + client: ApolloClient, position: string, value: FilterContainer, ) => { const index = parseInt(position, 10); const filterElement = getFilterElement(value, index); + const handler = createAPIHandler(filterElement, client, ""); - if (filterElement.isAttribute()) { - const { data } = await client.query({ - query: _GetAttributeChoicesDocument, - variables: { - slug: filterElement.value.value, - first: 5, - query: "", - }, - }); - return data.attribute.choices.edges.map(({ node }) => ({ - label: node.name, - value: node.id, - slug: node.slug, - })); - } - - if (filterElement.isCollection()) { - const { data } = await client.query({ - query: _GetCollectionsChoicesDocument, - variables: { - first: 5, - query: "", - }, - }); - - return data.collections.edges.map(({ node }) => ({ - label: node.name, - value: node.id, - slug: node.slug, - })); - } - - if (filterElement.isCategory()) { - const { data } = await client.query({ - query: _GetCategoriesChoicesDocument, - variables: { - first: 5, - query: "", - }, - }); - - return data.categories.edges.map(({ node }) => ({ - label: node.name, - value: node.id, - slug: node.slug, - })); - } - - if (filterElement.isProductType()) { - const { data } = await client.query({ - query: _GetProductTypesChoicesDocument, - variables: { - first: 5, - query: "", - }, - }); - - return data.productTypes.edges.map(({ node }) => ({ - label: node.name, - value: node.id, - slug: node.slug, - })); - } - - if (filterElement.isChannel()) { - const { data } = await client.query({ - query: _GetChannelOperandsDocument, - }); - - return data.channels.map(({ id, name, slug }) => ({ - label: name, - value: id, - slug, - })); - } + return handler.fetch(); }; export const getRightOperatorOptionsByQuery = async ( - client: ApolloClient, + client: ApolloClient, position: string, value: FilterContainer, inputValue: string, ) => { const index = parseInt(position, 10); const filterElement = getFilterElement(value, index); + const handler = createAPIHandler(filterElement, client, inputValue); - if (filterElement.isAttribute()) { - const { data } = await client.query({ - query: _GetAttributeChoicesDocument, - variables: { - slug: filterElement.value.value, - first: 5, - query: inputValue, - }, - }); - return data.attribute.choices.edges.map(({ node }) => ({ - label: node.name, - value: node.id, - })); - } - - if (filterElement.isCollection()) { - const { data } = await client.query({ - query: _GetCollectionsChoicesDocument, - variables: { - first: 5, - query: inputValue, - }, - }); - - return data.collections.edges.map(({ node }) => ({ - label: node.name, - value: node.id, - slug: node.slug, - })); - } - - if (filterElement.isCategory()) { - const { data } = await client.query({ - query: _GetCategoriesChoicesDocument, - variables: { - first: 5, - query: inputValue, - }, - }); - - return data.categories.edges.map(({ node }) => ({ - label: node.name, - value: node.id, - slug: node.slug, - })); - } - - if (filterElement.isProductType()) { - const { data } = await client.query({ - query: _GetProductTypesChoicesDocument, - variables: { - first: 5, - query: inputValue, - }, - }); - - return data.productTypes.edges.map(({ node }) => ({ - label: node.name, - value: node.id, - })); - } - - if (filterElement.isChannel()) { - const { data } = await client.query({ - query: _GetChannelOperandsDocument, - }); - const options = data.channels.map(({ id, name, slug }) => ({ - label: name, - value: id, - slug, - })); - - return options.filter(({ label }) => - label.toLowerCase().includes(inputValue.toLowerCase()), - ); - } + return handler.fetch(); }; export const getLeftOperatorOptions = async ( - client: any, + client: ApolloClient, inputValue: string, ) => { - const { data } = await client.query({ - query: _GetDynamicLeftOperandsDocument, - variables: { - first: 5, - query: inputValue, - }, - }); - return data.attributes.edges.map(({ node }) => ({ - label: node.name, - value: node.id, - type: node.inputType, - slug: node.slug, - })); + const handler = new AttributesHandler(client, inputValue); + return handler.fetch(); }; diff --git a/src/components/ConditionalFilter/FilterElement/ConditionOptions.ts b/src/components/ConditionalFilter/FilterElement/ConditionOptions.ts index 6a2fc1b09..6c8cab4b2 100644 --- a/src/components/ConditionalFilter/FilterElement/ConditionOptions.ts +++ b/src/components/ConditionalFilter/FilterElement/ConditionOptions.ts @@ -1,3 +1,5 @@ +import { AttributeInputTypeEnum } from "@dashboard/graphql"; + import { ATTRIBUTE_INPUT_TYPE_CONDITIONS, STATIC_CONDITIONS, @@ -50,7 +52,9 @@ export class ConditionOptions extends Array { return new ConditionOptions(options); } - public static fromName(name: AttributeInputType | StaticElementName) { + public static fromName( + name: AttributeInputType | StaticElementName | AttributeInputTypeEnum, + ) { const optionsStatic = this.isStaticName(name) && STATIC_CONDITIONS[name]; const optionsAttribute = this.isAttributeInputType(name) && ATTRIBUTE_INPUT_TYPE_CONDITIONS[name]; diff --git a/src/components/ConditionalFilter/FilterElement/ConditionSelected.ts b/src/components/ConditionalFilter/FilterElement/ConditionSelected.ts index db64fc7b7..7b6e02706 100644 --- a/src/components/ConditionalFilter/FilterElement/ConditionSelected.ts +++ b/src/components/ConditionalFilter/FilterElement/ConditionSelected.ts @@ -61,9 +61,5 @@ export class ConditionSelected { public setOptions(options: ConditionValue[]) { this.options = options; - - if (this.conditionValue) { - this.value = getDefaultByControlName(this.conditionValue.type); - } } } diff --git a/src/components/ConditionalFilter/index.tsx b/src/components/ConditionalFilter/index.tsx index 6fb2bb0d2..68e85128b 100644 --- a/src/components/ConditionalFilter/index.tsx +++ b/src/components/ConditionalFilter/index.tsx @@ -93,12 +93,12 @@ const FiltersArea = ({ provider, onConfirm }) => { const handleLeftOperatorInputValueChange = (event: any) => { const fetchAPI = async () => { + updateLeftLoadingState(event.path, true); const options = await getLeftOperatorOptions(client, event.value); - setOperands(options); + updateLeftLoadingState(event.path, false); + setOperands(prev => [...prev, ...options]); }; - updateLeftLoadingState(event.path, true); fetchAPI(); - updateLeftLoadingState(event.path, false); }; const handleLeftOperatorInputValueChangeDebounced = useDebounce( @@ -108,17 +108,17 @@ const FiltersArea = ({ provider, onConfirm }) => { const handleRightOperatorInputValueChange = (event: any) => { const fetchAPI = async () => { + updateRightLoadingState(event.path.split(".")[0], true); const options = await getRightOperatorOptionsByQuery( client, event.path.split(".")[0], value, event.value, ); + updateRightLoadingState(event.path.split(".")[0], false); updateRightOptions(event.path.split(".")[0], options); }; - updateRightLoadingState(event.path.split(".")[0], true); fetchAPI(); - updateRightLoadingState(event.path.split(".")[0], false); }; const handleRightOperatorInputValueChangeDebounced = useDebounce( diff --git a/src/components/ConditionalFilter/useLeftOperands.ts b/src/components/ConditionalFilter/useLeftOperands.ts index 235b26ed4..f630bc2f0 100644 --- a/src/components/ConditionalFilter/useLeftOperands.ts +++ b/src/components/ConditionalFilter/useLeftOperands.ts @@ -1,12 +1,10 @@ +import { AttributeInputTypeEnum } from "@dashboard/graphql"; import { useState } from "react"; -import { - AttributeInputType, - StaticElementName, -} from "./FilterElement/ConditionOptions"; +import { StaticElementName } from "./FilterElement/ConditionOptions"; export interface LeftOperand { - type: AttributeInputType | StaticElementName; + type: AttributeInputTypeEnum | StaticElementName; label: string; value: string; slug: string;