New filters for the product listing page (prototype) (#3811)

* Expression filters

* Filters

* Tokenizing

* Tokenizing

* feat: fetch inital state from API

* fix: integrate with code

* Loading

* feat: add attribute name & label

* feat: move input type

* Loading

* feat: update left operator + condition

* feat: fetch inital options on focus

* feat: fetch right options on autocomplete

* Flags

* Refactor

* fix: add loading state

* fix: after changes

* fix: remove debugger

* fix: proper selected setting

* Refactor

* Display properly

* Display properly

* Display properly

* feat: fetch left options

* Persist

* feat: add loading state

* feat: refactor getAPIOptions

* feat: add additional checks to filter element

* feat: use debounce

* FilterArray

* FilterArray

* Modeling

* fix: filters in popover

* feat: use new macaw ui version

* Types

* Feature flag

* fix: type errors

* Alignment

* Fix api

* feat: add slug

* feat: add slug

* feat: add slug for the last time

* fix: return slug from left options

* Fix combobox

* Force slug

* Changeset

* fix: serialize value

---------

Co-authored-by: Krzysztof Żuraw <9116238+krzysztofzuraw@users.noreply.github.com>
This commit is contained in:
Patryk Andrzejewski 2023-06-29 14:10:19 +02:00 committed by GitHub
parent a08d034e75
commit 198341cb41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 2200 additions and 49 deletions

View file

@ -0,0 +1,5 @@
---
"saleor-dashboard": minor
---
Prototype of the new filters for product listing page

14
package-lock.json generated
View file

@ -27,7 +27,7 @@
"@material-ui/lab": "^4.0.0-alpha.61",
"@material-ui/styles": "^4.11.4",
"@reach/auto-id": "^0.16.0",
"@saleor/macaw-ui": "0.8.0-pre.98",
"@saleor/macaw-ui": "0.8.0-pre.101",
"@saleor/sdk": "0.6.0",
"@sentry/react": "^6.0.0",
"@types/faker": "^5.1.6",
@ -7840,9 +7840,9 @@
}
},
"node_modules/@saleor/macaw-ui": {
"version": "0.8.0-pre.98",
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.98.tgz",
"integrity": "sha512-W/KCjRoVVr751JxPET/ebXt9im5lleCGAtydFcuDwmxoU11wTlyxHM25owsJJBpTq1L+jdrMyXO/vZ1FL06Osg==",
"version": "0.8.0-pre.101",
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.101.tgz",
"integrity": "sha512-q68uQs33L8CEjQkZyfRIaljOiRNV6dyIMR211VYdWtAs60jsozwfwnoifPMz3m1DBBJbbaHjwsC7dD9AdlaR2w==",
"dependencies": {
"@dessert-box/react": "^0.4.0",
"@floating-ui/react-dom-interactions": "^0.5.0",
@ -40651,9 +40651,9 @@
}
},
"@saleor/macaw-ui": {
"version": "0.8.0-pre.98",
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.98.tgz",
"integrity": "sha512-W/KCjRoVVr751JxPET/ebXt9im5lleCGAtydFcuDwmxoU11wTlyxHM25owsJJBpTq1L+jdrMyXO/vZ1FL06Osg==",
"version": "0.8.0-pre.101",
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.101.tgz",
"integrity": "sha512-q68uQs33L8CEjQkZyfRIaljOiRNV6dyIMR211VYdWtAs60jsozwfwnoifPMz3m1DBBJbbaHjwsC7dD9AdlaR2w==",
"requires": {
"@dessert-box/react": "^0.4.0",
"@floating-ui/react-dom-interactions": "^0.5.0",

View file

@ -34,7 +34,7 @@
"@material-ui/lab": "^4.0.0-alpha.61",
"@material-ui/styles": "^4.11.4",
"@reach/auto-id": "^0.16.0",
"@saleor/macaw-ui": "0.8.0-pre.98",
"@saleor/macaw-ui": "0.8.0-pre.101",
"@saleor/sdk": "0.6.0",
"@sentry/react": "^6.0.0",
"@types/faker": "^5.1.6",
@ -96,8 +96,6 @@
"use-react-router": "^1.0.7"
},
"devDependencies": {
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.1",
"@babel/cli": "^7.5.5",
"@babel/core": "^7.7.7",
"@babel/plugin-proposal-class-properties": "^7.5.0",
@ -110,6 +108,8 @@
"@babel/preset-react": "^7.7.4",
"@babel/preset-typescript": "^7.13.0",
"@babel/runtime": "^7.7.6",
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.1",
"@editorjs/embed": "^2.5.3",
"@esbuild-plugins/node-globals-polyfill": "^0.1.1",
"@formatjs/cli": "^4.5.0",

View file

@ -1,8 +1,10 @@
import { FilterErrorMessages, IFilter } from "@dashboard/components/Filter";
import { useFlag } from "@dashboard/featureFlags";
import { FilterProps, SearchPageProps } from "@dashboard/types";
import { Box } from "@saleor/macaw-ui/next";
import React, { ReactNode } from "react";
import { ExpressionFilters } from "./components/ExpressionFilters";
import { FiltersSelect } from "./components/FiltersSelect";
import SearchInput from "./components/SearchInput";
@ -25,36 +27,45 @@ export const ListFilters = ({
onFilterAttributeFocus,
errorMessages,
actions,
}: ListFiltersProps) => (
<>
<Box
display="grid"
gridTemplateColumns={2}
gap={4}
paddingBottom={2}
paddingX={6}
>
<Box display="flex" alignItems="center" gap={4}>
<FiltersSelect
errorMessages={errorMessages}
menu={filterStructure}
currencySymbol={currencySymbol}
onFilterAdd={onFilterChange}
onFilterAttributeFocus={onFilterAttributeFocus}
/>
}: ListFiltersProps) => {
const isProductPage = window.location.pathname.includes("/products");
const productListingPageFiltersFlag = useFlag("product_filters");
const filtersEnabled = isProductPage && productListingPageFiltersFlag.enabled;
<Box __width="320px">
<SearchInput
initialSearch={initialSearch}
placeholder={searchPlaceholder}
onSearchChange={onSearchChange}
/>
return (
<>
<Box
display="grid"
gridTemplateColumns={2}
gap={4}
paddingBottom={2}
paddingX={6}
>
<Box display="flex" alignItems="center" gap={4}>
{filtersEnabled ? (
<ExpressionFilters />
) : (
<FiltersSelect
errorMessages={errorMessages}
menu={filterStructure}
currencySymbol={currencySymbol}
onFilterAdd={onFilterChange}
onFilterAttributeFocus={onFilterAttributeFocus}
/>
)}
<Box __width="320px">
<SearchInput
initialSearch={initialSearch}
placeholder={searchPlaceholder}
onSearchChange={onSearchChange}
/>
</Box>
</Box>
<Box display="flex" justifyContent="flex-end">
{actions}
</Box>
</Box>
<Box display="flex" justifyContent="flex-end">
{actions}
</Box>
</Box>
</>
);
</>
);
};
ListFilters.displayName = "FilterBar";

View file

@ -0,0 +1,17 @@
import { ConditionalFilters } from "@dashboard/components/ConditionalFilter";
import { Box, Button, Popover } from "@saleor/macaw-ui/next";
import React from "react";
export const ExpressionFilters = () => (
<Popover>
<Popover.Trigger>
<Button>Show filters</Button>
</Popover.Trigger>
<Popover.Content align="start">
<Box __minWidth="200px" __minHeight="100px" paddingX={4} paddingY={3}>
<Popover.Arrow />
<ConditionalFilters />
</Box>
</Popover.Content>
</Popover>
);

View file

