From 76d8528c10bfa55a89a8dbee1372790ffd1ed534 Mon Sep 17 00:00:00 2001
From: James Tattersall <10601770+jamerst@users.noreply.github.com>
Date: Sat, 7 May 2022 17:30:09 +0100
Subject: [PATCH] Add support Fix single quotes not being escaped in strings
---
README.md | 11 ++-
.../FilterBuilder/components/FilterRoot.tsx | 29 +++---
packages/base/FilterBuilder/hooks.ts | 90 +++++++++++--------
packages/base/FilterBuilder/translation.ts | 42 +++++++++
packages/base/FilterBuilder/types.ts | 30 ++++++-
packages/base/FilterBuilder/utils.ts | 4 +-
packages/base/components/ODataGridBase.tsx | 34 +++++--
packages/o-data-grid-pro/README.md | 13 +--
packages/o-data-grid-pro/dev/App.tsx | 20 ++---
packages/o-data-grid-pro/package.json | 6 +-
packages/o-data-grid-pro/pnpm-lock.yaml | 44 +++++++--
packages/o-data-grid-pro/src/index.ts | 7 +-
packages/o-data-grid/README.md | 11 ++-
packages/o-data-grid/dev/App.tsx | 20 ++---
packages/o-data-grid/package.json | 6 +-
packages/o-data-grid/pnpm-lock.yaml | 44 +++++++--
packages/o-data-grid/src/index.ts | 7 +-
17 files changed, 296 insertions(+), 122 deletions(-)
create mode 100644 packages/base/FilterBuilder/translation.ts
diff --git a/README.md b/README.md
index 8af74ed..320beec 100644
--- a/README.md
+++ b/README.md
@@ -104,14 +104,17 @@ _* = not applicable to collection fields_
| `collectionFields` | `ODataGridColDef` | | Column definitions for the subfields of the collection. Any properties marked with * are not supported. |
| `datePickerProps` | [`DatePickerProps`](https://mui.com/api/date-picker/) | | Props to pass to the `DatePicker` component for columns with type `date` |
| `dateTimePickerProps` | [`DateTimePickerProps`](https://mui.com/api/date-time-picker/) | | Props to pass to the `DateTimePicker` component for columns with type `datetime` |
+| `expand` | `Expand` | | Include related entities using the `$expand` clause. |
| `filterable` | `boolean` | | Hides the field and does not allow filtering in the FilterBuilder when set to `false`. |
| `filterField` | `string` | | If the field name is different to the field which should be used for filtering, provide the field for filtering here. See also: `filterType`. |
+| `filterOnly` | `boolean` | `false` | Set to true if the field is for filtering only and cannot be displayed as a column in the datagrid. |
| `filterOperators` | `Operation[]` | `["eq", "ne", "gt", "lt", "ge", "le", "contains", "null", "notnull"]` | Array of supported filter operations for the field. |
| `filterType` | `string` | | If the type of the field to be filtered is different to that of the displayed field, provide the type here. See also: `filterField`. |
-| `getCustomFilterString` | `(op: Operation, value: any) => string` | | Function to generate a custom filter string for use in the `$filter` clause. |
-| `getCustomQueryString` | `(op: Operation, value: any) => ({ [key: string]: string })` | | Function to generate a custom set of query string values to add to the OData request. Allows custom filtering to be performed which is not supported by OData. |
+| `getCustomFilterString` | `(op: Operation, value: any) => string \| FilterCompute \| boolean` | | Function to generate a custom filter string for use in the `$filter` clause. Return `false` to skip and not add it to the `$filter` clause.
Also supports the use of the `$compute` clause by returning a `FilterCompute`. The computed property/properties can also be added to `$select` by returning a `ComputeSelect`. |
+| `getCustomQueryString` | `(op: Operation, value: any) => ({ [key: string]: string })` | | Function to generate a custom set of query string values to add to the OData request. |
| `label` | `string` | Defaults to the same value as `headerName` or `field` | Text to be displayed in the field selection dropdown. |
| `nullable` | `boolean` | | Adds an "Unknown" option to the value dropdown for columns with type `boolean` if set to `true`. |
+| `select` | `string` | | Additional fields to add to the `$select` clause. |
| `selectProps` | `{ selectProps?: SelectProps, formControlProps?: FormControlProps, label?: string }` | | Props to pass to the `Select`, `FormControl` and `Label` components for this column in the filter. See also: `textFieldProps`. |
| `sortField` | `string` | | If the name of the field to sort by is different to that of the displayed field, provide the name for sorting by here. |
| `textFieldProps` | [`TextFieldProps`](https://mui.com/api/text-field/) | | Props to pass to the `TextField` component in the filter for this column. See also: `selectProps`. |
@@ -129,8 +132,8 @@ _* = not applicable to collection fields_
| `filter` | `SerialisedGroup` | | Allows setting the state of the FilterBuilder using a `SerialisedGroup`. You could use this to implement filter saving and restoring.
Changing the value of this property will cause `restoreState` to be called, but with the `state` property undefined. |
| `localeText` | [`FilterBuilderLocaleText`](#FilterBuilderLocaleText) | | Localization strings for `FilterBuilder` (see [Localization](#localization) section) |
| `localizationProviderProps` | [`LocalizationProviderProps`](https://mui.com/components/date-picker/#localization) | | Props to pass to the `LocalizationProvider` component for the `DatePicker` and `DateTimePicker` components |
-| `onSubmit` | `(filter: string, serialised: SerialisedGroup \| undefined, queryString: QueryStringCollection \| undefined) => (void \| any)` | | Function called when FilterBuilder is submitted (e.g. when the search button is clicked). You should use this to trigger the OData request.
`filter` is the OData filter string, `serialised` is a serialised form of the query which can be used to load the query back into the filter builder. |
-| `onRestoreState` | `(filter: string, serialised: SerialisedGroup \| undefined, queryString: QueryStringCollection \| undefined, state?: any) => void` | | Function called when the state of the FilterBuilder is restored (e.g. from history navigation). You should also use this to trigger the OData request alongside the `onSubmit` callback.
`state` is the the value of `history.state` that the query was restored from. `state` will be undefined if the call is as a result of the `filter` property changing. |
+| `onSubmit` | `(params: FilterParameters) => (void \| any)` | | Function called when FilterBuilder is submitted (e.g. when the search button is clicked). You should use this to trigger the OData request.
`params.filter` is the OData filter string, `params.serialised` is a serialised form of the query which can be used to load the query back into the filter builder. |
+| `onRestoreState` | `(params: FilterParameters, state?: any) => void` | | Function called when the state of the FilterBuilder is restored (e.g. from history navigation). You should also use this to trigger the OData request alongside the `onSubmit` callback.
`state` is the the value of `history.state` that the query was restored from. `state` will be undefined if the call is as a result of the `filter` property changing. |
| `searchMenuItems` | `({ label: string, onClick: () => void })[]` | | Array of entries to add to the dropdown menu next to the Search button of the `FilterBuilder` |
## Localization
diff --git a/packages/base/FilterBuilder/components/FilterRoot.tsx b/packages/base/FilterBuilder/components/FilterRoot.tsx
index df1de1b..85984f5 100644
--- a/packages/base/FilterBuilder/components/FilterRoot.tsx
+++ b/packages/base/FilterBuilder/components/FilterRoot.tsx
@@ -1,4 +1,4 @@
-import React, { Fragment, useCallback, useEffect, useRef, useState } from "react"
+import React, { Fragment, useCallback, useEffect, useState } from "react"
import { useSetRecoilState } from "recoil";
import { ArrowDropDown } from "@mui/icons-material";
import { Button, ButtonGroup, Grid, MenuItem, MenuList, Paper, Popover } from "@mui/material";
@@ -26,7 +26,6 @@ const FilterRoot = ({ props }: FilterRootProps) => {
const odataFilter = UseODataFilter();
const odataFilterWithState = UseODataFilterWithState();
- const currentFilter = useRef("");
const [anchor, setAnchor] = useState(null);
@@ -37,9 +36,7 @@ const FilterRoot = ({ props }: FilterRootProps) => {
const result = odataFilter();
if (result.filter) {
- currentFilter.current = result.filter;
-
- const returned = onSubmit(result.filter, result.serialised, result.queryString);
+ const returned = onSubmit({ ...result, filter: result.filter });
if (disableHistory !== true) {
window.history.pushState(
@@ -48,6 +45,8 @@ const FilterRoot = ({ props }: FilterRootProps) => {
...returned,
filterBuilder: {
filter: result.filter,
+ compute: result.compute,
+ select: result.select,
serialised: result.serialised,
queryString: result.queryString
}
@@ -60,13 +59,11 @@ const FilterRoot = ({ props }: FilterRootProps) => {
}, [onSubmit, odataFilter, disableHistory]);
const reset = useCallback(() => {
- currentFilter.current = "";
-
setClauses(initialClauses.update(rootConditionUuid, (c) => ({ ...c as ConditionClause, field: props.schema[0].field })));
setTree(initialTree);
if (onSubmit) {
- onSubmit("", undefined, undefined);
+ onSubmit({ filter: "" });
}
if (disableHistory !== true) {
@@ -91,31 +88,31 @@ const FilterRoot = ({ props }: FilterRootProps) => {
}, [props.schema, setClauses, setTree]);
const restoreState = useCallback((state: any, isPopstate: boolean) => {
- let filter = "", obj, queryString;
+ let filter = "", serialised, queryString, compute, select;
if (state?.filterBuilder) {
if (state.filterBuilder.reset === true && isPopstate === true) {
restoreDefault();
}
+ compute = state.filterBuilder.compute as string;
filter = state.filterBuilder.filter as string;
- obj = state.filterBuilder.serialised as SerialisedGroup;
+ select = state.filterBuilder.select as string[];
+ serialised = state.filterBuilder.serialised as SerialisedGroup;
queryString = state.filterBuilder.queryString as QueryStringCollection;
} else {
restoreDefault();
}
- if (filter && obj) {
- currentFilter.current = filter;
-
- const [tree, clauses] = deserialise(obj);
+ if (filter && serialised) {
+ const [tree, clauses] = deserialise(serialised);
setClauses(clauses);
setTree(tree);
}
if (onRestoreState) {
- onRestoreState(filter, obj, queryString, state);
+ onRestoreState({ compute, filter, queryString, select, serialised}, state);
}
}, [onRestoreState, restoreDefault, setClauses, setTree]);
@@ -128,7 +125,7 @@ const FilterRoot = ({ props }: FilterRootProps) => {
if (onRestoreState) {
const result = odataFilterWithState(clauses, tree);
- onRestoreState(result.filter ?? "", result.serialised, result.queryString);
+ onRestoreState({ ...result, filter: result.filter ?? "" });
}
}, [setClauses, setTree, onRestoreState, odataFilterWithState]);
diff --git a/packages/base/FilterBuilder/hooks.ts b/packages/base/FilterBuilder/hooks.ts
index fab4970..d5eb392 100644
--- a/packages/base/FilterBuilder/hooks.ts
+++ b/packages/base/FilterBuilder/hooks.ts
@@ -2,7 +2,8 @@ import { useCallback } from "react";
import { useRecoilValue, waitForAll } from "recoil"
import { rootGroupUuid } from "./constants";
import { clauseState, schemaState, treeState } from "./state"
-import { BaseFieldDef, SerialisedCondition, ConditionClause, FieldDef, SerialisedGroup, GroupClause, Operation, QueryStringCollection, StateClause, StateTree, TreeGroup } from "./types";
+import { defaultTranslators } from "./translation";
+import { BaseFieldDef, SerialisedCondition, ConditionClause, FieldDef, SerialisedGroup, GroupClause, Operation, QueryStringCollection, StateClause, StateTree, TreeGroup, FilterCompute, FilterTranslator } from "./types";
export const UseODataFilter = () => {
const schema = useRecoilValue(schemaState);
@@ -23,6 +24,8 @@ export const UseODataFilterWithState = () => {
type BuiltInnerQuery = {
filter?: string,
+ compute?: string,
+ select?: string[],
queryString?: QueryStringCollection
}
@@ -53,12 +56,16 @@ const buildGroup = (schema: FieldDef[], clauses: StateClause, tree: StateTree, i
if (childClauses.length > 1) {
return {
filter: `(${childClauses.filter(c => c.filter).map(c => c.filter).join(` ${clause.connective} `)})`,
+ compute: `${childClauses.filter(c => c.compute).map(c => c.compute).join(",")}`,
+ select: childClauses.filter(c => c.select).flatMap(c => c.select!),
serialised: { connective: clause.connective, children: childClauses.map(c => c.serialised) },
queryString: childClauses.reduce((x, c) => ({ ...x, ...c.queryString }), {})
};
} else if (childClauses.length === 1) {
return {
filter: childClauses[0].filter,
+ compute: childClauses[0].compute,
+ select: childClauses[0].select,
serialised: { connective: clause.connective, children: [childClauses[0].serialised] },
queryString: childClauses[0].queryString
}
@@ -109,60 +116,71 @@ const buildCondition = (schema: FieldDef[], clauses: StateClause, id: string): (
}
if (typeof innerResult !== "boolean") {
- if (typeof innerResult === "string") {
+ if (innerResult.filter) {
return {
- filter: innerResult,
+ filter: innerResult.filter,
+ compute: innerResult.compute,
+ select: innerResult.select,
serialised: condition
- };
+ }
} else {
return {
serialised: condition,
- queryString: innerResult
- }
+ queryString: innerResult.queryString
+ };
}
} else {
return false;
}
}
-const buildInnerCondition = (schema: BaseFieldDef, field: string, op: Operation, value: any): string | QueryStringCollection | boolean => {
+const buildInnerCondition = (schema: BaseFieldDef, field: string, op: Operation, value: any): BuiltInnerQuery | boolean => {
if (schema.getCustomQueryString) {
- return schema.getCustomQueryString(op, value);
+ return {
+ queryString: schema.getCustomQueryString(op, value)
+ };
}
if (schema.getCustomFilterString) {
- return schema.getCustomFilterString(op, value)
- }
+ const result = schema.getCustomFilterString(op, value);
- if (op === "contains") {
- if ((schema.type && schema.type !== "string") || typeof value !== "string") {
- console.warn(`Warning: operation "contains" is only supported for fields of type "string"`);
- return false;
- }
- if (schema.caseSensitive === true) {
- return `contains(${field}, '${value}')`;
- } else {
- return `contains(tolower(${field}), tolower('${value}'))`;
- }
- } else if (op === "null") {
- return `${field} eq null`;
- } else if (op === "notnull") {
- return `${field} ne null`;
- } else {
- if (schema.type === "date") {
- return `date(${field}) ${op} ${value}`;
- } else if (schema.type === "datetime") {
- return `${field} ${op} ${value}`;
- } else if (schema.type === "boolean") {
- return `${field} ${op} ${value}`;
- } else if (!schema.type || schema.type === "string" || typeof value === "string") {
- if (schema.caseSensitive === true) {
- return `${field} ${op} '${value}'`;
+ if (typeof result === "string") {
+ return {
+ filter: result
+ }
+ } else if (typeof result !== "boolean") {
+ const compute = result.compute;
+ if (typeof compute === "string") {
+ return {
+ filter: result.filter,
+ compute: compute
+ };
} else {
- return `tolower(${field}) ${op} tolower('${value}')`;
+ return {
+ filter: result.filter,
+ compute: compute.compute,
+ select: compute.select
+ };
}
} else {
- return `${field} ${op} ${value}`;
+ return result;
}
}
+
+ let translator: FilterTranslator;
+ if (op in defaultTranslators) {
+ translator = defaultTranslators[op]!;
+ } else {
+ translator = defaultTranslators["default"]!;
+ }
+
+ const result = translator(schema, field, op, value);
+
+ if (typeof result === "string") {
+ return {
+ filter: result
+ };
+ } else {
+ return result;
+ }
}
\ No newline at end of file
diff --git a/packages/base/FilterBuilder/translation.ts b/packages/base/FilterBuilder/translation.ts
new file mode 100644
index 0000000..99bc3b9
--- /dev/null
+++ b/packages/base/FilterBuilder/translation.ts
@@ -0,0 +1,42 @@
+import { FilterTranslatorCollection } from "./types";
+import { escapeODataString } from "./utils";
+
+export const defaultTranslators: FilterTranslatorCollection = {
+ "contains": (schema, field, op, value) => {
+ if ((schema.type && schema.type !== "string") || typeof value !== "string") {
+ console.warn(`Warning: operation "contains" is only supported for fields of type "string"`);
+ return false;
+ }
+ if (schema.caseSensitive === true) {
+ return `contains(${field}, '${escapeODataString(value)}')`;
+ } else {
+ return `contains(tolower(${field}), tolower('${escapeODataString(value)}'))`;
+ }
+ },
+
+ "null": (schema, field, op, value) => {
+ return `${field} eq null`;
+ },
+
+ "notnull": (schema, field, op, value) => {
+ return `${field} ne null`;
+ },
+
+ "default": (schema, field, op, value) => {
+ if (schema.type === "date") {
+ return `date(${field}) ${op} ${value}`;
+ } else if (schema.type === "datetime") {
+ return `${field} ${op} ${value}`;
+ } else if (schema.type === "boolean") {
+ return `${field} ${op} ${value}`;
+ } else if (!schema.type || schema.type === "string" || typeof value === "string") {
+ if (schema.caseSensitive === true) {
+ return `${field} ${op} '${escapeODataString(value)}'`;
+ } else {
+ return `tolower(${field}) ${op} tolower('${escapeODataString(value)}')`;
+ }
+ } else {
+ return `${field} ${op} ${value}`;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/base/FilterBuilder/types.ts b/packages/base/FilterBuilder/types.ts
index 83ad5ec..925d920 100644
--- a/packages/base/FilterBuilder/types.ts
+++ b/packages/base/FilterBuilder/types.ts
@@ -6,8 +6,8 @@ import { ValueOption } from "../types";
export type ExternalBuilderProps = {
searchMenuItems?: ({ label: string, onClick: () => void })[],
- onSubmit?: (filter: string, serialised: SerialisedGroup | undefined, queryString: QueryStringCollection | undefined) => (void | any),
- onRestoreState?: (filter: string, serialised: SerialisedGroup | undefined, queryString: QueryStringCollection | undefined, state?: any) => void,
+ onSubmit?: (params: FilterParameters) => (void | any),
+ onRestoreState?: (params: FilterParameters, state?: any) => void,
localeText?: FilterBuilderLocaleText,
autocompleteGroups?: string[],
@@ -24,6 +24,14 @@ export type ExternalBuilderProps = {
filter?: SerialisedGroup
}
+export type FilterParameters = {
+ compute?: string,
+ filter: string,
+ queryString?: QueryStringCollection,
+ select?: string[],
+ serialised?: SerialisedGroup,
+}
+
export type FilterBuilderLocaleText = {
and?: string,
or?: string,
@@ -68,7 +76,7 @@ export type BaseFieldDef = {
filterOperators?: Operation[],
filterType?: string,
- getCustomFilterString?: (op: Operation, value: any) => string,
+ getCustomFilterString?: (op: Operation, value: any) => string | FilterCompute | boolean,
getCustomQueryString?: (op: Operation, value: any) => QueryStringCollection,
label?: string,
@@ -93,10 +101,26 @@ export type FieldDef = BaseFieldDef & {
export type CollectionFieldDef = BaseFieldDef;
+export type FilterCompute = {
+ filter: string,
+ compute: string | ComputeSelect
+}
+
+export type ComputeSelect = {
+ compute: string,
+ select: string[]
+}
+
export type QueryStringCollection = {
[key: string]: string
}
+export type FilterTranslatorCollection = {
+ [key in Operation | "default"]?: FilterTranslator
+}
+
+export type FilterTranslator = (schema: BaseFieldDef, field: string, op: Operation, value: any) => string | boolean;
+
export type Connective = "and" | "or"
export type Operation = "eq" | "ne" | "gt" | "lt" | "ge" | "le" | "contains" | "null" | "notnull"
diff --git a/packages/base/FilterBuilder/utils.ts b/packages/base/FilterBuilder/utils.ts
index 75e1731..51578e5 100644
--- a/packages/base/FilterBuilder/utils.ts
+++ b/packages/base/FilterBuilder/utils.ts
@@ -73,4 +73,6 @@ const groupObjToMap = (obj: SerialisedGroup, id: string, clauses?: StateClause):
});
return [{ id: id, children: children }, clauses]
-}
\ No newline at end of file
+}
+
+export const escapeODataString = (val: string) => val.replace("'", "''");
\ No newline at end of file
diff --git a/packages/base/components/ODataGridBase.tsx b/packages/base/components/ODataGridBase.tsx
index f44bbc0..d77434f 100644
--- a/packages/base/components/ODataGridBase.tsx
+++ b/packages/base/components/ODataGridBase.tsx
@@ -10,7 +10,7 @@ import { Expand, ODataResponse, ODataGridBaseProps, IGridSortModel, IGridProps,
import { ExpandToQuery, Flatten, GroupArrayBy, GetPageNumber, GetPageSizeOrDefault } from "../utils";
import { defaultPageSize } from "../constants";
-import { SerialisedGroup, QueryStringCollection } from "../FilterBuilder/types";
+import { SerialisedGroup, QueryStringCollection, FilterParameters } from "../FilterBuilder/types";
const ODataGridBase = (props.defaultSortModel);
const [filter, setFilter] = useState("");
+ const [filterSelects, setFilterSelects] = useState();
+ const [compute, setCompute] = useState();
const [queryString, setQueryString] = useState();
const [visibleColumns, setVisibleColumns] = useState(props.columns
@@ -65,6 +67,10 @@ const ODataGridBase = fields.add(c));
}
+ if (filterSelects) {
+ filterSelects.forEach((s) => fields.add(s));
+ }
+
// group all expands by the navigation field
const groupedExpands = GroupArrayBy(
props.columns
@@ -107,6 +113,10 @@ const ODataGridBase = 0) {
query.append("$orderby", sortModel.map(s => {
const sortCol = props.columns.find(c => c.field === s.field);
@@ -141,6 +151,8 @@ const ODataGridBase = {
+ const handleBuilderSubmit = useCallback((params: FilterParameters) => {
pendingFilter.current = true;
fetchCount.current = true;
if (props.filterBuilderProps?.onSubmit) {
- props.filterBuilderProps.onSubmit(f, s, q);
+ props.filterBuilderProps.onSubmit(params);
}
- setFilter(f);
- setQueryString(q);
+ setCompute(params.compute);
+ setFilter(params.filter);
+ setFilterSelects(params.select);
+ setQueryString(params.queryString);
setPageNumber(0);
return { oDataGrid: { sortModel: sortModel } };
}, [props.filterBuilderProps, sortModel]);
- const handleBuilderRestore = useCallback((f: string, s: SerialisedGroup | undefined, q: QueryStringCollection | undefined, state: any) => {
+ const handleBuilderRestore = useCallback((params: FilterParameters, state: any) => {
fetchCount.current = true;
if (props.filterBuilderProps?.onRestoreState) {
- props.filterBuilderProps.onRestoreState(f, s, q, state);
+ props.filterBuilderProps.onRestoreState(params, state);
}
if (props.disableHistory !== true) {
@@ -183,8 +197,10 @@ const ODataGridBase = {
diff --git a/packages/o-data-grid-pro/README.md b/packages/o-data-grid-pro/README.md
index 0d8bfdd..320beec 100644
--- a/packages/o-data-grid-pro/README.md
+++ b/packages/o-data-grid-pro/README.md
@@ -1,4 +1,4 @@
-# ODataGridPro
+# ODataGrid
ODataGrid is an extension to the [MUI DataGrid](https://github.com/mui-org/material-ui-x) React component which implements features such as sorting, pagination, column selection, and filtering using the [OData Standard](https://www.odata.org/). This allows you to quickly create a powerful interface for browsing data with minimal back-end code.

@@ -104,14 +104,17 @@ _* = not applicable to collection fields_
| `collectionFields` | `ODataGridColDef` | | Column definitions for the subfields of the collection. Any properties marked with * are not supported. |
| `datePickerProps` | [`DatePickerProps`](https://mui.com/api/date-picker/) | | Props to pass to the `DatePicker` component for columns with type `date` |
| `dateTimePickerProps` | [`DateTimePickerProps`](https://mui.com/api/date-time-picker/) | | Props to pass to the `DateTimePicker` component for columns with type `datetime` |
+| `expand` | `Expand` | | Include related entities using the `$expand` clause. |
| `filterable` | `boolean` | | Hides the field and does not allow filtering in the FilterBuilder when set to `false`. |
| `filterField` | `string` | | If the field name is different to the field which should be used for filtering, provide the field for filtering here. See also: `filterType`. |
+| `filterOnly` | `boolean` | `false` | Set to true if the field is for filtering only and cannot be displayed as a column in the datagrid. |
| `filterOperators` | `Operation[]` | `["eq", "ne", "gt", "lt", "ge", "le", "contains", "null", "notnull"]` | Array of supported filter operations for the field. |
| `filterType` | `string` | | If the type of the field to be filtered is different to that of the displayed field, provide the type here. See also: `filterField`. |
-| `getCustomFilterString` | `(op: Operation, value: any) => string` | | Function to generate a custom filter string for use in the `$filter` clause. |
-| `getCustomQueryString` | `(op: Operation, value: any) => ({ [key: string]: string })` | | Function to generate a custom set of query string values to add to the OData request. Allows custom filtering to be performed which is not supported by OData. |
+| `getCustomFilterString` | `(op: Operation, value: any) => string \| FilterCompute \| boolean` | | Function to generate a custom filter string for use in the `$filter` clause. Return `false` to skip and not add it to the `$filter` clause.
Also supports the use of the `$compute` clause by returning a `FilterCompute`. The computed property/properties can also be added to `$select` by returning a `ComputeSelect`. |
+| `getCustomQueryString` | `(op: Operation, value: any) => ({ [key: string]: string })` | | Function to generate a custom set of query string values to add to the OData request. |
| `label` | `string` | Defaults to the same value as `headerName` or `field` | Text to be displayed in the field selection dropdown. |
| `nullable` | `boolean` | | Adds an "Unknown" option to the value dropdown for columns with type `boolean` if set to `true`. |
+| `select` | `string` | | Additional fields to add to the `$select` clause. |
| `selectProps` | `{ selectProps?: SelectProps, formControlProps?: FormControlProps, label?: string }` | | Props to pass to the `Select`, `FormControl` and `Label` components for this column in the filter. See also: `textFieldProps`. |
| `sortField` | `string` | | If the name of the field to sort by is different to that of the displayed field, provide the name for sorting by here. |
| `textFieldProps` | [`TextFieldProps`](https://mui.com/api/text-field/) | | Props to pass to the `TextField` component in the filter for this column. See also: `selectProps`. |
@@ -129,8 +132,8 @@ _* = not applicable to collection fields_
| `filter` | `SerialisedGroup` | | Allows setting the state of the FilterBuilder using a `SerialisedGroup`. You could use this to implement filter saving and restoring.
Changing the value of this property will cause `restoreState` to be called, but with the `state` property undefined. |
| `localeText` | [`FilterBuilderLocaleText`](#FilterBuilderLocaleText) | | Localization strings for `FilterBuilder` (see [Localization](#localization) section) |
| `localizationProviderProps` | [`LocalizationProviderProps`](https://mui.com/components/date-picker/#localization) | | Props to pass to the `LocalizationProvider` component for the `DatePicker` and `DateTimePicker` components |
-| `onSubmit` | `(filter: string, serialised: SerialisedGroup \| undefined, queryString: QueryStringCollection \| undefined) => (void \| any)` | | Function called when FilterBuilder is submitted (e.g. when the search button is clicked). You should use this to trigger the OData request.
`filter` is the OData filter string, `serialised` is a serialised form of the query which can be used to load the query back into the filter builder. |
-| `onRestoreState` | `(filter: string, serialised: SerialisedGroup \| undefined, queryString: QueryStringCollection \| undefined, state?: any) => void` | | Function called when the state of the FilterBuilder is restored (e.g. from history navigation). You should also use this to trigger the OData request alongside the `onSubmit` callback.
`state` is the the value of `history.state` that the query was restored from. `state` will be undefined if the call is as a result of the `filter` property changing. |
+| `onSubmit` | `(params: FilterParameters) => (void \| any)` | | Function called when FilterBuilder is submitted (e.g. when the search button is clicked). You should use this to trigger the OData request.
`params.filter` is the OData filter string, `params.serialised` is a serialised form of the query which can be used to load the query back into the filter builder. |
+| `onRestoreState` | `(params: FilterParameters, state?: any) => void` | | Function called when the state of the FilterBuilder is restored (e.g. from history navigation). You should also use this to trigger the OData request alongside the `onSubmit` callback.
`state` is the the value of `history.state` that the query was restored from. `state` will be undefined if the call is as a result of the `filter` property changing. |
| `searchMenuItems` | `({ label: string, onClick: () => void })[]` | | Array of entries to add to the dropdown menu next to the Search button of the `FilterBuilder` |
## Localization
diff --git a/packages/o-data-grid-pro/dev/App.tsx b/packages/o-data-grid-pro/dev/App.tsx
index 3d558a5..93bcd3e 100644
--- a/packages/o-data-grid-pro/dev/App.tsx
+++ b/packages/o-data-grid-pro/dev/App.tsx
@@ -2,7 +2,7 @@ import React from "react"
import { CssBaseline, Typography, Grid, TextField, Slider, Chip } from "@mui/material";
import { createTheme, ThemeProvider } from "@mui/material/styles";
import { GridSortModel } from "@mui/x-data-grid"
-import { ODataGridColDef, QueryStringCollection, ODataColumnVisibilityModel } from "../src/index";
+import { ODataGridColDef, ODataColumnVisibilityModel, escapeODataString } from "../src/index";
import ODataGridPro from "../src/ODataGridPro";
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";
@@ -30,7 +30,6 @@ const App = () => {
getRowId={(row) => row.Id}
defaultSortModel={defaultSort}
filterBuilderProps={filterBuilderProps}
- defaultPageSize={15}
alwaysSelect={alwaysFetch}
/>
@@ -85,16 +84,17 @@ const columns: ODataGridColDef[] = [
),
- getCustomQueryString: (_, v) => {
+ getCustomFilterString: (_, v) => {
const filter = v as LocationFilter;
- const result: QueryStringCollection = {};
- if (filter.location) {
- result["location"] = filter.location!;
- result["distance"] = (filter.distance ?? 15).toString();
- }
-
- return result;
+ return {
+ filter: `Latitude ne null and Longitude ne null and Distance le ${filter.distance ?? 15}`,
+ compute: {
+ compute: `geocode('${escapeODataString(filter.location ?? "")}', Latitude, Longitude) as Distance`,
+ select: ["Distance"]
+ }
+ };
},
+ valueGetter: (params) => `${params.row.Location}${params.row.Distance ? ` (${params.row.Distance.toFixed(1)} mi away)` : ""}`,
autocompleteGroup: "Job"
},
{
diff --git a/packages/o-data-grid-pro/package.json b/packages/o-data-grid-pro/package.json
index d7e9f80..8f366a6 100644
--- a/packages/o-data-grid-pro/package.json
+++ b/packages/o-data-grid-pro/package.json
@@ -1,6 +1,6 @@
{
"name": "o-data-grid-pro",
- "version": "1.0.1",
+ "version": "1.1.0",
"description": "A React Data Grid and Query Builder for OData APIs. Based on the Material-UI DataGridPro.",
"main": "build/o-data-grid-pro-cjs.js",
"module": "build/o-data-grid-pro-esm.js",
@@ -46,8 +46,8 @@
},
"dependencies": {
"immutable": "^4.0.0",
- "recoil": "^0.5.2",
- "tss-react": "^3.2.4",
+ "recoil": "0.7.2",
+ "tss-react": "3.6.2",
"uuid": "^8.3.2"
}
}
diff --git a/packages/o-data-grid-pro/pnpm-lock.yaml b/packages/o-data-grid-pro/pnpm-lock.yaml
index ea37d00..1f47847 100644
--- a/packages/o-data-grid-pro/pnpm-lock.yaml
+++ b/packages/o-data-grid-pro/pnpm-lock.yaml
@@ -2,18 +2,28 @@ lockfileVersion: 5.3
specifiers:
immutable: ^4.0.0
- recoil: ^0.5.2
- tss-react: ^3.2.4
+ recoil: 0.7.2
+ tss-react: 3.6.2
uuid: ^8.3.2
dependencies:
immutable: 4.0.0
- recoil: 0.5.2
- tss-react: 3.4.1
+ recoil: 0.7.2
+ tss-react: 3.6.2
uuid: 8.3.2
packages:
+ /@emotion/cache/11.7.1:
+ resolution: {integrity: sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==}
+ dependencies:
+ '@emotion/memoize': 0.7.5
+ '@emotion/sheet': 1.1.0
+ '@emotion/utils': 1.0.0
+ '@emotion/weak-memoize': 0.2.5
+ stylis: 4.0.13
+ dev: false
+
/@emotion/hash/0.8.0:
resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==}
dev: false
@@ -32,6 +42,10 @@ packages:
csstype: 3.0.10
dev: false
+ /@emotion/sheet/1.1.0:
+ resolution: {integrity: sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==}
+ dev: false
+
/@emotion/unitless/0.7.5:
resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==}
dev: false
@@ -40,6 +54,10 @@ packages:
resolution: {integrity: sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==}
dev: false
+ /@emotion/weak-memoize/0.2.5:
+ resolution: {integrity: sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==}
+ dev: false
+
/csstype/3.0.10:
resolution: {integrity: sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==}
dev: false
@@ -52,8 +70,8 @@ packages:
resolution: {integrity: sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==}
dev: false
- /recoil/0.5.2:
- resolution: {integrity: sha512-Edibzpu3dbUMLy6QRg73WL8dvMl9Xqhp+kU+f2sJtXxsaXvAlxU/GcnDE8HXPkprXrhHF2e6SZozptNvjNF5fw==}
+ /recoil/0.7.2:
+ resolution: {integrity: sha512-OT4pI7FOUHcIoRtjsL5Lqq+lFFzQfir4MIbUkqyJ3nqv3WfBP1pHepyurqTsK5gw+T+I2R8+uOD28yH+Lg5o4g==}
peerDependencies:
react: '>=16.13.1'
react-dom: '*'
@@ -67,11 +85,21 @@ packages:
hamt_plus: 1.0.2
dev: false
- /tss-react/3.4.1:
- resolution: {integrity: sha512-S/OA9+8wBatj5B7JoWaz3K07dVY7UjPDwong0IZwOtqLUjTgJuT4GTrs7hxurq/cvF6DzPLv9HI754Aw1p3lqA==}
+ /stylis/4.0.13:
+ resolution: {integrity: sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==}
+ dev: false
+
+ /tss-react/3.6.2:
+ resolution: {integrity: sha512-+ecQIqCNFVlVJVk3NiCWZY2+5DhVKMVUVxIbEwv9nYsTRQA/KxyKCU8g+KMj5ByqFv9LW76+TUYzbWck/IHkfA==}
peerDependencies:
'@emotion/react': ^11.4.1
+ '@emotion/server': ^11.4.0
+ react: ^16.8.0 || ^17.0.2 || ^18.0.0
+ peerDependenciesMeta:
+ '@emotion/server':
+ optional: true
dependencies:
+ '@emotion/cache': 11.7.1
'@emotion/serialize': 1.0.2
'@emotion/utils': 1.0.0
dev: false
diff --git a/packages/o-data-grid-pro/src/index.ts b/packages/o-data-grid-pro/src/index.ts
index 49604f1..ba49602 100644
--- a/packages/o-data-grid-pro/src/index.ts
+++ b/packages/o-data-grid-pro/src/index.ts
@@ -19,11 +19,16 @@ export type { SelectOption, ValueOption, ODataColumnVisibilityModel } from "../.
export type {
CollectionFieldDef,
CollectionOperation,
+ ComputeSelect,
Connective,
FilterBuilderLocaleText,
+ FilterCompute,
+ FilterParameters,
FieldDef,
QueryStringCollection,
SerialisedGroup,
SerialisedCondition,
} from "../../base/FilterBuilder/types";
-export type { FilterBuilderProps } from "../../base/FilterBuilder/components/FilterBuilder";
\ No newline at end of file
+export type { FilterBuilderProps } from "../../base/FilterBuilder/components/FilterBuilder";
+
+export { escapeODataString } from "../../base/FilterBuilder/utils"
\ No newline at end of file
diff --git a/packages/o-data-grid/README.md b/packages/o-data-grid/README.md
index 8af74ed..320beec 100644
--- a/packages/o-data-grid/README.md
+++ b/packages/o-data-grid/README.md
@@ -104,14 +104,17 @@ _* = not applicable to collection fields_
| `collectionFields` | `ODataGridColDef` | | Column definitions for the subfields of the collection. Any properties marked with * are not supported. |
| `datePickerProps` | [`DatePickerProps`](https://mui.com/api/date-picker/) | | Props to pass to the `DatePicker` component for columns with type `date` |
| `dateTimePickerProps` | [`DateTimePickerProps`](https://mui.com/api/date-time-picker/) | | Props to pass to the `DateTimePicker` component for columns with type `datetime` |
+| `expand` | `Expand` | | Include related entities using the `$expand` clause. |
| `filterable` | `boolean` | | Hides the field and does not allow filtering in the FilterBuilder when set to `false`. |
| `filterField` | `string` | | If the field name is different to the field which should be used for filtering, provide the field for filtering here. See also: `filterType`. |
+| `filterOnly` | `boolean` | `false` | Set to true if the field is for filtering only and cannot be displayed as a column in the datagrid. |
| `filterOperators` | `Operation[]` | `["eq", "ne", "gt", "lt", "ge", "le", "contains", "null", "notnull"]` | Array of supported filter operations for the field. |
| `filterType` | `string` | | If the type of the field to be filtered is different to that of the displayed field, provide the type here. See also: `filterField`. |
-| `getCustomFilterString` | `(op: Operation, value: any) => string` | | Function to generate a custom filter string for use in the `$filter` clause. |
-| `getCustomQueryString` | `(op: Operation, value: any) => ({ [key: string]: string })` | | Function to generate a custom set of query string values to add to the OData request. Allows custom filtering to be performed which is not supported by OData. |
+| `getCustomFilterString` | `(op: Operation, value: any) => string \| FilterCompute \| boolean` | | Function to generate a custom filter string for use in the `$filter` clause. Return `false` to skip and not add it to the `$filter` clause.
Also supports the use of the `$compute` clause by returning a `FilterCompute`. The computed property/properties can also be added to `$select` by returning a `ComputeSelect`. |
+| `getCustomQueryString` | `(op: Operation, value: any) => ({ [key: string]: string })` | | Function to generate a custom set of query string values to add to the OData request. |
| `label` | `string` | Defaults to the same value as `headerName` or `field` | Text to be displayed in the field selection dropdown. |
| `nullable` | `boolean` | | Adds an "Unknown" option to the value dropdown for columns with type `boolean` if set to `true`. |
+| `select` | `string` | | Additional fields to add to the `$select` clause. |
| `selectProps` | `{ selectProps?: SelectProps, formControlProps?: FormControlProps, label?: string }` | | Props to pass to the `Select`, `FormControl` and `Label` components for this column in the filter. See also: `textFieldProps`. |
| `sortField` | `string` | | If the name of the field to sort by is different to that of the displayed field, provide the name for sorting by here. |
| `textFieldProps` | [`TextFieldProps`](https://mui.com/api/text-field/) | | Props to pass to the `TextField` component in the filter for this column. See also: `selectProps`. |
@@ -129,8 +132,8 @@ _* = not applicable to collection fields_
| `filter` | `SerialisedGroup` | | Allows setting the state of the FilterBuilder using a `SerialisedGroup`. You could use this to implement filter saving and restoring.
Changing the value of this property will cause `restoreState` to be called, but with the `state` property undefined. |
| `localeText` | [`FilterBuilderLocaleText`](#FilterBuilderLocaleText) | | Localization strings for `FilterBuilder` (see [Localization](#localization) section) |
| `localizationProviderProps` | [`LocalizationProviderProps`](https://mui.com/components/date-picker/#localization) | | Props to pass to the `LocalizationProvider` component for the `DatePicker` and `DateTimePicker` components |
-| `onSubmit` | `(filter: string, serialised: SerialisedGroup \| undefined, queryString: QueryStringCollection \| undefined) => (void \| any)` | | Function called when FilterBuilder is submitted (e.g. when the search button is clicked). You should use this to trigger the OData request.
`filter` is the OData filter string, `serialised` is a serialised form of the query which can be used to load the query back into the filter builder. |
-| `onRestoreState` | `(filter: string, serialised: SerialisedGroup \| undefined, queryString: QueryStringCollection \| undefined, state?: any) => void` | | Function called when the state of the FilterBuilder is restored (e.g. from history navigation). You should also use this to trigger the OData request alongside the `onSubmit` callback.
`state` is the the value of `history.state` that the query was restored from. `state` will be undefined if the call is as a result of the `filter` property changing. |
+| `onSubmit` | `(params: FilterParameters) => (void \| any)` | | Function called when FilterBuilder is submitted (e.g. when the search button is clicked). You should use this to trigger the OData request.
`params.filter` is the OData filter string, `params.serialised` is a serialised form of the query which can be used to load the query back into the filter builder. |
+| `onRestoreState` | `(params: FilterParameters, state?: any) => void` | | Function called when the state of the FilterBuilder is restored (e.g. from history navigation). You should also use this to trigger the OData request alongside the `onSubmit` callback.
`state` is the the value of `history.state` that the query was restored from. `state` will be undefined if the call is as a result of the `filter` property changing. |
| `searchMenuItems` | `({ label: string, onClick: () => void })[]` | | Array of entries to add to the dropdown menu next to the Search button of the `FilterBuilder` |
## Localization
diff --git a/packages/o-data-grid/dev/App.tsx b/packages/o-data-grid/dev/App.tsx
index a7f0f03..3a2844e 100644
--- a/packages/o-data-grid/dev/App.tsx
+++ b/packages/o-data-grid/dev/App.tsx
@@ -2,7 +2,7 @@ import React from "react"
import { CssBaseline, Typography, Grid, TextField, Slider, Chip } from "@mui/material";
import { createTheme, ThemeProvider } from "@mui/material/styles";
import { GridSortModel } from "@mui/x-data-grid"
-import { ODataGridColDef, QueryStringCollection, ODataColumnVisibilityModel } from "../src/index";
+import { ODataGridColDef, ODataColumnVisibilityModel, escapeODataString } from "../src/index";
import ODataGrid from "../src/ODataGrid";
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";
@@ -30,7 +30,6 @@ const App = () => {
getRowId={(row) => row.Id}
defaultSortModel={defaultSort}
filterBuilderProps={filterBuilderProps}
- defaultPageSize={15}
alwaysSelect={alwaysFetch}
/>
@@ -85,16 +84,17 @@ const columns: ODataGridColDef[] = [
),
- getCustomQueryString: (_, v) => {
+ getCustomFilterString: (_, v) => {
const filter = v as LocationFilter;
- const result: QueryStringCollection = {};
- if (filter.location) {
- result["location"] = filter.location!;
- result["distance"] = (filter.distance ?? 15).toString();
- }
-
- return result;
+ return {
+ filter: `Latitude ne null and Longitude ne null and Distance le ${filter.distance ?? 15}`,
+ compute: {
+ compute: `geocode('${escapeODataString(filter.location ?? "")}', Latitude, Longitude) as Distance`,
+ select: ["Distance"]
+ }
+ };
},
+ valueGetter: (params) => `${params.row.Location}${params.row.Distance ? ` (${params.row.Distance.toFixed(1)} mi away)` : ""}`,
autocompleteGroup: "Job"
},
{
diff --git a/packages/o-data-grid/package.json b/packages/o-data-grid/package.json
index f314245..195e4d4 100644
--- a/packages/o-data-grid/package.json
+++ b/packages/o-data-grid/package.json
@@ -1,6 +1,6 @@
{
"name": "o-data-grid",
- "version": "1.0.1",
+ "version": "1.1.0",
"description": "A React Data Grid and Query Builder for OData APIs. Based on the Material-UI DataGrid.",
"main": "build/o-data-grid-cjs.js",
"module": "build/o-data-grid-esm.js",
@@ -46,8 +46,8 @@
},
"dependencies": {
"immutable": "^4.0.0",
- "recoil": "^0.5.2",
- "tss-react": "^3.2.4",
+ "recoil": "0.7.2",
+ "tss-react": "3.6.2",
"uuid": "^8.3.2"
}
}
diff --git a/packages/o-data-grid/pnpm-lock.yaml b/packages/o-data-grid/pnpm-lock.yaml
index ea37d00..1f47847 100644
--- a/packages/o-data-grid/pnpm-lock.yaml
+++ b/packages/o-data-grid/pnpm-lock.yaml
@@ -2,18 +2,28 @@ lockfileVersion: 5.3
specifiers:
immutable: ^4.0.0
- recoil: ^0.5.2
- tss-react: ^3.2.4
+ recoil: 0.7.2
+ tss-react: 3.6.2
uuid: ^8.3.2
dependencies:
immutable: 4.0.0
- recoil: 0.5.2
- tss-react: 3.4.1
+ recoil: 0.7.2
+ tss-react: 3.6.2
uuid: 8.3.2
packages:
+ /@emotion/cache/11.7.1:
+ resolution: {integrity: sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==}
+ dependencies:
+ '@emotion/memoize': 0.7.5
+ '@emotion/sheet': 1.1.0
+ '@emotion/utils': 1.0.0
+ '@emotion/weak-memoize': 0.2.5
+ stylis: 4.0.13
+ dev: false
+
/@emotion/hash/0.8.0:
resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==}
dev: false
@@ -32,6 +42,10 @@ packages:
csstype: 3.0.10
dev: false
+ /@emotion/sheet/1.1.0:
+ resolution: {integrity: sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==}
+ dev: false
+
/@emotion/unitless/0.7.5:
resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==}
dev: false
@@ -40,6 +54,10 @@ packages:
resolution: {integrity: sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==}
dev: false
+ /@emotion/weak-memoize/0.2.5:
+ resolution: {integrity: sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==}
+ dev: false
+
/csstype/3.0.10:
resolution: {integrity: sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==}
dev: false
@@ -52,8 +70,8 @@ packages:
resolution: {integrity: sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==}
dev: false
- /recoil/0.5.2:
- resolution: {integrity: sha512-Edibzpu3dbUMLy6QRg73WL8dvMl9Xqhp+kU+f2sJtXxsaXvAlxU/GcnDE8HXPkprXrhHF2e6SZozptNvjNF5fw==}
+ /recoil/0.7.2:
+ resolution: {integrity: sha512-OT4pI7FOUHcIoRtjsL5Lqq+lFFzQfir4MIbUkqyJ3nqv3WfBP1pHepyurqTsK5gw+T+I2R8+uOD28yH+Lg5o4g==}
peerDependencies:
react: '>=16.13.1'
react-dom: '*'
@@ -67,11 +85,21 @@ packages:
hamt_plus: 1.0.2
dev: false
- /tss-react/3.4.1:
- resolution: {integrity: sha512-S/OA9+8wBatj5B7JoWaz3K07dVY7UjPDwong0IZwOtqLUjTgJuT4GTrs7hxurq/cvF6DzPLv9HI754Aw1p3lqA==}
+ /stylis/4.0.13:
+ resolution: {integrity: sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==}
+ dev: false
+
+ /tss-react/3.6.2:
+ resolution: {integrity: sha512-+ecQIqCNFVlVJVk3NiCWZY2+5DhVKMVUVxIbEwv9nYsTRQA/KxyKCU8g+KMj5ByqFv9LW76+TUYzbWck/IHkfA==}
peerDependencies:
'@emotion/react': ^11.4.1
+ '@emotion/server': ^11.4.0
+ react: ^16.8.0 || ^17.0.2 || ^18.0.0
+ peerDependenciesMeta:
+ '@emotion/server':
+ optional: true
dependencies:
+ '@emotion/cache': 11.7.1
'@emotion/serialize': 1.0.2
'@emotion/utils': 1.0.0
dev: false
diff --git a/packages/o-data-grid/src/index.ts b/packages/o-data-grid/src/index.ts
index c2ad922..ccf6c8a 100644
--- a/packages/o-data-grid/src/index.ts
+++ b/packages/o-data-grid/src/index.ts
@@ -19,11 +19,16 @@ export type { SelectOption, ValueOption, ODataColumnVisibilityModel } from "../.
export type {
CollectionFieldDef,
CollectionOperation,
+ ComputeSelect,
Connective,
FilterBuilderLocaleText,
+ FilterCompute,
+ FilterParameters,
FieldDef,
QueryStringCollection,
SerialisedGroup,
SerialisedCondition,
} from "../../base/FilterBuilder/types";
-export type { FilterBuilderProps } from "../../base/FilterBuilder/components/FilterBuilder";
\ No newline at end of file
+export type { FilterBuilderProps } from "../../base/FilterBuilder/components/FilterBuilder";
+
+export { escapeODataString } from "../../base/FilterBuilder/utils";
\ No newline at end of file