Skip to content

Commit

Permalink
fix(admin): Improve import projects from prod
Browse files Browse the repository at this point in the history
GitOrigin-RevId: 73c3fd0eea6899150f16b1d1a8d6ba3c1830ead4
  • Loading branch information
FMota0 authored and actions-user committed Dec 10, 2024
1 parent bfc5625 commit c1f4396
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 125 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { NonAuthCtx, useNonAuthCtx } from "@/wab/client/app-ctx";
import { Modal } from "@/wab/client/components/widgets/Modal";
import { WorkspaceId } from "@/wab/shared/ApiSchema";
import { Bundle } from "@/wab/shared/bundles";
import { DevFlagsType } from "@/wab/shared/devflags";
import { Button } from "antd";
import React, { useState } from "react";

type ProjectInfo = {
projectId: string;
bundle: string;
name: string;
};

function useImportFromProdListener(
onListen: (devflags: DevFlagsType, info: ProjectInfo[]) => Promise<void>
) {
React.useEffect(() => {
const listener = async (event: MessageEvent) => {
try {
const data = JSON.parse(event.data);
if (data.source === "import-project-from-prod") {
const { devflags, projectsInfo } = data;
await onListen(devflags, projectsInfo);
}
} catch {}
};
window.addEventListener("message", listener);
return () => window.removeEventListener("message", listener);
}, [onListen]);
}

async function setupHostlessWorkspace(
nonAuthCtx: NonAuthCtx,
hostLessWorkspaceId: WorkspaceId
) {
try {
// If it doesn't throw an error, it means the workspace exists
await nonAuthCtx.api.getWorkspace(hostLessWorkspaceId);
} catch (e) {
async function getTeamId() {
const { teams } = await nonAuthCtx.api.listTeams();
const nonPersonalTeam = teams.find((t) => !t.personalTeamOwnerId);
if (nonPersonalTeam) {
return nonPersonalTeam.id;
}
const { team } = await nonAuthCtx.api.createTeam("Hostless Team");
return team.id;
}

const teamId = await getTeamId();

await nonAuthCtx.api.adminCreateWorkspace({
id: hostLessWorkspaceId,
name: "Hostless Workspace",
description: "Workspace for hostless projects",
teamId: teamId,
});
}
}

async function resetProjects(
nonAuthCtx: NonAuthCtx,
projectsInfo: ProjectInfo[]
) {
console.log("## Deleting existing projects...");

const projectIds = projectsInfo.flatMap((info) => {
const parsedBundles = JSON.parse(info.bundle) as Array<[string, Bundle]>;
const dependenciesProjectIds = parsedBundles.flatMap(([_, bundle]) =>
Object.values(bundle.map)
.filter((inst) => inst.__type === "ProjectDependency")
.map((inst) => inst.projectId)
);

return [info.projectId, ...dependenciesProjectIds];
});

await Promise.all(
projectIds.map((projectId) =>
nonAuthCtx.api.deleteProjectAndRevisions(projectId)
)
);

console.log("## Uploading new projects...");
// We have to do it sync, since we can end up trying to insert the same project twice, concurrently and that will fail.
for (const bundle of projectsInfo) {
await nonAuthCtx.api.importProject(bundle.bundle, {
keepProjectIdsAndNames: true,
projectName: bundle.name,
});
}
}