@ -0,0 +1,56 @@
import { AttributeInputType } from "../FilterElement/ConditionOptions";
import { ItemOption } from "../FilterElement/ConditionSelected";
import { UrlToken } from "../ValueProvider/UrlToken";
interface AttributeDTO {
choices: Array<{ label: string; value: string; slug: string }>;
inputType: AttributeInputType;
label: string;
slug: string;
value: string;
}
export class InitialStateResponse {
constructor(
public category: ItemOption[],
public attribute: Record<string, AttributeDTO>,
public channel: ItemOption[],
public collection: ItemOption[],
public producttype: ItemOption[],
) {}
public attributeByName(name: string) {
return this.attribute[name];
}
public filterByUrlToken(token: UrlToken) {
if (token.isAttribute()) {
return this.attribute[token.name].choices.filter(({ value }) =>
token.value.includes(value),
);
}
if (!token.isLoadable()) {
return [token.value] as string[];
}
return this.getEntryByname(token.name).filter(
({ slug }) => slug && token.value.includes(slug),
);
}
private getEntryByname(name: string) {
switch (name) {
case "category":
return this.category;
case "collection":
return this.collection;
case "producttype":
return this.producttype;
case "channel":
return this.channel;
default:
return [];
}
}
}

View file

@ -0,0 +1,211 @@
// @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";
const getFilterElement = (value: any, index: number): FilterElement => {
const possibleFilterElement = value[index];
return typeof possibleFilterElement != "string"
? possibleFilterElement
: null;
};
export const getInitialRightOperatorOptions = async (
client: ApolloClient<any>,
position: string,
value: FilterContainer,
) => {
const index = parseInt(position, 10);
const filterElement = getFilterElement(value, index);
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,
}));
}
};
export const getRightOperatorOptionsByQuery = async (
client: ApolloClient<any>,
position: string,
value: FilterContainer,
inputValue: string,
) => {
const index = parseInt(position, 10);
const filterElement = getFilterElement(value, index);
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()),
);
}
};
export const getLeftOperatorOptions = async (
client: any,
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,
}));
};

View file

@ -0,0 +1,177 @@
// @ts-strict-ignore
import { useApolloClient } from "@apollo/client";
import {
_GetChannelOperandsDocument,
_SearchAttributeOperandsDocument,
_SearchCategoriesOperandsDocument,
_SearchCollectionsOperandsDocument,
_SearchProductTypesOperandsDocument,
} from "@dashboard/graphql";
import { useEffect, useState } from "react";
import { InitialStateResponse } from "./InitialStateResponse";
interface Props {
category?: string[];
collection?: string[];
channel?: string[];
producttype?: string[];
attribute?: {
[attribute: string]: string[];
};
}
export const useInitialAPIState = ({
category = [],
collection = [],
producttype = [],
channel = [],
attribute = {},
}: Props) => {
const client = useApolloClient();
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const queriesToRun = [];
const fetchQueries = async () => {
const data = await Promise.all(queriesToRun);
setData(data);
setLoading(false);
};
if (channel.length > 0) {
queriesToRun.push(
client.query({
query: _GetChannelOperandsDocument,
}),
);
} else {
queriesToRun.push({});
}
if (collection.length > 0) {
queriesToRun.push(
client.query({
query: _SearchCollectionsOperandsDocument,
variables: {
collectionsSlugs: collection,
first: collection.length,
},
}),
);
} else {
queriesToRun.push({});
}
if (category.length > 0) {
queriesToRun.push(
client.query({
query: _SearchCategoriesOperandsDocument,
variables: {
categoriesSlugs: category,
first: category.length,
},
}),
);
} else {
queriesToRun.push({});
}
if (producttype.length > 0) {
queriesToRun.push(
client.query({
query: _SearchProductTypesOperandsDocument,
variables: {
productTypesSlugs: producttype,
first: producttype.length,
},
}),
);
} else {
queriesToRun.push({});
}
if (Object.keys(attribute).length > 0) {
queriesToRun.push(
client.query({
query: _SearchAttributeOperandsDocument,
variables: {
attributesSlugs: Object.keys(attribute),
choicesIds: Object.values(attribute).flat(),
first: Object.keys(attribute).length,
},
}),
);
} else {
queriesToRun.push({});
}
fetchQueries();
}, []);
const [
channelData,
collectionData,
categoryData,
productTypesData,
attributesData,
] = data;
const channelPicks =
channelData?.data?.channels
?.filter(({ slug }) => channel.includes(slug))
.map(({ id, name }) => ({ label: name, value: id })) ?? [];
const collectionPicks =
collectionData?.data?.search?.edges.map(({ node }) => ({
label: node?.name,
value: node?.id,
slug: node?.slug,
})) ?? [];
const categoryPicks =
categoryData?.data?.search?.edges.map(({ node }) => ({
label: node?.name,
value: node?.id,
slug: node?.slug,
})) ?? [];
const productTypePicks =
productTypesData?.data?.search?.edges.map(({ node }) => ({
label: node?.name,
value: node?.id,
slug: node?.slug,
})) ?? [];
const attributePicks =
attributesData?.data?.search?.edges.reduce(
(acc, { node }) => ({
...acc,
[node?.slug]: {
choices: node?.choices.edges.map(({ node }) => ({
label: node?.name,
value: node?.id,
slug: node?.slug,
})),
slug: node?.slug,
value: node?.id,
label: node?.name,
inputType: node?.inputType,
},
}),
{},
) ?? {};
return {
data: new InitialStateResponse(
categoryPicks,
attributePicks,
channelPicks,
collectionPicks,
productTypePicks,
),
loading,
};
};

View file

@ -0,0 +1,156 @@
import { gql } from "@apollo/client";
export const initialDynamicLeftOperands = gql`
query _GetDynamicLeftOperands($first: Int!, $query: String!) {
attributes(first: $first, filter: { type: PRODUCT_TYPE, search: $query }) {
edges {
node {
id
name
slug
inputType
}
}
}
}
`;
export const initialDynamicOperands = gql`
query _GetChannelOperands {
channels {
id
name
slug
}
}
query _SearchCollectionsOperands($first: Int!, $collectionsSlugs: [String!]) {
search: collections(first: $first, filter: { slugs: $collectionsSlugs }) {
edges {
node {
id
name
slug
}
}
}
}
query _SearchCategoriesOperands(
$after: String
$first: Int!
$categoriesSlugs: [String!]
) {
search: categories(
after: $after
first: $first
filter: { slugs: $categoriesSlugs }
) {
edges {
node {
id
name
slug
}
}
}
}
query _SearchProductTypesOperands(
$after: String
$first: Int!
$productTypesSlugs: [String!]
) {
search: productTypes(
after: $after
first: $first
filter: { slugs: $productTypesSlugs }
) {
edges {
node {
id
name
slug
}
}
}
}
query _SearchAttributeOperands(
$attributesSlugs: [String!]
$choicesIds: [ID!]
$first: Int!
) {
search: attributes(first: $first, filter: { slugs: $attributesSlugs }) {
edges {
node {
id
name
slug
inputType
choices(first: 5, filter: { ids: $choicesIds }) {
edges {
node {
slug: id
id
name
}
}
}
}
}
}
}
`;
export const dynamicOperandsQueries = gql`
query _GetAttributeChoices($slug: String!, $first: Int!, $query: String!) {
attribute(slug: $slug) {
choices(first: $first, filter: { search: $query }) {
edges {
node {
slug: id
id
name
}
}
}
}
}
query _GetCollectionsChoices($first: Int!, $query: String!) {
collections(first: $first, filter: { search: $query }) {
edges {
node {
id
name
slug
}
}
}
}
query _GetCategoriesChoices($first: Int!, $query: String!) {
categories(first: $first, filter: { search: $query }) {
edges {
node {
id
name
slug
}
}
}
}
query _GetProductTypesChoices($first: Int!, $query: String!) {
productTypes(first: $first, filter: { search: $query }) {
edges {
node {
id
name
slug
}
}
}
}
`;

View file

