Skip to content

Commit

Permalink
Add deep access for relationship formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
cregourd committed Jan 31, 2024
1 parent fcc7c66 commit 6697dd2
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 75 deletions.
5 changes: 5 additions & 0 deletions .changeset/new-nails-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@premieroctet/next-admin": patch
---

Add deep access for relationship formatter
6 changes: 3 additions & 3 deletions packages/next-admin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export type PropertyPayload<M extends ModelName, P extends keyof ObjectField<M>>
Prisma.TypeMap["model"][M]["payload"]["objects"][P] extends infer T | null ? T : never |
Prisma.TypeMap["model"][M]["payload"]["objects"][P]

export type ModelFromProperty<M extends ModelName, P extends keyof ObjectField<M>> = PropertyPayload<M, P> extends Payload ? ModelFromPayload<PropertyPayload<M,P>> : never
export type ModelFromProperty<M extends ModelName, P extends keyof ObjectField<M>> = PropertyPayload<M, P> extends Payload ? ModelFromPayload<PropertyPayload<M, P>> : never

export type ModelWithoutRelationships<M extends ModelName> = Model<M, number>;

Expand All @@ -60,7 +60,7 @@ export type Field<P extends ModelName> = keyof Model<P>;

export type ListFieldsOptions<T extends ModelName> = {
[P in Field<T>]?: {
formatter?: (item: Model<T>[P], context?: NextAdminContext) => ReactNode;
formatter?: (item: P extends keyof ObjectField<T> ? ModelFromProperty<T, P> : Model<T>[P], context?: NextAdminContext) => ReactNode;
};
};

