Skip to content

Commit

Permalink
Allow creating a pub without a stage (#676)
Browse files Browse the repository at this point in the history
* Create a pub without a stage

* Add tests

* Reorder tests

* Fix pub move tests

* Use `createPubRecursiveNew` for creating pubs

* Combine createpub actions

* Handle the case of no values in the pub

* Add test for creating a pub without values
  • Loading branch information
allisonking authored Oct 2, 2024
1 parent 96d4bb6 commit b9914b2
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 88 deletions.
26 changes: 17 additions & 9 deletions core/app/components/pubs/PubEditor/PubEditorClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,14 @@ type InferFormValues<T> = T extends UseFormReturn<infer V> ? V : never;
export function PubEditorClient(props: Props) {
const hasValues = Object.keys(props.pubValues).length > 0;
const paramString = hasValues ? "update" : "create";
const runCreatePub = useServerAction(actions.createPub);
const runCreatePub = useServerAction(actions.createPubRecursive);
const runUpdatePub = useServerAction(actions.updatePub);
const availableStages = [
{ id: undefined, name: "No stage" },
...props.availableStages.map((s) => {
return { id: s.id, name: s.name };
}),
];

const path = usePathname();
const router = useRouter();
Expand Down Expand Up @@ -171,12 +177,14 @@ export function PubEditorClient(props: Props) {
}

const result = await runCreatePub({
pubId,
body: {
id: pubId,
pubTypeId: pubTypeId as PubTypesId,
stageId: stageId as StagesId,
values: enabledPubValues as Record<string, any>,
},
communityId: props.communityId,
parentId: props.parentId,
pubTypeId: pubTypeId as PubTypesId,
pubValues: enabledPubValues,
stageId: stageId as StagesId,
parent: props.parentId ? { id: props.parentId } : undefined,
});
if (didSucceed(result)) {
toast({
Expand Down Expand Up @@ -251,15 +259,15 @@ export function PubEditorClient(props: Props) {
data-testid="stage-selector"
>
{field.value
? props.availableStages.find(
? availableStages.find(
(stage) => stage.id === field.value
)?.name
: "Select Stage"}
: "No stage"}
<ChevronDown size="16" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
{props.availableStages.map((stage) => (
{availableStages.map((stage) => (
<DropdownMenuItem
key={stage.id}
onClick={() => {
Expand Down
71 changes: 6 additions & 65 deletions core/app/components/pubs/PubEditor/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,85 +10,26 @@ import { validatePubValuesBySchemaName } from "~/actions/_lib/validateFields";
import { db } from "~/kysely/database";
import { getLoginData } from "~/lib/auth/loginData";
import { isCommunityAdmin } from "~/lib/auth/roles";
import { createPubRecursiveNew } from "~/lib/server";
import { autoCache } from "~/lib/server/cache/autoCache";
import { autoRevalidate } from "~/lib/server/cache/autoRevalidate";
import { defineServerAction } from "~/lib/server/defineServerAction";

export const createPub = defineServerAction(async function createPub({
communityId,
stageId,
pubTypeId,
pubValues,
path,
parentId,
pubId,
}: {
communityId: CommunitiesId;
stageId: StagesId;
pubTypeId: PubTypesId;
pubValues: PubValues;
path?: string | null;
parentId?: PubsId;
pubId?: PubsId;
}) {
export const createPubRecursive = defineServerAction(async function createPubRecursive(
...[props]: Parameters<typeof createPubRecursiveNew>
) {
const { user } = await getLoginData();
if (!user) {
throw new Error("Not logged in");
}

if (!isCommunityAdmin(user, { id: communityId })) {
if (!isCommunityAdmin(user, { id: props.communityId })) {
return {
error: "You need to be an admin",
};
}

const pubValueEntries = Object.entries(pubValues);

try {
const query = db
.with("new_pub", (db) =>
db
.insertInto("pubs")
.values({
id: pubId,
communityId: communityId,
pubTypeId: pubTypeId,
parentId: parentId,
})
.returning("id")
)
.with("stage_create", (db) =>
db.insertInto("PubsInStages").values((eb) => ({
pubId: eb.selectFrom("new_pub").select("new_pub.id"),
stageId,
}))
);

await autoRevalidate(
// Running an `insertInto` with an empty array results in a SQL syntax
// error. I was not able to find a way to generate the insert into
// statement only if the array has on or more values. So I extracted the
// CTEs into a partial query builder above, then conditionally attach the
// insertInto if there are values, otherwise perform a null select to
// produce valid SQL.
pubValueEntries.length > 0
? query.insertInto("pub_values").values((eb) =>
pubValueEntries.map(([pubFieldSlug, pubValue]) => ({
pubId: eb.selectFrom("new_pub").select("new_pub.id"),
value: JSON.stringify(pubValue),
fieldId: eb
.selectFrom("pub_fields")
.where("pub_fields.slug", "=", pubFieldSlug)
.select("pub_fields.id"),
}))
)
: query.selectFrom("new_pub").select([])
).execute();

if (path) {
revalidatePath(path);
}

await createPubRecursiveNew(props);
return {
success: true,
report: `Successfully created a new Pub`,
Expand Down
2 changes: 1 addition & 1 deletion core/app/components/pubs/PubEditor/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const createPubEditorSchemaFromPubFields = (

return Type.Object<{ pubTypeId: TString; stageId: TString }>({
pubTypeId: Type.String({ format: "uuid" }),
stageId: Type.String({ format: "uuid" }),
stageId: Type.Optional(Type.String({ format: "uuid" })),
...pubFieldSchemasBySlug,
});
};
28 changes: 15 additions & 13 deletions core/lib/server/pub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,19 +465,21 @@ export const createPubRecursiveNew = async <Body extends CreatePubRequestBodyWit
).executeTakeFirstOrThrow();
}

const pubValues = await autoRevalidate(
trx
.insertInto("pub_values")
.values(
valuesWithFieldIds.map(({ id, value }, index) => ({
// not sure this is the best way to do this
fieldId: id,
pubId: newPub.id,
value: value,
}))
)
.returningAll()
).execute();
const pubValues = valuesWithFieldIds.length
? await autoRevalidate(
trx
.insertInto("pub_values")
.values(
valuesWithFieldIds.map(({ id, value }, index) => ({
// not sure this is the best way to do this
fieldId: id,
pubId: newPub.id,
value: value,
}))
)
.returningAll()
).execute()
: [];

if (!body.children) {
return {
Expand Down
46 changes: 46 additions & 0 deletions core/playwright/pub.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,49 @@ test.describe("Moving a pub", () => {
await expect(page.getByRole("button", { name: "Move" })).toHaveCount(0);
});
});

test.describe("Creating a pub", () => {
test("Can create a pub without a stage", async () => {
const pubsPage = new PubsPage(page, COMMUNITY_SLUG);
const title = "Pub without a stage";
await pubsPage.goTo();
await page.getByRole("button", { name: "Create" }).click();
await page.getByLabel("Title").fill(title);
await page.getByLabel("Content").fill("Some content");
await page.getByRole("button", { name: "Create Pub" }).click();
await page.getByRole("link", { name: title }).click();
await page.waitForURL(/.*\/c\/.+\/pubs\/.+/);
await expect(page.getByTestId("current-stage")).toHaveCount(0);
});

test("Can create a pub with a stage", async () => {
const pubsPage = new PubsPage(page, COMMUNITY_SLUG);
const title = "Pub with a stage";
const stage = "Submitted";
await pubsPage.goTo();
await page.getByRole("button", { name: "Create" }).click();
await page.getByLabel("Title").fill(title);
await page.getByLabel("Content").fill("Some content");
await page.getByRole("button", { name: "No stage" }).click();
await page.getByRole("menuitem", { name: stage }).click();
await page.getByRole("button", { name: "Create Pub" }).click();
await page.getByRole("link", { name: title }).click();
await page.waitForURL(/.*\/c\/.+\/pubs\/.+/);
await expect(page.getByTestId("current-stage")).toHaveText(stage);
});

test("Can create a pub with no values", async () => {
const pubsPage = new PubsPage(page, COMMUNITY_SLUG);
await pubsPage.goTo();
await page.getByRole("button", { name: "Create" }).click();
await page.getByLabel("Title").fill("asdf");
const toggles = await page.getByLabel("Toggle field").all();
for (const toggle of toggles) {
await toggle.click();
}
await page.getByRole("button", { name: "Create Pub" }).click();
await expect(page.getByRole("status").filter({ hasText: "New pub created" })).toHaveCount(
1
);
});
});

0 comments on commit b9914b2

Please sign in to comment.