@ -0,0 +1,80 @@
/* eslint-disable @typescript-eslint/member-ordering */
import { InitialStateResponse } from "../API/InitialStateResponse";
import { LeftOperand } from "./../useLeftOperands";
import { UrlToken } from "./../ValueProvider/UrlToken";
import { ConditionOptions } from "./ConditionOptions";
import { ConditionSelected } from "./ConditionSelected";
export class Condition {
private constructor(
public options: ConditionOptions,
public selected: ConditionSelected,
public loading: boolean,
) {}
public enableLoading() {
this.loading = true;
}
public disableLoading() {
this.loading = false;
}
public isLoading() {
return this.loading;
}
public static createEmpty() {
return new Condition(
ConditionOptions.empty(),
ConditionSelected.empty(),
false,
);
}
public static emptyFromLeftOperand(operand: LeftOperand) {
const options = ConditionOptions.fromName(operand.type);
return new Condition(
options,
ConditionSelected.fromConditionItem(options.first()),
false,
);
}
public static fromUrlToken(token: UrlToken, response: InitialStateResponse) {
if (ConditionOptions.isStaticName(token.name)) {
const staticOptions = ConditionOptions.fromStaticElementName(token.name);
const selectedOption = staticOptions.findByLabel(token.conditionKind);
const valueItems = response.filterByUrlToken(token);
const value =
selectedOption?.type === "multiselect" && valueItems.length > 0
? valueItems
: valueItems[0];
if (!selectedOption) {
return Condition.createEmpty();
}
return new Condition(
staticOptions,
ConditionSelected.fromConditionItemAndValue(selectedOption, value),
false,
);
}
if (token.isAttribute()) {
const attribute = response.attributeByName(token.name);
const options = ConditionOptions.fromAtributeType(attribute.inputType);
const value = response.filterByUrlToken(token);
return new Condition(
options,
ConditionSelected.fromConditionItemAndValue(options.first(), value),
false,
);
}
return Condition.createEmpty();
}
}

View file

@ -0,0 +1,79 @@
import {
ATTRIBUTE_INPUT_TYPE_CONDITIONS,
STATIC_CONDITIONS,
} from "../constants";
export type StaticElementName = keyof typeof STATIC_CONDITIONS;
export type AttributeInputType = keyof typeof ATTRIBUTE_INPUT_TYPE_CONDITIONS;
export interface ConditionItem {
type: string;
label: string;
value: string;
}
export class ConditionOptions extends Array<ConditionItem> {
private constructor(options: ConditionItem[] | number) {
if (Array.isArray(options)) {
super(...options);
return;
}
super(options);
}
public static isStaticName(name: string): name is StaticElementName {
return name in STATIC_CONDITIONS;
}
public static isAttributeInputType(name: string): name is AttributeInputType {
return name in ATTRIBUTE_INPUT_TYPE_CONDITIONS;
}
public static fromAtributeType(inputType: AttributeInputType) {
const options = ATTRIBUTE_INPUT_TYPE_CONDITIONS[inputType];
if (!options) {
throw new Error(`Unsupported attribute input type "${inputType}"`);
}
return new ConditionOptions(options);
}
public static fromStaticElementName(name: StaticElementName) {
const options = STATIC_CONDITIONS[name];
if (!options) {
throw new Error(`Unsupported static element "${name}"`);
}
return new ConditionOptions(options);
}
public static fromName(name: AttributeInputType | StaticElementName) {
const optionsStatic = this.isStaticName(name) && STATIC_CONDITIONS[name];
const optionsAttribute =
this.isAttributeInputType(name) && ATTRIBUTE_INPUT_TYPE_CONDITIONS[name];
if (optionsStatic) {
return new ConditionOptions(optionsStatic);
}
if (optionsAttribute) {
return new ConditionOptions(optionsAttribute);
}
throw new Error(`Unsupported condition element "${name}"`);
}
public static empty() {
return new ConditionOptions([]);
}
public findByLabel(label: string) {
return this.find(f => f.label === label);
}
public first() {
return this[0];
}
}

View file

@ -0,0 +1,68 @@
import { getDefaultByControlName } from "../controlsType";
import { ConditionItem } from "./ConditionOptions";
export interface ItemOption {
label: string;
value: string;
slug?: string;
}
export type ConditionOption =
| ItemOption
| ItemOption[]
| string
| string[]
| [string, string];
export class ConditionSelected {
private constructor(
public value: ConditionOption,
public conditionValue: ConditionItem | null,
public options: ConditionOption[],
public loading: boolean,
) {}
public static empty() {
return new ConditionSelected("", null, [], false);
}
public static fromConditionItem(conditionItem: ConditionItem) {
return new ConditionSelected(
getDefaultByControlName(conditionItem.type),
conditionItem,
[],
false,
);
}
public static fromConditionItemAndValue(
conditionItem: ConditionItem,
value: ConditionOption,
) {
return new ConditionSelected(value, conditionItem, [], false);
}
public enableLoading() {
this.loading = true;
}
public disableLoading() {
this.loading = false;
}
public isLoading() {
return this.loading;
}
public setValue(value: ConditionOption) {
this.value = value;
}
public setOptions(options: ConditionOption[]) {
this.options = options;
if (this.conditionValue) {
this.value = getDefaultByControlName(this.conditionValue.type);
}
}
}

View file

@ -0,0 +1,179 @@
/* eslint-disable @typescript-eslint/member-ordering */
import { InitialStateResponse } from "../API/InitialStateResponse";
import { LeftOperand } from "./../useLeftOperands";
import { CONDITIONS, UrlEntry, UrlToken } from "./../ValueProvider/UrlToken";
import { Condition } from "./Condition";
import { ConditionItem, ConditionOptions } from "./ConditionOptions";
import { ConditionOption, ConditionSelected } from "./ConditionSelected";
interface ExpressionValue {
value: string;
label: string;
type: string;
}
const createStaticEntry = (rawEntry: ConditionOption) => {
if (typeof rawEntry === "string") {
return rawEntry;
}
if (Array.isArray(rawEntry)) {
return rawEntry.map(el => (typeof el === "string" ? el : el.slug));
}
return rawEntry.slug;
};
const createAttributeEntry = (rawEntry: ConditionOption) => {
if (typeof rawEntry === "string") {
return rawEntry;
}
if (Array.isArray(rawEntry)) {
return rawEntry.map(el => (typeof el === "string" ? el : el.slug));
}
return rawEntry.slug;
};
export class FilterElement {
private constructor(
public value: ExpressionValue,
public condition: Condition,
public loading: boolean,
) {}
public enableLoading() {
this.loading = true;
}
public disableLoading() {
this.loading = false;
}
public isLoading() {
return this.loading;
}
public updateLeftOperator(leftOperand: LeftOperand) {
this.value = {
value: leftOperand.slug,
label: leftOperand.label,
type: leftOperand.type,
};
this.condition = Condition.emptyFromLeftOperand(leftOperand);
}
public updateLeftLoadingState(loading: boolean) {
this.loading = loading;
}
public updateCondition(conditionValue: ConditionItem) {
this.condition.selected =
ConditionSelected.fromConditionItem(conditionValue);
}
public updateRightOperator(value: ConditionOption) {
this.condition.selected.setValue(value);
}
public updateRightOptions(options: ConditionOption[]) {
this.condition.selected.setOptions(options);
}
public updateRightLoadingState(loading: boolean) {
if (loading) {
this.condition.selected.enableLoading();
return;
}
this.condition.selected.disableLoading();
}
public isEmpty() {
return this.value.type === "e";
}
public isStatic() {
return ConditionOptions.isStaticName(this.value.type);
}
public isAttribute() {
return ConditionOptions.isAttributeInputType(this.value.type);
}
public isCollection() {
return this.value.value === "collection";
}
public isCategory() {
return this.value.value === "category";
}
public isProductType() {
return this.value.value === "producttype";
}
public isChannel() {
return this.value.value === "channel";
}
public asUrlEntry(): UrlEntry {
const { conditionValue } = this.condition.selected;
const conditionIndex = CONDITIONS.findIndex(
el => conditionValue && el === conditionValue.label,
);
if (this.isAttribute()) {
return {
[`a${conditionIndex}.${this.value.value}`]: createAttributeEntry(
this.condition.selected.value,
),
};
}
return {
[`s${conditionIndex}.${this.value.value}`]: createStaticEntry(
this.condition.selected.value,
),
};
}
public static fromValueEntry(valueEntry: any) {
return new FilterElement(valueEntry.value, valueEntry.condition, false);
}
public static createEmpty() {
return new FilterElement(
{ value: "", label: "", type: "s" },
Condition.createEmpty(),
false,
);
}
public static fromUrlToken(token: UrlToken, response: InitialStateResponse) {
if (token.isStatic()) {
return new FilterElement(
{ value: token.name, label: token.name, type: token.name },
Condition.fromUrlToken(token, response),
false,
);
}
if (token.isAttribute()) {
const attribute = response.attributeByName(token.name);
return new FilterElement(
{
value: token.name,
label: attribute.label,
type: attribute.inputType,
},
Condition.fromUrlToken(token, response),
false,
);
}
return null;
}
}

