Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Order filtering by the metadata #5331

Merged
merged 10 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sweet-otters-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"saleor-dashboard": patch
---

Now it's possible to filter orders by its metadata.
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ const createAPIHandler = (
return new NoopValuesHandler([]);
}

if (rowType === "metadata") {
return new NoopValuesHandler([]);
}

throw new Error(`Unknown filter element: "${rowType}"`);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ export const createInitialOrderState = (data: InitialOrderAPIResponse[]) =>
isPreorder: createBooleanOptions(),
giftCardBought: createBooleanOptions(),
giftCardUsed: createBooleanOptions(),
customer: [],
ids: [],
created: "",
updatedAt: "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,34 +31,6 @@ describe("ConditionalFilter / API / Orders / InitialOrderState", () => {
expect(result).toEqual(expectedOutput);
});

it("should filter by customer", () => {
// Arrange
const initialOrderState = InitialOrderStateResponse.empty();

initialOrderState.customer = [
{
label: "Customer",
slug: "customer",
value: "test",
},
];

const token = UrlToken.fromUrlEntry(new UrlEntry("s0.customer", "test"));
const expectedOutput = [
{
label: "Customer",
slug: "customer",
value: "test",
},
];

// Act
const result = initialOrderState.filterByUrlToken(token);

// Assert
expect(result).toEqual(expectedOutput);
});

