diff --git a/packages/dataprovider/README.md b/packages/dataprovider/README.md
index 90ad204..52aa0db 100644
--- a/packages/dataprovider/README.md
+++ b/packages/dataprovider/README.md
@@ -126,11 +126,9 @@ const UserFilter = (props) => (
If you have relations, you can use `ReferenceArrayField/Input` or `Referenceinput/Field`. Make sure that the reference Model is also compatible (by calling `addCrudResolvers("MyReferenceModel")` from `@ra-data-prisma/backend` on your backend).
-### Sorting by relations
-
-`
`s can be sorted by relations. [Enable it in the backend](../backend#enable-sort-by-relation)
+Make sure to add the suffix `_id` or `_ids` (if its an array field) to the `source` property.
-#### some examples:
+#### Examples:
_show a list of cities with the country_
@@ -140,7 +138,11 @@ export const CityList = (props) => (
-
+
@@ -160,7 +162,7 @@ export const UserList = (props) => (
@@ -182,7 +184,7 @@ export const UserEdit = (props) => (
(
);
```
+### Sorting by relations
+
+`
`s can be sorted by relations. [Enable it in the backend](../backend#enable-sort-by-relation)
+
### Customize fetching & virtual Resources
react-admin has [no mechanism to tell the dataprovider which fields are requested for any resources](https://github.com/marmelab/react-admin/issues/4751),
@@ -342,7 +348,7 @@ buildGraphQLProvider({
fragment: {
many: {
type: "document",
- mode: "extend"
+ mode: "extend" // <---
doc: gql`
fragment OneUserWithTwitter on User {
userSocialMedia {
diff --git a/packages/dataprovider/src/buildQuery.ts b/packages/dataprovider/src/buildQuery.ts
index 3a9e080..7c7b3f0 100644
--- a/packages/dataprovider/src/buildQuery.ts
+++ b/packages/dataprovider/src/buildQuery.ts
@@ -5,7 +5,6 @@ import { IntrospectionResult } from "./constants/interfaces";
import getResponseParser from "./getResponseParser";
import {
FetchType,
- isDeprecatedDocumentNodeFragment,
isOneAndManyFragment,
OurOptions,
ResourceFragment,
@@ -81,13 +80,6 @@ export const buildQueryFactory = (
resourceViewFragment,
);
const parseResponse = getResponseParser(introspectionResults, {
- shouldSanitizeLinkedResources: !(
- // don't sanitze on real fragments
- (
- resourceViewFragment &&
- isDeprecatedDocumentNodeFragment(resourceViewFragment)
- )
- ),
queryDialect: options.queryDialect,
})(aorFetchType, resource);
diff --git a/packages/dataprovider/src/buildVariables/buildData.ts b/packages/dataprovider/src/buildVariables/buildData.ts
index 927ea68..f60765c 100644
--- a/packages/dataprovider/src/buildVariables/buildData.ts
+++ b/packages/dataprovider/src/buildVariables/buildData.ts
@@ -14,6 +14,7 @@ import isObject from "lodash/isObject";
import { IntrospectionResult } from "../constants/interfaces";
import exhaust from "../utils/exhaust";
import getFinalType from "../utils/getFinalType";
+import { sanitizeData } from "../utils/sanitizeData";
enum ModifiersParams {
connect = "connect",
@@ -145,7 +146,12 @@ const buildNewInputValue = (
(i) => i.name === ModifiersParams.delete,
);
- if (setModifier && !connectModifier && !disconnectModifier && !deleteModifier) {
+ if (
+ setModifier &&
+ !connectModifier &&
+ !disconnectModifier &&
+ !deleteModifier
+ ) {
// if its a date, convert it to a date
if (
setModifier.type.kind === "SCALAR" &&
@@ -378,14 +384,15 @@ export const buildData = (
if (!inputType) {
return {};
}
+ const data = sanitizeData(params.data);
+ const previousData =
+ "previousData" in params ? sanitizeData(params.previousData) : null;
return inputType.inputFields.reduce((acc, field) => {
const key = field.name;
const fieldType =
field.type.kind === "NON_NULL" ? field.type.ofType : field.type;
- const fieldData = params.data[key];
- //console.log(key, fieldData, fieldType);
- const previousFieldData =
- (params as UpdateParams)?.previousData?.[key] ?? null;
+ const fieldData = data[key];
+ const previousFieldData = previousData?.[key] ?? null;
// TODO in case the content of the array has changed but not the array itself?
if (
isEqual(fieldData, previousFieldData) ||
@@ -395,7 +402,7 @@ export const buildData = (
}
const newVaue = buildNewInputValue(
- params.data[key],
+ fieldData,
previousFieldData,
field.name,
fieldType,
diff --git a/packages/dataprovider/src/buildWhere.ts b/packages/dataprovider/src/buildWhere.ts
index 60c7a5d..0f86668 100644
--- a/packages/dataprovider/src/buildWhere.ts
+++ b/packages/dataprovider/src/buildWhere.ts
@@ -12,6 +12,7 @@ import {
Resource,
} from "./constants/interfaces";
import { OurOptions } from "./types";
+import { sanitizeKey } from "./utils/sanitizeData";
const getStringFilter = (
key: string,
@@ -491,11 +492,13 @@ const buildWhereWithType = (
const hasAnd = whereType.inputFields.some((i) => i.name === "AND");
const where = hasAnd
? Object.keys(filter ?? {}).reduce(
- (acc, key) => {
+ (acc, keyRaw) => {
+ const value = filter[keyRaw];
+ const key = sanitizeKey(keyRaw);
// defaults to AND
const filters = getFilters(
key,
- filter[key],
+ value,
whereType,
introspectionResults,
@@ -506,10 +509,12 @@ const buildWhereWithType = (
},
{ AND: [] },
)
- : Object.keys(filter ?? {}).reduce((acc, key) => {
+ : Object.keys(filter ?? {}).reduce((acc, keyRaw) => {
+ const value = filter[keyRaw];
+ const key = sanitizeKey(keyRaw);
const filters = getFilters(
key,
- filter[key],
+ value,
whereType,
introspectionResults,
diff --git a/packages/dataprovider/src/getResponseParser.test.ts b/packages/dataprovider/src/getResponseParser.test.ts
index a3abfd8..b3daabe 100644
--- a/packages/dataprovider/src/getResponseParser.test.ts
+++ b/packages/dataprovider/src/getResponseParser.test.ts
@@ -1,4 +1,3 @@
-import { TypeKind } from "graphql";
import {
GET_LIST,
GET_MANY,
@@ -31,20 +30,12 @@ const testListTypes = (type: FetchType) => {
{
id: "user1",
firstName: "firstName1",
- roles: [
- {
- id: "admin",
- },
- ],
+ roles: [{ id: "admin" }],
},
{
id: "post2",
firstName: "firstName2",
- roles: [
- {
- id: "admin",
- },
- ],
+ roles: [{ id: "admin" }],
},
],
total: 100,
@@ -61,12 +52,14 @@ const testListTypes = (type: FetchType) => {
{
id: "user1",
firstName: "firstName1",
- roles: ["admin"],
+ roles: [{ id: "admin" }],
+ roles_ids: ["admin"],
},
{
id: "post2",
firstName: "firstName2",
- roles: ["admin"],
+ roles: [{ id: "admin" }],
+ roles_ids: ["admin"],
},
],
total: 100,
@@ -86,20 +79,12 @@ const testListTypes = (type: FetchType) => {
{
id: "user1",
firstName: "firstName1",
- roles: [
- {
- id: "admin",
- },
- ],
+ roles: [{ id: "admin" }],
},
{
id: "post2",
firstName: "firstName2",
- roles: [
- {
- id: "admin",
- },
- ],
+ roles: [{ id: "admin" }],
},
],
total: { _count: { _all: 100 } },
@@ -116,12 +101,14 @@ const testListTypes = (type: FetchType) => {
{
id: "user1",
firstName: "firstName1",
- roles: ["admin"],
+ roles: [{ id: "admin" }],
+ roles_ids: ["admin"],
},
{
id: "post2",
firstName: "firstName2",
- roles: ["admin"],
+ roles: [{ id: "admin" }],
+ roles_ids: ["admin"],
},
],
total: 100,
@@ -141,11 +128,7 @@ const testSingleTypes = (type: FetchType) => {
data: {
id: "user1",
firstName: "firstName1",
- roles: [
- {
- id: "admin",
- },
- ],
+ roles: [{ id: "admin" }],
},
},
};
@@ -158,7 +141,8 @@ const testSingleTypes = (type: FetchType) => {
data: {
id: "user1",
firstName: "firstName1",
- roles: ["admin"],
+ roles: [{ id: "admin" }],
+ roles_ids: ["admin"],
},
});
});
diff --git a/packages/dataprovider/src/getResponseParser.ts b/packages/dataprovider/src/getResponseParser.ts
index 0fdfe72..68c5792 100644
--- a/packages/dataprovider/src/getResponseParser.ts
+++ b/packages/dataprovider/src/getResponseParser.ts
@@ -5,11 +5,7 @@ import { IntrospectionResult, Resource } from "./constants/interfaces";
import { FetchType, QueryDialect } from "./types";
const sanitizeResource =
- (
- introspectionResults: IntrospectionResult,
- resource: Resource,
- shouldSanitizeLinkedResources: boolean = true,
- ) =>
+ (introspectionResults: IntrospectionResult, resource: Resource) =>
(data: { [key: string]: any } = {}): any => {
return Object.keys(data).reduce((acc, key) => {
if (key.startsWith("_")) {
@@ -24,25 +20,23 @@ const sanitizeResource =
if (type.kind !== TypeKind.OBJECT) {
return { ...acc, [field.name]: data[field.name] };
}
-
- // FIXME: We might have to handle linked types which are not resources but will have to be careful about endless circular dependencies
- const linkedResource = introspectionResults.resources.find(
- (r) => r.type.name === type.name,
- );
-
- if (shouldSanitizeLinkedResources && linkedResource) {
- const linkedResourceData = data[field.name];
-
- if (Array.isArray(linkedResourceData)) {
- return {
- ...acc,
- [field.name]: data[field.name].map((obj) => obj.id),
- };
- }
-
+ // if the field contains an array of object with ids, we add a field field_ids to the data
+ if (
+ Array.isArray(data[field.name]) &&
+ data[field.name]?.every((c) => c.id)
+ ) {
+ return {
+ ...acc,
+ [field.name]: data[field.name],
+ [`${field.name}_ids`]: data[field.name].map((c) => c.id),
+ };
+ }
+ // similarly if its an object with id
+ if (data[field.name]?.id) {
return {
...acc,
- [field.name]: data[field.name]?.id,
+ [field.name]: data[field.name],
+ [`${field.name}_id`]: data[field.name].id,
};
}
@@ -52,18 +46,11 @@ const sanitizeResource =
export default (
introspectionResults: IntrospectionResult,
- {
- shouldSanitizeLinkedResources = true,
- queryDialect,
- }: { shouldSanitizeLinkedResources?: boolean; queryDialect: QueryDialect },
+ { queryDialect }: { queryDialect: QueryDialect },
) =>
(aorFetchType: FetchType, resource: Resource) =>
(response: { [key: string]: any }) => {
- const sanitize = sanitizeResource(
- introspectionResults,
- resource,
- shouldSanitizeLinkedResources,
- );
+ const sanitize = sanitizeResource(introspectionResults, resource);
const data = response.data;
const getTotal = () => {
diff --git a/packages/dataprovider/src/utils/sanitizeData.ts b/packages/dataprovider/src/utils/sanitizeData.ts
new file mode 100644
index 0000000..a02675c
--- /dev/null
+++ b/packages/dataprovider/src/utils/sanitizeData.ts
@@ -0,0 +1,25 @@
+export const sanitizeKey = (key: string) => {
+ if (key.endsWith("_ids")) {
+ return key.substring(0, key.lastIndexOf("_ids"));
+ }
+ if (key.endsWith("_id")) {
+ return key.substring(0, key.lastIndexOf("_id"));
+ }
+ return key;
+};
+/**
+ * Due to some implementation details in react-admin, we have to add copies with suffixed keys of certain field data.
+ * This function sanitizes these keys:
+ * - suffix _id: a string reference
+ * - suffix _ids: an array of ids referencing
+ * @param data data
+ * @returns data where the suffixes got removed and the original data is overwritten with the suffixed version
+ */
+export const sanitizeData = (data: { [key: string]: any }) => {
+ return Object.fromEntries(
+ Object.entries(data).reduce((acc, [keyRaw, value]) => {
+ const key = sanitizeKey(keyRaw);
+ return [...acc, [key, value]];
+ }, []),
+ );
+};
diff --git a/tsconfig-test.json b/tsconfig-test.json
index b4d9ee0..68075c8 100644
--- a/tsconfig-test.json
+++ b/tsconfig-test.json
@@ -3,8 +3,8 @@
"compilerOptions": {
"module": "commonjs",
- "target": "es2018",
- "lib": ["es2018", "esnext.asynciterable"],
+ "target": "es2019",
+ "lib": ["es2019", "esnext.asynciterable"],
"emitDecoratorMetadata": true,
"experimentalDecorators": true
}