View file

@ -0,0 +1,2 @@
export { FilterElement } from "./FilterElement"
export { Condition } from "./Condition"

View file

@ -0,0 +1,7 @@
import { FilterContainer } from "./useFilterContainer";
export interface FilterValueProvider {
value: FilterContainer;
loading: boolean;
persist: (newValue: FilterContainer) => void;
}

View file

@ -0,0 +1,40 @@
// @ts-strict-ignore
import { UrlToken } from "../UrlToken";
export interface FetchingParams {
category: string[];
collection: string[];
channel: string[];
producttype: [];
attribute: Record<string, string[]>;
}
export const emptyFetchingParams: FetchingParams = {
category: [],
collection: [],
channel: [],
producttype: [],
attribute: {},
};
const unique = <T>(array: Iterable<T>) => Array.from(new Set(array));
export const toFetchingParams = (p: FetchingParams, c: UrlToken) => {
if (!c.isAttribute() && !p[c.name]) {
p[c.name] = [];
}
if (c.isAttribute() && !p.attribute[c.name]) {
p.attribute[c.name] = [];
}
if (c.isAttribute()) {
p.attribute[c.name] = unique(p.attribute[c.name].concat(c.value));
return p;
}
p[c.name] = unique(p[c.name].concat(c.value));
return p;
};

View file

@ -0,0 +1,106 @@
// @ts-strict-ignore
import { parse, ParsedQs } from "qs";
import { useRef } from "react";
import { InitialStateResponse } from "../../API/InitialStateResponse";
import { FilterElement } from "../../FilterElement";
import { FilterContainer } from "../../useFilterContainer";
import { UrlToken } from "../UrlToken";
import {
emptyFetchingParams,
FetchingParams,
toFetchingParams,
} from "./fetchingParams";
const toFlatUrlTokens = (p: UrlToken[], c: TokenArray[number]) => {
if (typeof c == "string") {
return p;
}
if (Array.isArray(c)) {
return p.concat(flatenate(c));
}
return p.concat(c);
};
const flatenate = (tokens: TokenArray): UrlToken[] =>
tokens.reduce<UrlToken[]>(toFlatUrlTokens, []);
const mapToTokens = (urlEntries: Array<ParsedQs | string>): TokenArray =>
urlEntries.map(entry => {
if (typeof entry === "string") {
return entry;
}
if (Array.isArray(entry)) {
return mapToTokens(entry);
}
return UrlToken.fromUrlEntry(entry);
}) as TokenArray;
const tokenizeUrl = (urlParams: string) => {
const parsedUrl = Object.values(parse(urlParams)) as Array<ParsedQs | string>;
return mapToTokens(parsedUrl);
};
const mapUrlTokensToFilterValues = (
urlTokens: TokenArray,
response: InitialStateResponse,
) =>
urlTokens.map(el => {
if (typeof el === "string") {
return el;
}
if (Array.isArray(el)) {
return mapUrlTokensToFilterValues(el, response);
}
return FilterElement.fromUrlToken(el, response);
});
export class TokenArray extends Array<string | UrlToken | TokenArray> {
constructor(url: string) {
super(...tokenizeUrl(url));
}
public getFetchingParams() {
return this.asFlatArray()
.filter(token => token.isLoadable())
.reduce<FetchingParams>(toFetchingParams, emptyFetchingParams);
}
public asFlatArray() {
return flatenate(this);
}
public asFilterValuesFromResponse(
response: InitialStateResponse,
): FilterContainer {
return this.map(el => {
if (typeof el === "string") {
return el;
}
if (Array.isArray(el)) {
return mapUrlTokensToFilterValues(el, response);
}
return FilterElement.fromUrlToken(el, response);
});
}
}
export const useTokenArray = (url: string) => {
const instance = useRef<TokenArray>(null);
if (!instance.current) {
instance.current = new TokenArray(url);
}
return instance.current;
};

View file

@ -0,0 +1,47 @@
// @ts-strict-ignore
export const CONDITIONS = ["is", "equals", "in", "between", "lower", "greater"];
const STATIC_TO_LOAD = ["category", "collection", "channel", "producttype"];
type TokenType = "a" | "s";
// export type UrlEntry = Record<string, string | string[]>
export class UrlEntry {
constructor(key: string, value: string | string[]) {
this[key] = value;
}
}
export class UrlToken {
private constructor(
public name: string,
public value: string | string[],
public type: TokenType,
public conditionKind: string,
) {}
public static fromUrlEntry(entry: UrlEntry) {
const [key, value] = Object.entries(entry)[0] as [
string,
string | string[],
];
const [identifier, entryName] = key.split(".");
const [type, control] = identifier.split("") as [TokenType, string];
return new UrlToken(entryName, value, type, CONDITIONS[control]);
}
public isStatic() {
return this.type === "s";
}
public isAttribute() {
return this.type === "a";
}
public isLoadable() {
return STATIC_TO_LOAD.includes(this.name) || this.isAttribute();
}
}

View file

@ -0,0 +1,51 @@
// @ts-strict-ignore
import { stringify } from "qs";
import useRouter from "use-react-router";
import { useInitialAPIState } from "../API/getInitalAPIState";
import { FilterValueProvider } from "../FilterValueProvider";
import { FilterContainer } from "../useFilterContainer";
import { useTokenArray } from "./TokenArray";
const prepareStructure = filterValue =>
filterValue.map(f => {
if (typeof f === "string") {
return f;
}
if (Array.isArray(f)) {
return prepareStructure(f);
}
return f.asUrlEntry();
});
/*
exampple url: http://localhost:9000/dashboard/products/?0%5Bs2.category%5D%5B0%5D=accessories&0%5Bs2.category%5D%5B1%5D=groceries&1=o&2%5Ba2.abv%5D%5B0%5D=QXR0cmlidXRlVmFsdWU6Njg%3D&3=a&4%5Bs2.collection%5D%5B0%5D=featured-products&5=a&6%5Bs2.producttype%5D%5B0%5D=beer&7=a&8%5B0%5D%5Bs2.category%5D%5B0%5D=apparel&8%5B1%5D=o&8%5B2%5D%5Ba2.bottle-size%5D%5B0%5D=QXR0cmlidXRlVmFsdWU6NDY%3D&8%5B2%5D%5Ba2.bottle-size%5D%5B1%5D=QXR0cmlidXRlVmFsdWU6NDc%3D&asc=true&sort=name
*/
export const useUrlValueProvider = (): FilterValueProvider => {
const router = useRouter();
const params = new URLSearchParams(router.location.search);
params.delete("asc");
params.delete("sort");
const tokenizedUrl = useTokenArray(params.toString());
const fetchingParams = tokenizedUrl.getFetchingParams();
const { data, loading } = useInitialAPIState(fetchingParams);
const value = loading ? [] : tokenizedUrl.asFilterValuesFromResponse(data);
const persist = (filterValue: FilterContainer) => {
router.history.replace({
pathname: router.location.pathname,
search: stringify(prepareStructure(filterValue)),
});
};
return {
value,
loading,
persist,
};
};

View file