it("should filter by click and collect", () => {
// Arrange
const initialOrderState = InitialOrderStateResponse.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@ export interface InitialOrderState {
isPreorder: ItemOption[];
giftCardBought: ItemOption[];
giftCardUsed: ItemOption[];
customer: ItemOption[];
created: string | string[];
updatedAt: string | string[];
ids: ItemOption[];
}

const isTextInput = (name: string) => ["customer"].includes(name);
const isDateField = (name: string) => ["created", "updatedAt"].includes(name);

export class InitialOrderStateResponse implements InitialOrderState {
Expand All @@ -31,7 +29,6 @@ export class InitialOrderStateResponse implements InitialOrderState {
public isPreorder: ItemOption[] = [],
public giftCardBought: ItemOption[] = [],
public giftCardUsed: ItemOption[] = [],
public customer: ItemOption[] = [],
public created: string | string[] = [],
public updatedAt: string | string[] = [],
public ids: ItemOption[] = [],
Expand All @@ -48,8 +45,8 @@ export class InitialOrderStateResponse implements InitialOrderState {

const entry = this.getEntryByName(token.name);

if (isTextInput(token.name)) {
return entry;
if (!token.isLoadable()) {
return [token.value] as string[];
}

return (entry as ItemOption[]).filter(({ slug }) => slug && token.value.includes(slug));
Expand All @@ -75,8 +72,6 @@ export class InitialOrderStateResponse implements InitialOrderState {
return this.giftCardBought;
case "giftCardUsed":
return this.giftCardUsed;
case "customer":
return this.customer;
case "ids":
return this.ids;
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,6 @@ import { createInitialOrderState } from "../helpers";
import { InitialOrderAPIResponse } from "../types";
import { InitialOrderStateResponse } from "./InitialOrderState";

const getCustomer = (customer: string[]) => {
if (Array.isArray(customer) && customer.length > 0) {
return customer.at(-1) ?? "";
}

return "";
};

const mapIDsToOptions = (ids: string[]) =>
ids.map(id => ({
type: "ids",
Expand Down Expand Up @@ -53,7 +45,6 @@ export const useInitialOrderState = (): InitialOrderAPIState => {
paymentStatus,
status,
authorizeStatus,
customer,
ids,
}: OrderFetchingParams) => {
if (channels.length > 0) {
Expand Down Expand Up @@ -93,14 +84,6 @@ export const useInitialOrderState = (): InitialOrderAPIState => {
status: await statusInit.fetch(),
authorizeStatus: await authorizeStatusInit.fetch(),
chargeStatus: await chargeStatusInit.fetch(),
customer: [
{
type: "customer",
label: "Customer",
value: getCustomer(customer),
slug: "customer",
},
],
ids: mapIDsToOptions(ids),
};

Expand All @@ -115,7 +98,6 @@ export const useInitialOrderState = (): InitialOrderAPIState => {
initialState.isClickAndCollect,
initialState.giftCardBought,
initialState.giftCardUsed,
initialState.customer,
initialState.created,
initialState.updatedAt,
initialState.ids,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ export class Condition {
const isMultiSelect = selectedOption?.type === "multiselect" && valueItems.length > 0;
const isBulkSelect = selectedOption?.type === "bulkselect" && valueItems.length > 0;
const isDate = ["created", "updatedAt", "startDate", "endDate"].includes(token.name);

const value = isMultiSelect || isDate || isBulkSelect ? valueItems : valueItems[0];

if (!selectedOption) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class ConditionSelected {
return (
this.value === "" ||
(isItemOptionArray(this.value) && this.value.length === 0) ||
(isTuple(this.value) && this.value.includes(""))
(isTuple(this.value) && this.value.every(el => el === ""))
);
}

Expand Down
69 changes: 69 additions & 0 deletions src/components/ConditionalFilter/UI/MetadataInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Box, Input } from "@saleor/macaw-ui-next";
import React from "react";

import { FilterEventEmitter } from "./EventEmitter";
import { DoubleTextOperator } from "./types";

interface MetadataInputProps {
index: number;
selected: DoubleTextOperator;
emitter: FilterEventEmitter;
error: boolean;
disabled: boolean;
}

export const MetadataInput = ({
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: WDYT about make this component more generic and name it like DoubleTextInput?

Copy link
Member Author

Choose a reason for hiding this comment

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

I had that in the beginning, but metadata is specific field so i wanted to create something dedicated to this, in case we need to change something just for metadata. Double text input makes sense, but... i cannot foresee what will be needed in the future and want to avoid latter on eg. TripleTextInput etc.

index,
selected,
emitter,
error,
disabled,
}: MetadataInputProps) => {
return (
<Box
display="flex"
className="conditional-metadata"
borderWidth={1}
borderStyle="solid"
borderColor={{
default: "default1",
focusWithin: "accent1",
}}
borderRadius={3}
>
<Input
data-test-id={`right-${index}-1`}
value={selected.value[0] || ""}
onChange={e => {
emitter.changeRightOperator(index, [e.target.value, selected.value[1]]);
}}
onFocus={() => {
emitter.focusRightOperator(index);
}}
onBlur={() => {
emitter.blurRightOperator(index);
}}
error={error}
placeholder="Key"
Copy link
Member

Choose a reason for hiding this comment

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

Suggestion: I think we're missing translations here

disabled={disabled}
/>
<Box __width="1px" backgroundColor="default1Focused" />
<Input
data-test-id={`right-${index}-2`}
value={selected.value[1] || ""}
onChange={e => {
emitter.changeRightOperator(index, [selected.value[0], e.target.value]);
}}
onFocus={() => {
emitter.focusRightOperator(index);
}}
onBlur={() => {
emitter.blurRightOperator(index);
}}
error={error}
placeholder="Value"
disabled={disabled}
/>
</Box>
);
};
14 changes: 14 additions & 0 deletions src/components/ConditionalFilter/UI/RightOperator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import React from "react";

import BulkSelect from "./BulkSelect";
import { FilterEventEmitter } from "./EventEmitter";
import { MetadataInput } from "./MetadataInput";
import {
isBulkSelect,
isCombobox,
isDate,
isDateRange,
isDateTime,
isDateTimeRange,
isDoubleText,
isMultiselect,
isNumberInput,
isNumberRange,
Expand Down Expand Up @@ -261,5 +263,17 @@ export const RightOperator = ({
);
}

if (isDoubleText(selected)) {
return (
<MetadataInput
index={index}
selected={selected}
emitter={emitter}
error={error}
disabled={disabled}
/>
);
}

return <Input disabled value={selected.value} data-test-id={`right-${index}`} />;
};
4 changes: 4 additions & 0 deletions src/components/ConditionalFilter/UI/operators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ComboboxOperator,
DateOperator,
DateTimeOperator,
DoubleTextOperator,
InputOperator,
MultiselectOperator,
NumberRangeOperator,
Expand Down Expand Up @@ -42,3 +43,6 @@ export const isDateRange = (value: SelectedOperator): value is DateOperator =>

export const isDateTimeRange = (value: SelectedOperator): value is DateTimeOperator =>
value.conditionValue?.type === "datetime.range";

export const isDoubleText = (value: SelectedOperator): value is DoubleTextOperator =>
value.conditionValue?.type === "text.double";
9 changes: 8 additions & 1 deletion src/components/ConditionalFilter/UI/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type ConditionOptionTypes = ConditionOption<
| "datetime"
| "date.range"
| "datetime.range"
| "text.double"
>;

export interface Row {
Expand Down Expand Up @@ -60,7 +61,8 @@ export type SelectedOperator =
| DateOperator
| DateTimeOperator
| DateRangeOperator
| DateTimeRangeOperator;
| DateTimeRangeOperator
| DoubleTextOperator;

export interface InputOperator {
value: string | RightOperatorOption;
Expand Down Expand Up @@ -119,6 +121,11 @@ export interface DateTimeRangeOperator {
conditionValue: ConditionOption<"datetime.range"> | null;
}

export interface DoubleTextOperator {
value: [string, string];
conditionValue: ConditionOption<"text.double"> | null;
}

export interface FilterEvent extends Event {
detail?:
| RowAddData
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const ORDER_STATICS = [
"isPreorder",
"isClickAndCollect",
"channels",
"customer",
"ids",
];

Expand Down
13 changes: 13 additions & 0 deletions src/components/ConditionalFilter/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ export const STATIC_CONDITIONS = {
value: "input-1",
},
],
metadata: [
{
type: "text.double",
label: "is",
value: "input-1",
},
],
};

export const CONSTRAINTS = {
Expand Down Expand Up @@ -258,6 +265,12 @@ export const STATIC_ORDER_OPTIONS: LeftOperand[] = [
type: "customer",
slug: "customer",
},
{
value: "metadata",
label: "Metadata",
type: "metadata",
slug: "metadata",
},
];

export const STATIC_OPTIONS = [
Expand Down
1 change: 1 addition & 0 deletions src/components/ConditionalFilter/controlsType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const CONTROL_DEFAULTS = {
datetime: "",
"date.range": ["", ""] as [string, string],
"datetime.range": ["", ""] as [string, string],
"text.double": ["", ""] as [string, string],
};

export const getDefaultByControlName = (name: string): ConditionValue =>
Expand Down
Loading
Loading