Expand Down Expand Up @@ -184,7 +184,7 @@ export type Order<M extends ModelName> = {
export type Select<M extends ModelName> = {
[P in Field<M>]?: boolean;
} & {
_count: {
_count?: {
select: {
[key in string]: boolean;
};
Expand Down
91 changes: 53 additions & 38 deletions packages/next-admin/src/utils/prisma.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Prisma, PrismaClient } from "@prisma/client";
import { ITEMS_PER_PAGE } from "../config";
import {
EditOptions,
Field,
ListOptions,
ModelName,
Expand All @@ -23,23 +24,23 @@ export const createWherePredicate = (
) => {
return search
? {
OR: fieldsFiltered
?.filter((field) => field.kind === "scalar")
.map((field) => {
if (field.type === "String") {
// @ts-ignore
const mode = Prisma?.QueryMode ? { mode: Prisma.QueryMode.insensitive } : {};
return {
[field.name]: { contains: search, ...mode },
};
}
if (field.type === "Int" && !isNaN(Number(search))) {
return { [field.name]: Number(search) };
}
return null;
})
.filter(Boolean),
}
OR: fieldsFiltered
?.filter((field) => field.kind === "scalar")
.map((field) => {
if (field.type === "String") {
// @ts-ignore
const mode = Prisma?.QueryMode ? { mode: Prisma.QueryMode.insensitive } : {};
return {
[field.name]: { contains: search, ...mode },
};
}
if (field.type === "Int" && !isNaN(Number(search))) {
return { [field.name]: Number(search) };
}
return null;
})
.filter(Boolean),
}
: {};
};

Expand Down Expand Up @@ -80,22 +81,7 @@ export const preparePrismaListRequest = <M extends ModelName>(
let fieldsFiltered = model?.fields;
const list = options?.model?.[resource]?.list as ListOptions<M>;
if (list) {
const listDisplayedKeys = list.display;
select = listDisplayedKeys?.reduce(
(acc, column) => {
const field = model?.fields.find(({ name }) => name === column);
if (field?.kind === "object" && field?.isList === true) {
if (!acc._count) acc._count = { select: {} };
acc._count.select = { ...acc._count.select, [column]: true };
} else {
// @ts-expect-error
acc[column] = true;
}
return acc;
},
{ [getModelIdProperty(resource)]: true } as Select<M>
);

select = selectPayloadForModel(resource, list, 'object');
fieldsFiltered =
model?.fields.filter(({ name }) =>
list.search?.includes(name as Field<M>)
Expand Down Expand Up @@ -157,11 +143,9 @@ export const getMappedDataList = async (
data.forEach((item, index) => {
Object.keys(item).forEach((key) => {
let itemValue;

const model = capitalize(key) as ModelName;
const idProperty = getModelIdProperty(model);
if (typeof item[key] === "object" && item[key] !== null) {
const model = capitalize(key) as ModelName;
const idProperty = getModelIdProperty(model);

switch (item[key].type) {
case "link":
itemValue = item[key].value.label;
Expand Down Expand Up @@ -196,10 +180,18 @@ export const getMappedDataList = async (
item[key].__nextadmin_formatted = listFields[
key as keyof typeof listFields
// @ts-expect-error
]?.formatter?.(itemValue ?? item[key], context);
]?.formatter?.(itemValue ?? item[key], context)
} else {
if (typeof item[key]?.__nextadmin_formatted === 'object') {
item[key].__nextadmin_formatted = item[key].__nextadmin_formatted[idProperty]
}
data[index][key] = item[key];
}

if (typeof item[key]?.value === 'object') {
item[key].value.label = item[key].value.label[idProperty]
}

});
});

Expand All @@ -209,3 +201,26 @@ export const getMappedDataList = async (
error,
};
};

export const selectPayloadForModel = <M extends ModelName>(resource: M, options?: EditOptions<M> | ListOptions<M>, level: 'scalar' | 'object' = 'scalar') => {
const model = getPrismaModelForResource(resource);
const idProperty = getModelIdProperty(resource);

const displayKeys = options?.display;

let selectedFields = model?.fields.reduce(
(acc, field) => {
if ((displayKeys && displayKeys.includes(field.name as Field<M>) || !displayKeys)) {
if (level === 'object' && field.kind === 'object') {
acc[field.name] = { select: selectPayloadForModel(field.type as ModelName, {}, 'scalar') };
} else {
acc[field.name] = true;
}
}
return acc;
},
{ [idProperty]: true } as any
);

return selectedFields;
}
28 changes: 6 additions & 22 deletions packages/next-admin/src/utils/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from "../types";
import { createBoundServerAction } from "./actions";
import { getCustomInputs } from "./options";
import { getMappedDataList } from "./prisma";
import { getMappedDataList, selectPayloadForModel } from "./prisma";
import {
fillRelationInSchema,
getModelIdProperty,
Expand All @@ -23,7 +23,7 @@ import {
getResources,
orderSchema,
transformData,
transformSchema,
transformSchema
} from "./server";

export type GetPropsFromParamsParams = {
Expand Down Expand Up @@ -176,21 +176,13 @@ export async function getPropsFromParams({
}
case Page.EDIT: {
const resourceId = getResourceIdFromParam(params[1], resource);
const model = getPrismaModelForResource(resource);
const idProperty = getModelIdProperty(resource);
let selectedFields = model?.fields.reduce(
(acc, field) => {
acc[field.name] = true;
return acc;
},
{ [idProperty]: true }
);

const dmmfSchema = getPrismaModelForResource(resource);
const edit = options?.model?.[resource]?.edit as EditOptions<
typeof resource
>;

let deepCopySchema = cloneDeep(schema);
deepCopySchema = transformSchema(deepCopySchema, resource, edit);
deepCopySchema = await fillRelationInSchema(
Expand All @@ -201,25 +193,17 @@ export async function getPropsFromParams({
options,
);
deepCopySchema = orderSchema(deepCopySchema, resource, options);

const customInputs = isAppDir
? getCustomInputs(resource, options)
: undefined;

if (resourceId !== undefined) {
const editDisplayedKeys = edit?.display;
const editSelect = editDisplayedKeys?.reduce(
(acc, column) => {
acc[column] = true;
return acc;
},
{ [idProperty]: true }
);
selectedFields = editSelect ?? selectedFields;
const select = selectPayloadForModel(resource, edit, "object");
// @ts-expect-error
let data = await prisma[resource].findUniqueOrThrow({
select,
where: { [idProperty]: resourceId },
select: selectedFields,
});
data = transformData(data, resource, edit, options);
return {
Expand Down
24 changes: 12 additions & 12 deletions packages/next-admin/src/utils/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
ScalarField,
Schema
} from "../types";
import { createWherePredicate } from "./prisma";
import { createWherePredicate, selectPayloadForModel } from "./prisma";
import { isNativeFunction, uncapitalize } from "./tools";

export const models = Prisma.dmmf.datamodel.models;
Expand Down Expand Up @@ -44,14 +44,14 @@ export const getResources = (
};

export const getToStringForRelations = <M extends ModelName>(modelName: M, fieldName: Field<M>, modelNameRelation: ModelName, options?: NextAdminOptions, relationToFields?: any[]) => {
const nonChekedToString =
const nonCheckedToString =
// @ts-expect-error
options?.model?.[modelName]?.edit?.fields?.[fieldName]?.optionFormatter
|| options?.model?.[modelNameRelation]?.toString;
const modelRelationIdField = getModelIdProperty(modelNameRelation);
const toStringForRelations =
(nonChekedToString && !isNativeFunction(nonChekedToString))
? nonChekedToString
(nonCheckedToString && !isNativeFunction(nonCheckedToString))
? nonCheckedToString
: (item: any) =>
item[relationToFields?.[0]] ?? item[modelRelationIdField];

Expand Down Expand Up @@ -163,9 +163,11 @@ export const fillRelationInSchema = async (
//Relation One-to-Many, Many side
const relationRemoteProperty = relationToFields![0];
let enumeration: Enumeration[] = [];
const select = selectPayloadForModel(modelNameRelation)
await prisma[uncapitalize(modelNameRelation)]
// @ts-expect-error
.findMany({
select,
where,
take: 20,
})
Expand Down Expand Up @@ -200,9 +202,11 @@ export const fillRelationInSchema = async (
]
if (fieldValue) {
let enumeration: Enumeration[] = [];
const select = selectPayloadForModel(modelNameRelation)
await prisma[uncapitalize(modelNameRelation)]
// @ts-expect-error
.findMany({
select,
where,
take: 20,
})
Expand Down Expand Up @@ -319,18 +323,15 @@ export const findRelationInData = async (
url: `${dmmfProperty.type as ModelName}/${item[dmmfPropertyName][idProperty]}`,
},
};
} else {
return item;
}
return item;
});
} else {
data.map((item) => {
if (item._count) {
Object.keys(item._count).forEach((key) => {
item[key] = { type: "count", value: item._count[key] };
});
delete item._count;
if (item[dmmfPropertyName]) {
item[dmmfPropertyName] = { type: "count", value: item[dmmfPropertyName].length };
}
return item;
});
}
}
Expand Down Expand Up @@ -452,7 +453,6 @@ export const formattedFormData = async <M extends ModelName>(
}
} else {
const dmmfPropertyName = dmmfProperty.name as keyof ScalarField<M>;
console.log(dmmfPropertyName, dmmfPropertyType)
if (dmmfPropertyType === "Int" || dmmfPropertyType === "Float" || dmmfPropertyType === "Decimal") {
formattedData[dmmfPropertyName] = !isNaN(
Number(formData[dmmfPropertyName])
Expand Down

0 comments on commit 6697dd2

Please sign in to comment.