Skip to content

Commit

Permalink
feat: add depth selection for actions (#470)
Browse files Browse the repository at this point in the history
  • Loading branch information
foyarash authored Oct 7, 2024
1 parent 9eed548 commit 56ea03b
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/dry-tips-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@premieroctet/next-admin": patch
---

feat: add depth selection for actions (#443)
6 changes: 6 additions & 0 deletions apps/docs/pages/docs/api/model-configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,12 @@ The `actions` property is an array of objects that allows you to define a set of
description:
"a message that will be displayed when the action fails if action doesn't return a message object or throw an error with a message",
},
{
name: "depth",
type: "Number",
description:
"a number that defines the depth of the relations to select in the resource. Use this with caution, a number too high can potentially cause slower queries. Defaults to 2.",
},
]}
/>

Expand Down
1 change: 1 addition & 0 deletions apps/example/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ export const options: NextAdminOptions = {
id: "user-details",
title: "actions.user.details.title",
component: <UserDetailsDialog />,
depth: 3,
},
],
},
Expand Down
16 changes: 15 additions & 1 deletion packages/next-admin/src/appHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,25 @@ export const createHandler = <P extends string = "nextadmin">({
?.split(",")
.map((id) => formatId(resource, id));

const depth = req.nextUrl.searchParams.get("depth");

if (!ids) {
return NextResponse.json({ error: "No ids provided" }, { status: 400 });
}

const data = await getRawData({ prisma, resource, resourceIds: ids });
if (depth && isNaN(Number(depth))) {
return NextResponse.json(
{ error: "Depth should be a number" },
{ status: 400 }
);
}

const data = await getRawData({
prisma,
resource,
resourceIds: ids,
maxDepth: depth ? Number(depth) : undefined,
});

return NextResponse.json(data);
})
Expand Down
13 changes: 10 additions & 3 deletions packages/next-admin/src/components/ClientActionDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,16 @@ const ClientActionDialog = <M extends ModelName>({

useEffect(() => {
setIsLoading(true);
fetch(
`${apiBasePath}/${slugify(resource)}/raw?ids=${resourceIds.join(",")}`
)
const params = new URLSearchParams();

params.set("ids", resourceIds.join(","));

if (action.depth) {
// Avoid negative depth
params.set("depth", Math.max(1, action.depth).toString());
}

fetch(`${apiBasePath}/${slugify(resource)}/raw?${params.toString()}`)
.then((res) => res.json())
.then(setData)
.finally(() => {
Expand Down
13 changes: 12 additions & 1 deletion packages/next-admin/src/pageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,18 @@ export const createHandler = <P extends string = "nextadmin">({
ids = ids?.split(",").map((id: string) => formatId(resource, id));
}

const data = await getRawData({ prisma, resource, resourceIds: ids });
const depth = req.query.depth;

if (depth && isNaN(Number(depth))) {
return res.status(400).json({ error: "Depth should be a number" });
}

const data = await getRawData({
prisma,
resource,
resourceIds: ids,
maxDepth: depth ? Number(depth) : undefined,
});

return res.json(data);
})
Expand Down
6 changes: 6 additions & 0 deletions packages/next-admin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,12 @@ export type BareModelAction<M extends ModelName> = {
canExecute?: (item: Model<M>) => boolean;
icon?: keyof typeof OutlineIcons;
style?: ActionStyle;
/**
* Max depth of the related records to select
*
* @default 2
*/
depth?: number;
};

export type ServerAction = {
Expand Down
44 changes: 35 additions & 9 deletions packages/next-admin/src/utils/prisma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,38 @@ export const getDataItem = async <M extends ModelName>({
return data;
};

type DeepIncludeRecord = Record<string, true | any>;

const includeDataByDepth = <M extends ModelName>(
model: Prisma.DMMF.Model,
currentDepth: number,
maxDepth: number
) => {
const include = model?.fields.reduce((acc, field) => {
if (field.kind === "object") {
/**
* We substract because, if the condition matches,
* we will have all the fields in the related model, which are
* counted in currentDepth + 1
*/
if (currentDepth < maxDepth - 1) {
acc[field.name] = {
include: includeDataByDepth(
getPrismaModelForResource(field.type as M)!,
currentDepth + 1,
maxDepth
),
};
} else {
acc[field.name] = true;
}
}
return acc;
}, {} as DeepIncludeRecord);

return include;
};

/**
* Get raw data from Prisma (2-deep nested relations)
* @param prisma
Expand All @@ -672,22 +704,16 @@ export const getRawData = async <M extends ModelName>({
prisma,
resource,
resourceIds,
maxDepth = 2,
}: {
prisma: PrismaClient;
resource: M;
resourceIds: Array<string | number>;
maxDepth?: number;
}): Promise<Model<M>[]> => {
const modelDMMF = getPrismaModelForResource(resource);

const include = modelDMMF?.fields.reduce(
(acc, field) => {
if (field.kind === "object") {
acc[field.name] = true;
}
return acc;
},
{} as Record<string, true>
);
const include = includeDataByDepth(modelDMMF!, 1, maxDepth);

// @ts-expect-error
const data = await prisma[resource].findMany({
Expand Down
4 changes: 4 additions & 0 deletions packages/next-admin/src/utils/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,14 @@ export const getEnableToExecuteActions = async <M extends ModelName>(
actions?: Omit<ModelAction<M>, "action">[]
): Promise<OutputModelAction | undefined> => {
if (actions?.some((action) => action.canExecute)) {
const maxDepth = Math.max(0, ...actions.map((action) => action.depth ?? 0));

const data: Model<typeof resource>[] = await getRawData<typeof resource>({
prisma,
resource,
resourceIds: ids,
// Apply the default value if its 0
maxDepth: maxDepth || undefined,
});

return actions?.reduce(
Expand Down

0 comments on commit 56ea03b

Please sign in to comment.