Skip to content

Commit

Permalink
Merge pull request #3466 from quantified-uncertainty/sqvalue-compare
Browse files Browse the repository at this point in the history
New /admin/upgrade-versions UI; compareVersions function
  • Loading branch information
berekuk authored Dec 26, 2024
2 parents 522aa06 + 2ffceb2 commit fc0c177
Show file tree
Hide file tree
Showing 12 changed files with 666 additions and 160 deletions.
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>
);
};
36 changes: 36 additions & 0 deletions apps/hub/src/app/admin/upgrade-versions/compare/UpgradeButton.tsx
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

0 comments on commit fc0c177

Please sign in to comment.