Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New /admin/upgrade-versions UI; compareVersions function #3466

Merged
merged 4 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions apps/hub/src/app/admin/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ export default async function AdminSearchPage() {
<SafeActionButton
action={adminRebuildSearchIndexAction}
input={{}}
title="Rebuild"
confirmation="Index updated"
theme="primary"
/>
>
Rebuild
</SafeActionButton>
</div>
);
}
176 changes: 121 additions & 55 deletions apps/hub/src/app/admin/upgrade-versions/UpgradeVersionsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,143 @@
"use client";
import { useRouter } from "next/navigation";
import { FC, useState } from "react";
import clsx from "clsx";
import { FC, useEffect, useState } from "react";

import {
Button,
CheckIcon,
Dropdown,
DropdownMenu,
DropdownMenuActionItem,
RefreshIcon,
XIcon,
} from "@quri/ui";
import { defaultSquiggleVersion } from "@quri/versioned-squiggle-components";
import {
checkSquiggleVersion,
defaultSquiggleVersion,
} from "@quri/versioned-squiggle-components";
import { compareVersions } from "@quri/versioned-squiggle-components/compareVersions";

import { H2 } from "@/components/ui/Headers";
import { SafeActionButton } from "@/components/ui/SafeActionButton";
import { StyledLink } from "@/components/ui/StyledLink";
import { modelRoute } from "@/lib/routes";
import { adminUpdateModelVersionAction } from "@/models/actions/adminUpdateModelVersionAction";
import { ModelByVersion } from "@/models/data/byVersion";

import { UpgradeableModel } from "./UpgradeableModel";
const ComparedCode: FC<{
modelId: string;
version: string;
code: string;
}> = ({ modelId, version, code }) => {
const [status, setStatus] = useState<
"loading" | "success" | "error" | "upgraded"
>("loading");

useEffect(() => {
if (!checkSquiggleVersion(version)) {
setStatus("error");
return;
}

compareVersions({
version1: version,
version2: defaultSquiggleVersion,
code: code,
}).then((result) => {
if (result) {
setStatus("error");
} else {
setStatus("success");
}
});
}, [version, code]);

const commonClasses = "w-6 h-6";
switch (status) {
case "loading":
return (
<RefreshIcon
className={clsx(commonClasses, "animate-spin text-gray-400")}
/>
);
case "success":
return (
<div className="flex gap-1">
<CheckIcon className={clsx(commonClasses, "text-green-500")} />
<SafeActionButton
action={adminUpdateModelVersionAction}
input={{
modelId: modelId,
version: defaultSquiggleVersion,
}}
onSuccess={() => setStatus("upgraded")}
theme="primary"
size="small"
>
Upgrade to {defaultSquiggleVersion}
</SafeActionButton>
</div>
);
case "upgraded":
return <CheckIcon className={clsx(commonClasses, "text-green-500")} />;

case "error":
return <XIcon className={clsx(commonClasses, "text-red-500")} />;
}
};

const ComparedModel: FC<{
model: ModelByVersion["models"][number];
}> = ({ model }) => {
const squiggleSnippet = model.currentRevision?.squiggleSnippet;

if (!squiggleSnippet) {
return null;
}

const version = squiggleSnippet.version;

return (
<div className="col-span-2 grid grid-cols-subgrid gap-2">
<StyledLink
href={`/admin/upgrade-versions/compare?owner=${model.owner.slug}&slug=${model.slug}`}
>
{model.owner.slug}/{model.slug}
</StyledLink>
<ComparedCode
modelId={model.id}
version={version}
code={squiggleSnippet.code}
/>
</div>
);
};