@ -0,0 +1,30 @@
export const STATIC_CONDITIONS = {
category: [
{ type: "combobox", label: "is", value: "input-1" },
{ type: "multiselect", label: "in", value: "input-2" },
],
price: [
{ type: "number", label: "is", value: "input-1" },
{ type: "number", label: "lower", value: "input-2" },
{ type: "number", label: "greater", value: "input-3" },
{ type: "number.range", label: "between", value: "input-4" },
],
collection: [{ type: "multiselect", label: "in", value: "input-4" }],
producttype: [{ type: "multiselect", label: "in", value: "input-4" }],
channel: [{ type: "select", label: "is", value: "input-5" }],
};
export const ATTRIBUTE_INPUT_TYPE_CONDITIONS = {
DROPDOWN: [{ type: "multiselect", label: "in", value: "input-2" }],
MULTISELECT: [{ type: "multiselect", label: "in", value: "input-2" }],
BOOLEAN: [{ type: "select", label: "is", value: "input-5" }],
NUMERIC: [
{ type: "number", label: "is", value: "input-1" },
{ type: "number", label: "lower", value: "input-2" },
{ type: "number", label: "greater", value: "input-3" },
{ type: "number.range", label: "between", value: "input-4" },
],
DATE_TIME: [{ type: "date", label: "is", value: "input-1" }],
DATE: [{ type: "date", label: "is", value: "input-1" }],
SWATCH: [{ type: "multiselect", label: "in", value: "input-2" }],
};

View file

@ -0,0 +1,14 @@
// @ts-strict-ignore
import { ConditionOption } from "./FilterElement/ConditionSelected";
export const CONTROL_DEFAULTS = {
text: "",
number: "",
"number.range": [] as unknown as [string, string],
multiselect: [] as ConditionOption[],
select: "",
combobox: "",
};
export const getDefaultByControlName = (name: string): ConditionOption =>
CONTROL_DEFAULTS[name];

View file

@ -0,0 +1,142 @@
// @ts-strict-ignore
import { useApolloClient } from "@apollo/client";
import useDebounce from "@dashboard/hooks/useDebounce";
import { _ExperimentalFilters, Box, Text } from "@saleor/macaw-ui/next";
import React from "react";
import {
getInitialRightOperatorOptions,
getLeftOperatorOptions,
getRightOperatorOptionsByQuery,
} from "./API/getAPIOptions";
import { useFilterContainer } from "./useFilterContainer";
import { useLeftOperands } from "./useLeftOperands";
import { useUrlValueProvider } from "./ValueProvider/useUrlValueProvider";
const FiltersArea = ({ provider, onConfirm }) => {
const client = useApolloClient();
const {
value,
addEmpty,
removeAt,
updateLeftOperator,
updateRightOperator,
updateCondition,
updateRightOptions,
updateRightLoadingState,
updateLeftLoadingState,
} = useFilterContainer(provider);
const { operands, setOperands } = useLeftOperands();
const handleLeftOperatorInputValueChange = (event: any) => {
const fetchAPI = async () => {
const options = await getLeftOperatorOptions(client, event.value);
setOperands(options);
};
updateLeftLoadingState(event.path, true);
fetchAPI();
updateLeftLoadingState(event.path, false);
};
const handleLeftOperatorInputValueChangeDebounced = useDebounce(
handleLeftOperatorInputValueChange,
500,
);
const handleRightOperatorInputValueChange = (event: any) => {
const fetchAPI = async () => {
const options = await getRightOperatorOptionsByQuery(
client,
event.path.split(".")[0],
value,
event.value,
);
updateRightOptions(event.path.split(".")[0], options);
};
updateRightLoadingState(event.path.split(".")[0], true);
fetchAPI();
updateRightLoadingState(event.path.split(".")[0], false);
};
const handleRightOperatorInputValueChangeDebounced = useDebounce(
handleRightOperatorInputValueChange,
500,
);
const handleStateChange = async event => {
if (event.type === "row.add") {
addEmpty();
}
if (event.type === "row.remove") {
removeAt(event.path);
}
if (event.type === "leftOperator.onChange") {
updateLeftOperator(event.path, event.value);
}
if (event.type === "condition.onChange") {
updateCondition(event.path.split(".")[0], event.value);
}
if (event.type === "rightOperator.onChange") {
updateRightOperator(event.path.split(".")[0], event.value);
}
if (event.type === "rightOperator.onFocus") {
const path = event.path.split(".")[0];
updateRightLoadingState(path, true);
const options = await getInitialRightOperatorOptions(client, path, value);
updateRightOptions(path, options);
updateRightLoadingState(path, false);
}
if (event.type === "rightOperator.onInputValueChange") {
handleRightOperatorInputValueChangeDebounced(event);
}
if (event.type === "leftOperator.onInputValueChange") {
handleLeftOperatorInputValueChangeDebounced(event);
}
};
const handleConfirm = () => onConfirm(value);
return (
<Box>
<_ExperimentalFilters
leftOptions={operands}
// @ts-ignore
value={value}
onChange={handleStateChange}
>
<_ExperimentalFilters.Footer>
<_ExperimentalFilters.AddRowButton>
Add new row
</_ExperimentalFilters.AddRowButton>
<_ExperimentalFilters.ConfirmButton onClick={handleConfirm}>
Confirm
</_ExperimentalFilters.ConfirmButton>
</_ExperimentalFilters.Footer>
</_ExperimentalFilters>
</Box>
);
};
export const ConditionalFilters = () => {
const provider = useUrlValueProvider();
return (
<Box>
{provider.loading ? (
<Text>Loading...</Text>
) : (
// @ts-ignore
<FiltersArea provider={provider.value} onConfirm={provider.persist} />
)}
</Box>
);
};

View file

@ -0,0 +1,125 @@
// @ts-strict-ignore
import { useState } from "react";
import { FilterElement } from "./FilterElement";
export type FilterContainer = Array<string | FilterElement | FilterContainer>;
export const useFilterContainer = (initialValue: FilterContainer) => {
const [value, setValue] = useState(initialValue);
const addEmpty = () => {
const newValue = [];
if (value.length > 0) {
newValue.push("OR");
}
newValue.push(FilterElement.createEmpty());
setValue(v => v.concat(newValue));
};
const removeAt = (position: string) => {
const index = parseInt(position, 10);
if (value.length > 0) {
setValue(v =>
v.filter((_, elIndex) => ![index - 1, index].includes(elIndex)),
);
return;
}
setValue(v => v.filter((_, elIndex) => ![index].includes(elIndex)));
};
const updateLeftOperator = (position: string, leftOperator: any) => {
const index = parseInt(position, 10);
setValue(v =>
v.map((el, elIndex) => {
if (elIndex === index && typeof el != "string" && !Array.isArray(el)) {
el.updateLeftOperator(leftOperator);
}
return el;
}),
);
};
const updateLeftLoadingState = (position: string, loading: boolean) => {
const index = parseInt(position, 10);
setValue(v =>
v.map((el, elIndex) => {
if (elIndex === index && typeof el != "string" && !Array.isArray(el)) {
el.updateLeftLoadingState(loading);
}
return el;
}),
);
};
const updateRightOperator = (position: string, leftOperator: any) => {
const index = parseInt(position, 10);
setValue(v =>
v.map((el, elIndex) => {
if (elIndex === index && typeof el != "string" && !Array.isArray(el)) {
el.updateRightOperator(leftOperator);
}
return el;
}),
);
};
const updateRightOptions = (position: string, options: any) => {
const index = parseInt(position, 10);
setValue(v =>
v.map((el, elIndex) => {
if (elIndex === index && typeof el != "string" && !Array.isArray(el)) {
el.updateRightOptions(options);
}
return el;
}),
);
};
const updateRightLoadingState = (position: string, loading: boolean) => {
const index = parseInt(position, 10);
setValue(v =>
v.map((el, elIndex) => {
if (elIndex === index && typeof el != "string" && !Array.isArray(el)) {
el.updateRightLoadingState(loading);
}
return el;
}),
);
};
const updateCondition = (position: string, conditionValue: any) => {
const index = parseInt(position, 10);
setValue(v =>
v.map((el, elIndex) => {
if (elIndex === index && typeof el != "string" && !Array.isArray(el)) {
el.updateCondition(conditionValue);
}
return el;
}),
);
};
return {
value,
addEmpty,
removeAt,
updateLeftOperator,
updateRightOperator,
updateCondition,
updateRightOptions,
updateRightLoadingState,
updateLeftLoadingState,
};
};

View file

