Skip to content

Commit

Permalink
Advanced Filters (refinedev#455)
Browse files Browse the repository at this point in the history
* Export TitleProps on antd.

* Add CrudFilter interfaces.

* Optimize new CrudFilter interface for core.

* Optimize new CrudFilter interface for example.

* Add filter mapper on nestjsx-curd data provider.

* Map Antd:Filter -> Refine:CrudFilter on useTable onChange event.

* Implemted new CrudFilter interface for ExportButton.

* Implemted new CrudFilter interface for useRadioGroup.

* Implemted new CrudFilter interface for useSelect.

* Implemted new CrudFilter interface for syncWithLocation.

* Replace query-string -> qs library.

* Fixed core tests.

* Fixed useTable permanentFilter.

* Export crud interfaces.

* Add filter mapper on json-server data provider.

* Fixed useCheckboxGroup filters param.

* Fixed json-server data provider tests.

* Add RcFile, UploadFile export for useStrapiUpload.

* Fixed tests on strapi data provider.

* cleanup

* Fixed tests.
  • Loading branch information
yildirayunlu committed May 18, 2021
1 parent fdf4d37 commit fb224a6
Show file tree
Hide file tree
Showing 26 changed files with 19,709 additions and 81,957 deletions.
946 changes: 583 additions & 363 deletions example/package-lock.json

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions example/src/components/pages/post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,13 @@ interface ICategory {
export const PostList = (props: any) => {
const translate = useTranslate();
const { tableProps, sorter, filters } = useTable<IPost>({
// permanentFilter: {
// categoryId: [50, 49],
// },
// permanentFilter: [
// {
// field: "createdAt",
// operator: "gte",
// value: "2021-05-17",
// },
// ],
initialSorter: [
{
field: "createdAt",
Expand Down
62,698 changes: 0 additions & 62,698 deletions examples/strapi/package-lock.json

This file was deleted.

36,878 changes: 18,430 additions & 18,448 deletions packages/core/package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@testing-library/user-event": "^7.2.1",
"@types/lodash": "^4.14.168",
"@types/pluralize": "^0.0.29",
"@types/qs": "^6.9.6",
"@types/react": "^17.0.1",
"@types/react-csv": "^1.1.1",
"@types/react-dom": "^17.0.0",
Expand All @@ -62,7 +63,7 @@
"lodash": "^4.17.21",
"papaparse": "^5.3.0",
"pluralize": "^8.0.0",
"query-string": "^6.14.0",
"qs": "^6.10.1",
"react-csv": "^2.0.3",
"react-markdown": "^5.0.3",
"react-query": "^3.8.2",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/components/antd/antd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ export { default as Tooltip } from "antd/lib/tooltip";

export type { TypographyProps } from "antd/lib/typography";
export { default as Typography } from "antd/lib/typography";
export type { TitleProps } from "antd/lib/typography/Title";

export type { UploadProps } from "antd/lib/upload";

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/components/buttons/export/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import { useResourceWithRoute, useTranslate } from "@hooks";
import {
ResourceRouterParams,
Sort,
Filters,
IDataContext,
BaseRecord,
CrudFilters,
} from "../../../interfaces";
import { DataContext } from "@contexts/data";
import { CSVDownloadProps } from "./csvDownload.interface";

type ExportButtonProps = ButtonProps & {
resourceName?: string;
sorter?: Sort;
filters?: Filters;
filters?: CrudFilters;
maxItemCount?: number;
pageSize?: number;
mapData?(value: BaseRecord, index: number, array: BaseRecord[]): BaseRecord;
Expand Down
43 changes: 41 additions & 2 deletions packages/core/src/contexts/data/IDataContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,46 @@ export interface Search {
value?: string;
}

export type Filters = Record<string, (string | number | boolean)[] | null>;
// Filters are used as a suffix of a field name:

// | Filter | Description |
// | ------------------- | ------------------------------ |
// | No suffix or `eq` | Equal |
// | ne | Not equal |
// | lt | Less than |
// | gt | Greater than |
// | lte | Less than or equal to |
// | gte | Greater than or equal to |
// | in | Included in an array |
// | nin | Not included in an array |
// | contains | Contains |
// | ncontains | Doesn't contain |
// | containss | Contains, case sensitive |
// | ncontainss | Doesn't contain, case sensitive|
// | null | Is null or not null |

export type CrudOperators =
| "eq"
| "ne"
| "lt"
| "gt"
| "lte"
| "gte"
| "in"
| "nin"
| "contains"
| "ncontains"
| "containss"
| "ncontainss"
| "null";

export type CrudFilter = {
field: string;
operator: CrudOperators;
value: any;
};

export type CrudFilters = CrudFilter[];

export interface GetListResponse<RecordType = BaseRecord> {
data: RecordType[];
Expand Down Expand Up @@ -60,7 +99,7 @@ export interface IDataContext {
pagination?: Pagination;
search?: Search;
sort?: Sort;
filters?: Filters;
filters?: CrudFilter[];
},
) => Promise<GetListResponse<RecordType>>;
getMany: <RecordType extends BaseRecord = BaseRecord>(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`definitions/table stringify table params correctly 1`] = `"current=1&pageSize=10&categoryId[]=1&categoryId[]=2&sort[]=id&sort[]=title&order[]=descend&order[]=descend"`;
exports[`definitions/table stringify table params correctly 1`] = `"current=1&pageSize=10&categoryId__in[]=1&categoryId__in[]=2&sort[]=id&sort[]=title&order[]=descend&order[]=descend"`;

exports[`definitions/table stringify table single sort params correctly 1`] = `"current=1&pageSize=10&categoryId[]=1&categoryId[]=2&sort=id&order=descend"`;
exports[`definitions/table stringify table single sort params correctly 1`] = `"current=1&pageSize=10&categoryId__in[]=1&categoryId__in[]=2&sort=id&order=descend"`;
40 changes: 21 additions & 19 deletions packages/core/src/definitions/table/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { stringifyTableParams, parseTableParams } from "./";
import { TablePaginationConfig } from "@components/antd";
import { Sort, Filters } from "../../interfaces";
import { Sort, CrudFilters } from "../../interfaces";

describe("definitions/table", () => {
it("stringify table params correctly", async () => {
Expand All @@ -19,9 +19,14 @@ describe("definitions/table", () => {
order: "descend",
},
];
const filters: Filters = {
categoryId: [1, 2],
};

const filters: CrudFilters = [
{
field: "categoryId",
operator: "in",
value: [1, 2],
},
];

const url = stringifyTableParams({ pagination, sorter, filters });
expect(url).toMatchSnapshot();
Expand All @@ -34,37 +39,34 @@ describe("definitions/table", () => {
};

const sorter: Sort = { field: "id", order: "descend" };
const filters: Filters = {
categoryId: [1, 2],
};
const filters: CrudFilters = [
{
field: "categoryId",
operator: "in",
value: [1, 2],
},
];

const url = stringifyTableParams({ pagination, sorter, filters });
expect(url).toMatchSnapshot();
});

it("parse table params with single sorter correctly", async () => {
const url =
"current=1&pageSize=10&categoryId[]=1&categoryId[]=2&sort=id&order=descend";

const initialSorter: Sort = [];
const initialFilter: Filters = {};
"?current=1&pageSize=10&categoryId__in[]=1&categoryId__in[]=2&sort=id&order=descend";

const {
parsedCurrent,
parsedPageSize,
parsedSorter,
parsedFilters,
} = parseTableParams({
initialSorter,
initialFilter,
url,
});
} = parseTableParams(url);

expect(parsedCurrent).toBe(1);
expect(parsedPageSize).toBe(10);
expect(parsedSorter).toStrictEqual([{ field: "id", order: "descend" }]);
expect(parsedFilters).toStrictEqual({
categoryId: ["1", "2"],
});
expect(parsedFilters).toStrictEqual([
{ field: "categoryId", operator: "in", value: ["1", "2"] },
]);
});
});
75 changes: 41 additions & 34 deletions packages/core/src/definitions/table/index.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,31 @@
import qs, { StringifyOptions } from "query-string";
import qs, { IStringifyOptions } from "qs";

import { Sort, Filters } from "../../interfaces";
import { Sort, CrudFilters, CrudOperators } from "../../interfaces";
import {
SorterResult,
SortOrder,
TablePaginationConfig,
} from "antd/lib/table/interface";
import mergeWith from "lodash/mergeWith";
import isArray from "lodash/isArray";
import get from "lodash/get";

const queryStringOptions = (): StringifyOptions => {
return {
arrayFormat: "bracket",
skipNull: true,
};
};

export const merge = (object: any, source: any) => {
return mergeWith(object, source, (val, src): any => {
if (isArray(val)) {
if (Array.isArray(val)) {
return val.concat(src);
}
});
};

export const parseTableParams = (params: {
initialSorter?: Sort;
initialFilter?: Filters;
url: string;
}) => {
const options = queryStringOptions();
const { initialSorter, initialFilter, url } = params;

export const parseTableParams = (url: string) => {
const { current, pageSize, sort, order, ...filters } = qs.parse(
url,
options,
url.substring(1), // remove first ? character
);

let parsedSorter: Sort;
let parsedSorter: Sort = [];
if (Array.isArray(sort) && Array.isArray(order)) {
const arrSorter: SorterResult<any>[] = [];

sort.map((item, index) => {
sort.forEach((item: any, index: any) => {
const sortOrder = order[index] as SortOrder;
arrSorter.push({
field: item,
Expand All @@ -60,23 +43,39 @@ export const parseTableParams = (params: {
];
}

const parsedFilters: CrudFilters = [];
Object.keys(filters).map((item) => {
const [field, operator] = item.split("__");
const value = filters[item];

parsedFilters.push({
field,
operator: operator as CrudOperators,
value,
});
});

return {
parsedCurrent: current && Number(current),
parsedPageSize: pageSize && Number(pageSize),
parsedSorter: merge(initialSorter, parsedSorter),
parsedFilters: merge(initialFilter, filters) as Filters,
parsedSorter,
parsedFilters,
};
};

export const stringifyTableParams = (params: {
pagination: TablePaginationConfig;
sorter: Sort;
filters: Filters;
filters: CrudFilters;
}): string => {
const options: IStringifyOptions = {
skipNulls: true,
arrayFormat: "brackets",
encode: false,
};

const { pagination, sorter, filters } = params;
const options = queryStringOptions();

const qsFilters = qs.stringify(filters, options);
let sortFields;
let sortOrders;

Expand All @@ -93,6 +92,13 @@ export const stringifyTableParams = (params: {

let queryString = `current=${pagination.current}&pageSize=${pagination.pageSize}`;

const qsFilterItems: { [key: string]: string } = {};
filters.map((filterItem) => {
qsFilterItems[`${filterItem.field}__${filterItem.operator}`] =
filterItem.value;
});

const qsFilters = qs.stringify(qsFilterItems, options);
if (qsFilters) {
queryString = `${queryString}&${qsFilters}`;
}
Expand Down Expand Up @@ -127,12 +133,13 @@ export const getDefaultSortOrder = (

export const getDefaultFilter = (
columnName: string,
filters?: Filters,
filters?: CrudFilters,
): string[] | undefined => {
const value = get(filters, columnName);
if (!filters || !value) {
return undefined;
const value = filters?.find(({ field }) => field === columnName);

if (value) {
return value.value || [];
}

return (value ?? []) as string[];
return undefined;
};
4 changes: 2 additions & 2 deletions packages/core/src/hooks/data/useList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
IDataContext,
Sort,
Search,
Filters,
CrudFilters,
Pagination,
BaseRecord,
} from "../../interfaces";
Expand All @@ -16,7 +16,7 @@ interface UseListConfig {
pagination?: Pagination;
search?: Search;
sort?: Sort;
filters?: Filters;
filters?: CrudFilters;
}

export const useList = <RecordType = BaseRecord>(
Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/hooks/fields/useCheckboxGroup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ import { CheckboxGroupProps } from "antd/lib/checkbox";
import { QueryObserverResult } from "react-query";

import { useList } from "@hooks";
import { Sort, Option, BaseRecord, GetListResponse } from "../../../interfaces";
import {
Sort,
Option,
BaseRecord,
GetListResponse,
CrudFilters,
} from "../../../interfaces";

export type useCheckboxGroupProps = {
resource: string;
optionLabel?: string;
optionValue?: string;
sort?: Sort;
filters?: Record<string, (string | number | boolean)[] | null>;
filters?: CrudFilters;
};

export type UseCheckboxGroupReturnType<
Expand Down
Loading

0 comments on commit fb224a6

Please sign in to comment.