export function AdminImportProjectsFromProd() {
const nonAuthCtx = useNonAuthCtx();
const [modalVisible, setModalVisible] = useState(false);
const ref = React.createRef<HTMLIFrameElement>();

const onListenProjectsInfo = React.useCallback(
async (devflags: DevFlagsType, info: ProjectInfo[]) => {
if (devflags.hostLessWorkspaceId) {
await setupHostlessWorkspace(nonAuthCtx, devflags.hostLessWorkspaceId);
}

devflags.hideBlankStarter = false;

await nonAuthCtx.api.setDevFlagOverrides(
JSON.stringify(devflags, null, 2)
);

await resetProjects(nonAuthCtx, info);

console.log("## Posting message to window...");
ref.current!.contentWindow?.postMessage(
JSON.stringify({
source: "import-project-from-prod",
done: true,
})
);

setModalVisible(false);
},
[nonAuthCtx, ref]
);

useImportFromProdListener(onListenProjectsInfo);

const showImportFromProd = window.location.hostname.includes("localhost");
// Don't even show this on prod, just for extra safety
if (!showImportFromProd) {
return null;
}

return (
<div>
<h2>Import devflags and plasmic projects from prod</h2>
<Modal
open={modalVisible}
footer={null}
title={"Import plasmic projects from prod"}
onCancel={() => setModalVisible(false)}
width={800}
>
<iframe
src="https://studio.plasmic.app/import-projects-from-prod"
width={760}
height={500}
ref={ref}
/>
</Modal>
<Button
disabled={window.location.hostname.startsWith(
"https://studio.plasmic.app"
)}
onClick={() => setModalVisible((v) => !v)}
>
Import
</Button>
<p>This will override your current devflags</p>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import {
AdminCtxProvider,
useAdminCtx,
} from "@/wab/client/components/pages/admin/AdminCtx";
import { AdminImportProjectsFromProd } from "@/wab/client/components/pages/admin/AdminImportProjectsFromProd";
import { AdminTeamsView } from "@/wab/client/components/pages/admin/AdminTeamsView";
import { AdminUsersView } from "@/wab/client/components/pages/admin/AdminUsersView";
import { ImportProjectsFromProd } from "@/wab/client/components/pages/admin/ImportProjectsFromProd";
import {
LinkButton,
SearchBox,
Expand Down Expand Up @@ -131,7 +131,7 @@ function AdminPageTabs() {
<TourCypressTest />
<DownloadPkgForPkgMgr />
<DownloadPlumePkg />
<ImportProjectsFromProd />
<AdminImportProjectsFromProd />
<CopilotFeedbackView />
<AppAuthMetrics />
</div>
Expand Down Expand Up @@ -1272,7 +1272,7 @@ function EditPkgVersionBundle() {
onClick={async () => {
const data = editorRef.current?.getValue();
if (data && JSON.parse(data)) {
const res = await nonAuthCtx.api.savePkgVersionAsAdmin({
await nonAuthCtx.api.savePkgVersionAsAdmin({
pkgVersionId: pkgVersion.id,
data,
});
Expand Down

This file was deleted.

5 changes: 5 additions & 0 deletions platform/wab/src/wab/server/AppServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,11 @@ export function addMainAppServerRoutes(
adminOnly,
withNext(adminRoutes.listProjects)
);
app.post(
"/api/v1/admin/workspaces",
adminOnly,
withNext(adminRoutes.createWorkspace)
);
app.post(
"/api/v1/admin/delete-project",
adminOnly,
Expand Down
53 changes: 42 additions & 11 deletions platform/wab/src/wab/server/db/DbMgr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2314,6 +2314,29 @@ export class DbMgr implements MigrationDbMgr {
return workspace;
}

async createWorkspaceWithId({
id,
name,
description,
teamId,
}: {
id: WorkspaceId;
name: string;
description: string;
teamId: TeamId;
}): Promise<Workspace> {
this.checkSuperUser();
const workspace = this.workspaces().create({
...this.stampNew(true),
id,
name,
description,
team: { id: teamId },
});
await this.entMgr.save(workspace);
return workspace;
}

async deleteWorkspace(id: WorkspaceId) {
return this._deleteResource({ type: "workspace", id });
}
Expand Down Expand Up @@ -2591,17 +2614,10 @@ export class DbMgr implements MigrationDbMgr {
...(ownerId ? { createdBy: { id: ownerId } } : {}),
...(projectId ? { id: projectId } : {}),
});
const rev = this.entMgr.create(ProjectRevision, {
...this.stampNew(),
revision: 1,
project: { id: project.id },
// This is a placeholder. The first client to load this placeholder will save back a valid initial
// project.
//
// Longer-term, the project code will actually be running on the server.
data: "{}",
});
await this.entMgr.save([project, rev]);

await this.entMgr.save(project);
const rev = await this.createFirstProjectRev(project.id);

if (project.createdById) {
await this.sudo()._assignResourceOwner(
{ type: "project", id: project.id },
Expand Down Expand Up @@ -3288,6 +3304,21 @@ export class DbMgr implements MigrationDbMgr {
return await this.projectRevs().save(rev);
}

async createFirstProjectRev(projectId: ProjectId) {
const revision = this.entMgr.create(ProjectRevision, {
...this.stampNew(),
revision: 1,
project: { id: projectId },
// This is a placeholder. The first client to load this placeholder will save back a valid initial
// project.
data: "{}",
});

await this.entMgr.save(revision);

return revision;
}

async getPreviousPkgId(
projectId: ProjectId,
branchId: BranchId | undefined,
Expand Down
Loading

0 comments on commit c1f4396

Please sign in to comment.