@ -0,0 +1,34 @@
import { useState } from "react";
import {
AttributeInputType,
StaticElementName,
} from "./FilterElement/ConditionOptions";
export interface LeftOperand {
type: AttributeInputType | StaticElementName;
label: string;
value: string;
slug: string;
}
const STATIC_OPTIONS: LeftOperand[] = [
{ value: "price", label: "Price", type: "price", slug: "price" },
{ value: "category", label: "Category", type: "category", slug: "category" },
{
value: "collection",
label: "Collection",
type: "collection",
slug: "collection",
},
{ value: "channel", label: "Channel", type: "channel", slug: "channel" },
];
export const useLeftOperands = () => {
const [operands, setOperands] = useState<LeftOperand[]>(STATIC_OPTIONS);
return {
operands,
setOperands,
};
};

View file

@ -24,16 +24,10 @@ const AVAILABLE_FLAGS = [
*/
{
name: "flag1",
displayName: "Flag 1",
description: "some description",
content: { enabled: false, payload: "default" },
} as const,
{
name: "flag2",
displayName: "Flag 2",
description: "some description 2",
content: { enabled: false, payload: "default2" },
name: "product_filters",
displayName: "Product filters",
description: "New filters on product listing page",
content: { enabled: false, payload: "" },
} as const,
] satisfies FlagDefinition[];

View file