const ModelList: FC<{
models: ModelByVersion["models"];
}> = ({ models }) => {
const router = useRouter();

const [pos, setPos] = useState(0);
const limitStep = 10;
const [limit, setLimit] = useState(limitStep);

if (!models.length) return null;
const usedPos = Math.min(pos, models.length - 1);
const model = models[usedPos];

return (
<div>
<div className="flex items-center gap-2">
<div>
Model:{" "}
<StyledLink
href={modelRoute({
owner: model.owner.slug,
slug: model.slug,
})}
>
{model.owner.slug}/{model.slug}
</StyledLink>
</div>
<SafeActionButton
action={adminUpdateModelVersionAction}
input={{
modelId: model.id,
version: defaultSquiggleVersion,
}}
onSuccess={() => router.refresh()}
title={`Upgrade to ${defaultSquiggleVersion}`}
theme="primary"
/>
<Button onClick={() => setPos(usedPos - 1)} disabled={usedPos <= 0}>
&larr; Prev
</Button>
<Button
onClick={() => setPos(usedPos + 1)}
disabled={usedPos >= models.length - 1}
>
Next &rarr;
</Button>
<p className="pb-4 text-xs">
{`In the list below, you'll see the green checkmark if the model's output is identical to the output of the new version. If not, you'll see a red X, but you can go to the model's "compare" page to see the difference and upgrade it manually.`}
</p>
<div
className="grid gap-2"
style={{ gridTemplateColumns: "repeat(2, max-content)" }}
>
{models.slice(0, limit).map((model) => (
<ComparedModel model={model} key={model.id} />
))}
</div>
<UpgradeableModel model={model} />
{models.length > limit ? (
<Button onClick={() => setLimit((limit) => limit + limitStep)}>
Show more
</Button>
) : null}
</div>
);
};
Expand Down Expand Up @@ -100,19 +173,8 @@ export const UpgradeVersionsPage: FC<{
getEntryByVersion(defaultSquiggleVersion)?.models.length ?? 0;

return (
<div>
<div className="container mx-auto">
<H2>Upgrade model versions</H2>
<div>
<p className="text-xs">
Check models with their current version and the new version, then
press the upgrade button if everything is ok.
</p>
<p className="text-xs">
<strong>
{`Code edits won't be saved, "Upgrade" button bumps only the model's version.`}
</strong>
</p>
</div>
<div className="mt-2 text-sm">
<div>Dev models: {devCount}</div>
<div>
Expand Down Expand Up @@ -143,7 +205,11 @@ export const UpgradeVersionsPage: FC<{
</Button>
</Dropdown>
</div>
{selectedEntry ? <ModelList models={selectedEntry.models} /> : null}
{selectedEntry ? (
<div className="mt-4">
<ModelList models={selectedEntry.models} />
</div>
) : null}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use client";
import { useRouter } from "next/navigation";
import { FC } from "react";

import { defaultSquiggleVersion } from "@quri/versioned-squiggle-components";

import { SafeActionButton } from "@/components/ui/SafeActionButton";
import { adminUpdateModelVersionAction } from "@/models/actions/adminUpdateModelVersionAction";
import { ModelFullDTO } from "@/models/data/full";

export const UpgradeButton: FC<{
model: ModelFullDTO;
}> = ({ model }) => {
const router = useRouter();

if (
model.currentRevision.contentType === "SquiggleSnippet" &&
model.currentRevision.squiggleSnippet.version === defaultSquiggleVersion
) {
return null;
}

return (
<SafeActionButton
action={adminUpdateModelVersionAction}
input={{
modelId: model.id,
version: defaultSquiggleVersion,
}}
onSuccess={() => router.refresh()}
theme="primary"
>
Upgrade to {defaultSquiggleVersion}
</SafeActionButton>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use client";
import { FC, use, useEffect, useState } from "react";
import Skeleton from "react-loading-skeleton";
import { FC, use } from "react";

import {
defaultSquiggleVersion,
Expand All @@ -10,12 +9,10 @@ import {
} from "@quri/versioned-squiggle-components";

import { EditSquiggleSnippetModel } from "@/app/models/[owner]/[slug]/EditSquiggleSnippetModel";
import { loadModelFullAction } from "@/models/actions/loadModelFullAction";
import { ModelByVersion } from "@/models/data/byVersion";
import { ModelFullDTO } from "@/models/data/full";
import { sqProjectWithHubLinker } from "@/squiggle/linker";

const InnerUpgradeableModel: FC<{
export const UpgradeableModel: FC<{
model: ModelFullDTO;
}> = ({ model }) => {
const currentRevision = model.currentRevision;
Expand All @@ -33,7 +30,7 @@ const InnerUpgradeableModel: FC<{
const project = sqProjectWithHubLinker(squiggle);
const updatedProject = sqProjectWithHubLinker(updatedSquiggle);

// TODO - starting from 0.9.5, we will be able to compare serialized outputs for the new and old verison.
// TODO - compare outputs with compareVersions

if (versionSupportsSquiggleChart.plain(version)) {
const headerClasses = "py-1 px-2 m-1 bg-slate-200 font-medium";
Expand All @@ -58,36 +55,3 @@ const InnerUpgradeableModel: FC<{
);
}
};

export const UpgradeableModel: FC<{
model: ModelByVersion["models"][number];
}> = ({ model: incompleteModel }) => {
const [model, setModel] = useState<ModelFullDTO | "loading" | null>(
"loading"
);

useEffect(() => {
// TODO - this is done with a server action, so it's not cached.
// A route would be better.
loadModelFullAction({
owner: incompleteModel.owner.slug,
slug: incompleteModel.slug,
}).then((result) => {
if (result?.data) {
setModel(result.data);
} else {
setModel(null);
}
});
}, [incompleteModel]);

if (model === "loading") {
return <Skeleton height={160} />;
}

if (!model) {
return <div>Model not found</div>;
}

return <InnerUpgradeableModel model={model} />;
};
57 changes: 57 additions & 0 deletions apps/hub/src/app/admin/upgrade-versions/compare/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { notFound } from "next/navigation";

import { H2 } from "@/components/ui/Headers";
import { StyledLink } from "@/components/ui/StyledLink";
import { modelRoute } from "@/lib/routes";
import { loadModelFull } from "@/models/data/full";

import { UpgradeableModel } from "./UpgradeableModel";
import { UpgradeButton } from "./UpgradeButton";

type Props = {
searchParams: Promise<{ owner: string; slug: string }>;
};

export default async function CompareVersionsPage({ searchParams }: Props) {
const { owner, slug } = await searchParams;

const model = await loadModelFull({ owner, slug });
if (!model) {
notFound();
}

const { contentType } = model.currentRevision;
if (contentType !== "SquiggleSnippet") {
notFound();
}

return (
<div className="bg-white">
<H2>
<div className="flex items-center gap-4">
<div>
Compare model{" "}
<StyledLink
href={modelRoute({ owner: model.owner.slug, slug: model.slug })}
>
{model.owner.slug}/{model.slug}
</StyledLink>
</div>
<UpgradeButton model={model} />
</div>
</H2>
<div>
<p className="text-xs">
Check models with their current version and the new version, then
press the upgrade button if everything is ok.
</p>
<p className="text-xs">
<strong>
{`Code edits won't be saved, "Upgrade" button bumps only the model's version.`}
</strong>
</p>
</div>
<UpgradeableModel model={model} />
</div>
);
}
Loading
Loading