@ -5417,6 +5417,445 @@ export function useAddressValidationRulesLazyQuery(baseOptions?: ApolloReactHook
export type AddressValidationRulesQueryHookResult = ReturnType<typeof useAddressValidationRulesQuery>;
export type AddressValidationRulesLazyQueryHookResult = ReturnType<typeof useAddressValidationRulesLazyQuery>;
export type AddressValidationRulesQueryResult = Apollo.QueryResult<Types.AddressValidationRulesQuery, Types.AddressValidationRulesQueryVariables>;
export const _GetDynamicLeftOperandsDocument = gql`
query _GetDynamicLeftOperands($first: Int!, $query: String!) {
attributes(first: $first, filter: {type: PRODUCT_TYPE, search: $query}) {
edges {
node {
id
name
slug
inputType
}
}
}
}
`;
/**
* __use_GetDynamicLeftOperandsQuery__
*
* To run a query within a React component, call `use_GetDynamicLeftOperandsQuery` and pass it any options that fit your needs.
* When your component renders, `use_GetDynamicLeftOperandsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = use_GetDynamicLeftOperandsQuery({
* variables: {
* first: // value for 'first'
* query: // value for 'query'
* },
* });
*/
export function use_GetDynamicLeftOperandsQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types._GetDynamicLeftOperandsQuery, Types._GetDynamicLeftOperandsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useQuery<Types._GetDynamicLeftOperandsQuery, Types._GetDynamicLeftOperandsQueryVariables>(_GetDynamicLeftOperandsDocument, options);
}
export function use_GetDynamicLeftOperandsLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types._GetDynamicLeftOperandsQuery, Types._GetDynamicLeftOperandsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useLazyQuery<Types._GetDynamicLeftOperandsQuery, Types._GetDynamicLeftOperandsQueryVariables>(_GetDynamicLeftOperandsDocument, options);
}
export type _GetDynamicLeftOperandsQueryHookResult = ReturnType<typeof use_GetDynamicLeftOperandsQuery>;
export type _GetDynamicLeftOperandsLazyQueryHookResult = ReturnType<typeof use_GetDynamicLeftOperandsLazyQuery>;
export type _GetDynamicLeftOperandsQueryResult = Apollo.QueryResult<Types._GetDynamicLeftOperandsQuery, Types._GetDynamicLeftOperandsQueryVariables>;
export const _GetChannelOperandsDocument = gql`
query _GetChannelOperands {
channels {
id
name
slug
}
}
`;
/**
* __use_GetChannelOperandsQuery__
*
* To run a query within a React component, call `use_GetChannelOperandsQuery` and pass it any options that fit your needs.
* When your component renders, `use_GetChannelOperandsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = use_GetChannelOperandsQuery({
* variables: {
* },
* });
*/
export function use_GetChannelOperandsQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<Types._GetChannelOperandsQuery, Types._GetChannelOperandsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useQuery<Types._GetChannelOperandsQuery, Types._GetChannelOperandsQueryVariables>(_GetChannelOperandsDocument, options);
}
export function use_GetChannelOperandsLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types._GetChannelOperandsQuery, Types._GetChannelOperandsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useLazyQuery<Types._GetChannelOperandsQuery, Types._GetChannelOperandsQueryVariables>(_GetChannelOperandsDocument, options);
}
export type _GetChannelOperandsQueryHookResult = ReturnType<typeof use_GetChannelOperandsQuery>;
export type _GetChannelOperandsLazyQueryHookResult = ReturnType<typeof use_GetChannelOperandsLazyQuery>;
export type _GetChannelOperandsQueryResult = Apollo.QueryResult<Types._GetChannelOperandsQuery, Types._GetChannelOperandsQueryVariables>;
export const _SearchCollectionsOperandsDocument = gql`
query _SearchCollectionsOperands($first: Int!, $collectionsSlugs: [String!]) {
search: collections(first: $first, filter: {slugs: $collectionsSlugs}) {
edges {
node {
id
name
slug
}
}
}
}
`;
/**
* __use_SearchCollectionsOperandsQuery__
*
* To run a query within a React component, call `use_SearchCollectionsOperandsQuery` and pass it any options that fit your needs.
* When your component renders, `use_SearchCollectionsOperandsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = use_SearchCollectionsOperandsQuery({
* variables: {
* first: // value for 'first'
* collectionsSlugs: // value for 'collectionsSlugs'
* },
* });
*/
export function use_SearchCollectionsOperandsQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types._SearchCollectionsOperandsQuery, Types._SearchCollectionsOperandsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useQuery<Types._SearchCollectionsOperandsQuery, Types._SearchCollectionsOperandsQueryVariables>(_SearchCollectionsOperandsDocument, options);
}
export function use_SearchCollectionsOperandsLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types._SearchCollectionsOperandsQuery, Types._SearchCollectionsOperandsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useLazyQuery<Types._SearchCollectionsOperandsQuery, Types._SearchCollectionsOperandsQueryVariables>(_SearchCollectionsOperandsDocument, options);
}
export type _SearchCollectionsOperandsQueryHookResult = ReturnType<typeof use_SearchCollectionsOperandsQuery>;
export type _SearchCollectionsOperandsLazyQueryHookResult = ReturnType<typeof use_SearchCollectionsOperandsLazyQuery>;
export type _SearchCollectionsOperandsQueryResult = Apollo.QueryResult<Types._SearchCollectionsOperandsQuery, Types._SearchCollectionsOperandsQueryVariables>;
export const _SearchCategoriesOperandsDocument = gql`
query _SearchCategoriesOperands($after: String, $first: Int!, $categoriesSlugs: [String!]) {
search: categories(
after: $after
first: $first
filter: {slugs: $categoriesSlugs}
) {
edges {
node {
id
name
slug
}
}
}
}
`;
/**
* __use_SearchCategoriesOperandsQuery__
*
* To run a query within a React component, call `use_SearchCategoriesOperandsQuery` and pass it any options that fit your needs.
* When your component renders, `use_SearchCategoriesOperandsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = use_SearchCategoriesOperandsQuery({
* variables: {
* after: // value for 'after'
* first: // value for 'first'
* categoriesSlugs: // value for 'categoriesSlugs'
* },
* });
*/
export function use_SearchCategoriesOperandsQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types._SearchCategoriesOperandsQuery, Types._SearchCategoriesOperandsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useQuery<Types._SearchCategoriesOperandsQuery, Types._SearchCategoriesOperandsQueryVariables>(_SearchCategoriesOperandsDocument, options);
}
export function use_SearchCategoriesOperandsLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types._SearchCategoriesOperandsQuery, Types._SearchCategoriesOperandsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useLazyQuery<Types._SearchCategoriesOperandsQuery, Types._SearchCategoriesOperandsQueryVariables>(_SearchCategoriesOperandsDocument, options);
}
export type _SearchCategoriesOperandsQueryHookResult = ReturnType<typeof use_SearchCategoriesOperandsQuery>;
export type _SearchCategoriesOperandsLazyQueryHookResult = ReturnType<typeof use_SearchCategoriesOperandsLazyQuery>;
export type _SearchCategoriesOperandsQueryResult = Apollo.QueryResult<Types._SearchCategoriesOperandsQuery, Types._SearchCategoriesOperandsQueryVariables>;
export const _SearchProductTypesOperandsDocument = gql`
query _SearchProductTypesOperands($after: String, $first: Int!, $productTypesSlugs: [String!]) {
search: productTypes(
after: $after
first: $first
filter: {slugs: $productTypesSlugs}
) {
edges {
node {
id
name
slug
}
}
}
}
`;
/**
* __use_SearchProductTypesOperandsQuery__
*
* To run a query within a React component, call `use_SearchProductTypesOperandsQuery` and pass it any options that fit your needs.
* When your component renders, `use_SearchProductTypesOperandsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = use_SearchProductTypesOperandsQuery({
* variables: {
* after: // value for 'after'
* first: // value for 'first'
* productTypesSlugs: // value for 'productTypesSlugs'
* },
* });
*/
export function use_SearchProductTypesOperandsQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types._SearchProductTypesOperandsQuery, Types._SearchProductTypesOperandsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useQuery<Types._SearchProductTypesOperandsQuery, Types._SearchProductTypesOperandsQueryVariables>(_SearchProductTypesOperandsDocument, options);
}
export function use_SearchProductTypesOperandsLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types._SearchProductTypesOperandsQuery, Types._SearchProductTypesOperandsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useLazyQuery<Types._SearchProductTypesOperandsQuery, Types._SearchProductTypesOperandsQueryVariables>(_SearchProductTypesOperandsDocument, options);
}
export type _SearchProductTypesOperandsQueryHookResult = ReturnType<typeof use_SearchProductTypesOperandsQuery>;
export type _SearchProductTypesOperandsLazyQueryHookResult = ReturnType<typeof use_SearchProductTypesOperandsLazyQuery>;
export type _SearchProductTypesOperandsQueryResult = Apollo.QueryResult<Types._SearchProductTypesOperandsQuery, Types._SearchProductTypesOperandsQueryVariables>;
export const _SearchAttributeOperandsDocument = gql`
query _SearchAttributeOperands($attributesSlugs: [String!], $choicesIds: [ID!], $first: Int!) {
search: attributes(first: $first, filter: {slugs: $attributesSlugs}) {
edges {
node {
id
name
slug
inputType
choices(first: 5, filter: {ids: $choicesIds}) {
edges {
node {
slug: id
id
name
}
}
}
}
}
}
}
`;
/**
* __use_SearchAttributeOperandsQuery__
*
* To run a query within a React component, call `use_SearchAttributeOperandsQuery` and pass it any options that fit your needs.
* When your component renders, `use_SearchAttributeOperandsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = use_SearchAttributeOperandsQuery({
* variables: {
* attributesSlugs: // value for 'attributesSlugs'
* choicesIds: // value for 'choicesIds'
* first: // value for 'first'
* },
* });
*/
export function use_SearchAttributeOperandsQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types._SearchAttributeOperandsQuery, Types._SearchAttributeOperandsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useQuery<Types._SearchAttributeOperandsQuery, Types._SearchAttributeOperandsQueryVariables>(_SearchAttributeOperandsDocument, options);
}
export function use_SearchAttributeOperandsLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types._SearchAttributeOperandsQuery, Types._SearchAttributeOperandsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useLazyQuery<Types._SearchAttributeOperandsQuery, Types._SearchAttributeOperandsQueryVariables>(_SearchAttributeOperandsDocument, options);
}
export type _SearchAttributeOperandsQueryHookResult = ReturnType<typeof use_SearchAttributeOperandsQuery>;
export type _SearchAttributeOperandsLazyQueryHookResult = ReturnType<typeof use_SearchAttributeOperandsLazyQuery>;
export type _SearchAttributeOperandsQueryResult = Apollo.QueryResult<Types._SearchAttributeOperandsQuery, Types._SearchAttributeOperandsQueryVariables>;
export const _GetAttributeChoicesDocument = gql`
query _GetAttributeChoices($slug: String!, $first: Int!, $query: String!) {
attribute(slug: $slug) {
choices(first: $first, filter: {search: $query}) {
edges {
node {
slug: id
id
name
}
}
}
}
}
`;
/**
* __use_GetAttributeChoicesQuery__
*
* To run a query within a React component, call `use_GetAttributeChoicesQuery` and pass it any options that fit your needs.
* When your component renders, `use_GetAttributeChoicesQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = use_GetAttributeChoicesQuery({
* variables: {
* slug: // value for 'slug'
* first: // value for 'first'
* query: // value for 'query'
* },
* });
*/
export function use_GetAttributeChoicesQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types._GetAttributeChoicesQuery, Types._GetAttributeChoicesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useQuery<Types._GetAttributeChoicesQuery, Types._GetAttributeChoicesQueryVariables>(_GetAttributeChoicesDocument, options);
}
export function use_GetAttributeChoicesLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types._GetAttributeChoicesQuery, Types._GetAttributeChoicesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useLazyQuery<Types._GetAttributeChoicesQuery, Types._GetAttributeChoicesQueryVariables>(_GetAttributeChoicesDocument, options);
}
export type _GetAttributeChoicesQueryHookResult = ReturnType<typeof use_GetAttributeChoicesQuery>;
export type _GetAttributeChoicesLazyQueryHookResult = ReturnType<typeof use_GetAttributeChoicesLazyQuery>;
export type _GetAttributeChoicesQueryResult = Apollo.QueryResult<Types._GetAttributeChoicesQuery, Types._GetAttributeChoicesQueryVariables>;
export const _GetCollectionsChoicesDocument = gql`
query _GetCollectionsChoices($first: Int!, $query: String!) {
collections(first: $first, filter: {search: $query}) {
edges {
node {
id
name
slug
}
}
}
}
`;
/**
* __use_GetCollectionsChoicesQuery__
*
* To run a query within a React component, call `use_GetCollectionsChoicesQuery` and pass it any options that fit your needs.
* When your component renders, `use_GetCollectionsChoicesQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = use_GetCollectionsChoicesQuery({
* variables: {
* first: // value for 'first'
* query: // value for 'query'
* },
* });
*/
export function use_GetCollectionsChoicesQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types._GetCollectionsChoicesQuery, Types._GetCollectionsChoicesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useQuery<Types._GetCollectionsChoicesQuery, Types._GetCollectionsChoicesQueryVariables>(_GetCollectionsChoicesDocument, options);
}
export function use_GetCollectionsChoicesLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types._GetCollectionsChoicesQuery, Types._GetCollectionsChoicesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useLazyQuery<Types._GetCollectionsChoicesQuery, Types._GetCollectionsChoicesQueryVariables>(_GetCollectionsChoicesDocument, options);
}
export type _GetCollectionsChoicesQueryHookResult = ReturnType<typeof use_GetCollectionsChoicesQuery>;
export type _GetCollectionsChoicesLazyQueryHookResult = ReturnType<typeof use_GetCollectionsChoicesLazyQuery>;
export type _GetCollectionsChoicesQueryResult = Apollo.QueryResult<Types._GetCollectionsChoicesQuery, Types._GetCollectionsChoicesQueryVariables>;
export const _GetCategoriesChoicesDocument = gql`
query _GetCategoriesChoices($first: Int!, $query: String!) {
categories(first: $first, filter: {search: $query}) {
edges {
node {
id
name
slug
}
}
}
}
`;
/**
* __use_GetCategoriesChoicesQuery__
*
* To run a query within a React component, call `use_GetCategoriesChoicesQuery` and pass it any options that fit your needs.
* When your component renders, `use_GetCategoriesChoicesQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = use_GetCategoriesChoicesQuery({
* variables: {
* first: // value for 'first'
* query: // value for 'query'
* },
* });
*/
export function use_GetCategoriesChoicesQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types._GetCategoriesChoicesQuery, Types._GetCategoriesChoicesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useQuery<Types._GetCategoriesChoicesQuery, Types._GetCategoriesChoicesQueryVariables>(_GetCategoriesChoicesDocument, options);
}
export function use_GetCategoriesChoicesLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types._GetCategoriesChoicesQuery, Types._GetCategoriesChoicesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useLazyQuery<Types._GetCategoriesChoicesQuery, Types._GetCategoriesChoicesQueryVariables>(_GetCategoriesChoicesDocument, options);
}
export type _GetCategoriesChoicesQueryHookResult = ReturnType<typeof use_GetCategoriesChoicesQuery>;
export type _GetCategoriesChoicesLazyQueryHookResult = ReturnType<typeof use_GetCategoriesChoicesLazyQuery>;
export type _GetCategoriesChoicesQueryResult = Apollo.QueryResult<Types._GetCategoriesChoicesQuery, Types._GetCategoriesChoicesQueryVariables>;
export const _GetProductTypesChoicesDocument = gql`
query _GetProductTypesChoices($first: Int!, $query: String!) {
productTypes(first: $first, filter: {search: $query}) {
edges {
node {
id
name
slug
}
}
}
}
`;
/**
* __use_GetProductTypesChoicesQuery__
*
* To run a query within a React component, call `use_GetProductTypesChoicesQuery` and pass it any options that fit your needs.
* When your component renders, `use_GetProductTypesChoicesQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = use_GetProductTypesChoicesQuery({
* variables: {
* first: // value for 'first'
* query: // value for 'query'
* },
* });
*/
export function use_GetProductTypesChoicesQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types._GetProductTypesChoicesQuery, Types._GetProductTypesChoicesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useQuery<Types._GetProductTypesChoicesQuery, Types._GetProductTypesChoicesQueryVariables>(_GetProductTypesChoicesDocument, options);
}
export function use_GetProductTypesChoicesLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types._GetProductTypesChoicesQuery, Types._GetProductTypesChoicesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useLazyQuery<Types._GetProductTypesChoicesQuery, Types._GetProductTypesChoicesQueryVariables>(_GetProductTypesChoicesDocument, options);
}
export type _GetProductTypesChoicesQueryHookResult = ReturnType<typeof use_GetProductTypesChoicesQuery>;
export type _GetProductTypesChoicesLazyQueryHookResult = ReturnType<typeof use_GetProductTypesChoicesLazyQuery>;
export type _GetProductTypesChoicesQueryResult = Apollo.QueryResult<Types._GetProductTypesChoicesQuery, Types._GetProductTypesChoicesQueryVariables>;
export const TriggerWebhookDryRunDocument = gql`
mutation TriggerWebhookDryRun($objectId: ID!, $query: String!) {
webhookDryRun(objectId: $objectId, query: $query) {

View file

@ -8323,6 +8323,87 @@ export type AddressValidationRulesQueryVariables = Exact<{
export type AddressValidationRulesQuery = { __typename: 'Query', addressValidationRules: { __typename: 'AddressValidationData', allowedFields: Array<string>, countryAreaChoices: Array<{ __typename: 'ChoiceValue', raw: string | null, verbose: string | null }> } | null };
export type _GetDynamicLeftOperandsQueryVariables = Exact<{
first: Scalars['Int'];
query: Scalars['String'];
}>;
export type _GetDynamicLeftOperandsQuery = { __typename: 'Query', attributes: { __typename: 'AttributeCountableConnection', edges: Array<{ __typename: 'AttributeCountableEdge', node: { __typename: 'Attribute', id: string, name: string | null, slug: string | null, inputType: AttributeInputTypeEnum | null } }> } | null };
export type _GetChannelOperandsQueryVariables = Exact<{ [key: string]: never; }>;
export type _GetChannelOperandsQuery = { __typename: 'Query', channels: Array<{ __typename: 'Channel', id: string, name: string, slug: string }> | null };
export type _SearchCollectionsOperandsQueryVariables = Exact<{
first: Scalars['Int'];
collectionsSlugs?: InputMaybe<Array<Scalars['String']> | Scalars['String']>;
}>;
export type _SearchCollectionsOperandsQuery = { __typename: 'Query', search: { __typename: 'CollectionCountableConnection', edges: Array<{ __typename: 'CollectionCountableEdge', node: { __typename: 'Collection', id: string, name: string, slug: string } }> } | null };
export type _SearchCategoriesOperandsQueryVariables = Exact<{
after?: InputMaybe<Scalars['String']>;
first: Scalars['Int'];
categoriesSlugs?: InputMaybe<Array<Scalars['String']> | Scalars['String']>;
}>;
export type _SearchCategoriesOperandsQuery = { __typename: 'Query', search: { __typename: 'CategoryCountableConnection', edges: Array<{ __typename: 'CategoryCountableEdge', node: { __typename: 'Category', id: string, name: string, slug: string } }> } | null };
export type _SearchProductTypesOperandsQueryVariables = Exact<{
after?: InputMaybe<Scalars['String']>;
first: Scalars['Int'];
productTypesSlugs?: InputMaybe<Array<Scalars['String']> | Scalars['String']>;
}>;
export type _SearchProductTypesOperandsQuery = { __typename: 'Query', search: { __typename: 'ProductTypeCountableConnection', edges: Array<{ __typename: 'ProductTypeCountableEdge', node: { __typename: 'ProductType', id: string, name: string, slug: string } }> } | null };
export type _SearchAttributeOperandsQueryVariables = Exact<{
attributesSlugs?: InputMaybe<Array<Scalars['String']> | Scalars['String']>;
choicesIds?: InputMaybe<Array<Scalars['ID']> | Scalars['ID']>;
first: Scalars['Int'];
}>;
export type _SearchAttributeOperandsQuery = { __typename: 'Query', search: { __typename: 'AttributeCountableConnection', edges: Array<{ __typename: 'AttributeCountableEdge', node: { __typename: 'Attribute', id: string, name: string | null, slug: string | null, inputType: AttributeInputTypeEnum | null, choices: { __typename: 'AttributeValueCountableConnection', edges: Array<{ __typename: 'AttributeValueCountableEdge', node: { __typename: 'AttributeValue', id: string, name: string | null, slug: string } }> } | null } }> } | null };
export type _GetAttributeChoicesQueryVariables = Exact<{
slug: Scalars['String'];
first: Scalars['Int'];
query: Scalars['String'];
}>;
export type _GetAttributeChoicesQuery = { __typename: 'Query', attribute: { __typename: 'Attribute', choices: { __typename: 'AttributeValueCountableConnection', edges: Array<{ __typename: 'AttributeValueCountableEdge', node: { __typename: 'AttributeValue', id: string, name: string | null, slug: string } }> } | null } | null };
export type _GetCollectionsChoicesQueryVariables = Exact<{
first: Scalars['Int'];
query: Scalars['String'];
}>;
export type _GetCollectionsChoicesQuery = { __typename: 'Query', collections: { __typename: 'CollectionCountableConnection', edges: Array<{ __typename: 'CollectionCountableEdge', node: { __typename: 'Collection', id: string, name: string, slug: string } }> } | null };
export type _GetCategoriesChoicesQueryVariables = Exact<{
first: Scalars['Int'];
query: Scalars['String'];
}>;
export type _GetCategoriesChoicesQuery = { __typename: 'Query', categories: { __typename: 'CategoryCountableConnection', edges: Array<{ __typename: 'CategoryCountableEdge', node: { __typename: 'Category', id: string, name: string, slug: string } }> } | null };
export type _GetProductTypesChoicesQueryVariables = Exact<{
first: Scalars['Int'];
query: Scalars['String'];
}>;
export type _GetProductTypesChoicesQuery = { __typename: 'Query', productTypes: { __typename: 'ProductTypeCountableConnection', edges: Array<{ __typename: 'ProductTypeCountableEdge', node: { __typename: 'ProductType', id: string, name: string, slug: string } }> } | null };
export type TriggerWebhookDryRunMutationVariables = Exact<{
objectId: Scalars['ID'];
query: Scalars['String'];