diff --git a/.changeset/mean-flies-wave.md b/.changeset/mean-flies-wave.md new file mode 100644 index 0000000000..eecb7e7829 --- /dev/null +++ b/.changeset/mean-flies-wave.md @@ -0,0 +1,6 @@ +--- +"@quri/squiggle-lang": patch +"@quri/squiggle-components": patch +--- + +Seeds should be stored for Model Revisions diff --git a/packages/components/src/lib/hooks/usePlaygroundSettings.ts b/packages/components/src/lib/hooks/usePlaygroundSettings.ts index 08dd10f04c..d37e5db2a2 100644 --- a/packages/components/src/lib/hooks/usePlaygroundSettings.ts +++ b/packages/components/src/lib/hooks/usePlaygroundSettings.ts @@ -1,12 +1,13 @@ import merge from "lodash/merge.js"; import { useCallback, useState } from "react"; +import { generateSeed } from "@quri/squiggle-lang"; + import { defaultPlaygroundSettings, PartialPlaygroundSettings, type PlaygroundSettings, } from "../../components/PlaygroundSettings.js"; -import { randomSeed } from "../seedGenerator.js"; export type Args = { defaultSettings: PartialPlaygroundSettings; @@ -41,7 +42,7 @@ export function usePlaygroundSettings({ ...settings, environment: { ...settings.environment, - seed: randomSeed(), + seed: generateSeed(), }, }); }; diff --git a/packages/components/src/lib/seedGenerator.ts b/packages/components/src/lib/seedGenerator.ts deleted file mode 100644 index 5a0af1f853..0000000000 --- a/packages/components/src/lib/seedGenerator.ts +++ /dev/null @@ -1,7 +0,0 @@ -import sample from "lodash/sample.js"; - -import { seedWords } from "./seedWords.js"; - -export function randomSeed(): string { - return [sample(seedWords), sample(seedWords), sample(seedWords)].join("_"); -} diff --git a/packages/hub/package.json b/packages/hub/package.json index fd51912bca..769ab86415 100644 --- a/packages/hub/package.json +++ b/packages/hub/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "db:migrate": "PRISMA_HIDE_UPDATE_MESSAGE=1 prisma migrate deploy", + "db:reset": "PRISMA_HIDE_UPDATE_MESSAGE=1 prisma migrate reset", "dev": "next dev -p 3001", "start": "__NEXT_PRIVATE_PREBUNDLED_REACT=next next start", "gen:prisma": "PRISMA_HIDE_UPDATE_MESSAGE=1 prisma generate", diff --git a/packages/hub/prisma/migrations/20240217215104_squiggle_snippet_seed/migration.sql b/packages/hub/prisma/migrations/20240217215104_squiggle_snippet_seed/migration.sql new file mode 100644 index 0000000000..25b51cbf73 --- /dev/null +++ b/packages/hub/prisma/migrations/20240217215104_squiggle_snippet_seed/migration.sql @@ -0,0 +1,3 @@ +ALTER TABLE "SquiggleSnippet" ADD COLUMN "seed" TEXT DEFAULT 'DEFAULT_SEED'; +UPDATE "SquiggleSnippet" SET "seed" = 'DEFAULT_SEED' WHERE "seed" IS NULL; +ALTER TABLE "SquiggleSnippet" ALTER COLUMN "seed" SET NOT NULL; \ No newline at end of file diff --git a/packages/hub/prisma/schema.prisma b/packages/hub/prisma/schema.prisma index 049cdff78a..cdd6023fd6 100644 --- a/packages/hub/prisma/schema.prisma +++ b/packages/hub/prisma/schema.prisma @@ -211,8 +211,7 @@ model ModelRevision { // optional until we populate old rows after migration author User? @relation(fields: [authorId], references: [id]) authorId String? - - comment String @default("") + comment String @default("") contentType ModelType @@ -233,6 +232,7 @@ model SquiggleSnippet { code String version String + seed String revision ModelRevision? } diff --git a/packages/hub/schema.graphql b/packages/hub/schema.graphql index 09546eb950..7b1eb05dad 100644 --- a/packages/hub/schema.graphql +++ b/packages/hub/schema.graphql @@ -347,6 +347,9 @@ input MutationCreateSquiggleSnippetModelInput { """Defaults to false""" isPrivate: Boolean + + """A unique seed, used for calculation""" + seed: String! slug: String! version: String! } @@ -667,11 +670,13 @@ interface SquiggleOutput { type SquiggleSnippet implements Node { code: String! id: ID! + seed: String! version: String! } input SquiggleSnippetContentInput { code: String! + seed: String! version: String! } diff --git a/packages/hub/src/app/admin/upgrade-versions/UpgradeVersionsPage.tsx b/packages/hub/src/app/admin/upgrade-versions/UpgradeVersionsPage.tsx index e02ee978c3..19ac93bb0a 100644 --- a/packages/hub/src/app/admin/upgrade-versions/UpgradeVersionsPage.tsx +++ b/packages/hub/src/app/admin/upgrade-versions/UpgradeVersionsPage.tsx @@ -44,6 +44,7 @@ const UpgradeableModel: FC<{ modelRef: UpgradeVersionsPage_Model$key }> = ({ id code version + seed } } } diff --git a/packages/hub/src/app/api/updateModelSeeds/route.ts b/packages/hub/src/app/api/updateModelSeeds/route.ts new file mode 100644 index 0000000000..b454ccc1dd --- /dev/null +++ b/packages/hub/src/app/api/updateModelSeeds/route.ts @@ -0,0 +1,80 @@ +import { PrismaClient } from "@prisma/client"; +import { NextRequest, NextResponse } from "next/server"; + +import { generateSeed } from "@quri/squiggle-lang"; + +const prisma = new PrismaClient(); + +async function updateSquiggleSnippetsSeedForModels() { + // Retrieve all models + const models = await prisma.model.findMany({ + include: { + revisions: { + include: { + squiggleSnippet: true, + }, + orderBy: { + createdAt: "desc", + }, + take: 1, + }, + }, + }); + + for (const model of models) { + const lastRevision = model.revisions[0]; + + // Check if the last revision's SquiggleSnippet has the DEFAULT_SEED + if ( + lastRevision.squiggleSnippet && + lastRevision.squiggleSnippet.seed === "DEFAULT_SEED" + ) { + const newSeed = generateSeed(); + + // Update all SquiggleSnippets for all revisions of the current model + for (const revision of model.revisions) { + if (revision.squiggleSnippet) { + await prisma.squiggleSnippet.update({ + where: { + id: revision.squiggleSnippet.id, + }, + data: { + seed: newSeed, + }, + }); + } + } + } + } +} + +export async function POST(req: NextRequest) { + const adminToken = req.headers.get("x-admin-token"); + const secretToken = process.env["ADMIN_SECRET_TOKEN"]; + + if (!secretToken || adminToken !== secretToken) { + return new NextResponse(null, { + status: 401, + statusText: "Unauthorized access.", + }); + } + + try { + await updateSquiggleSnippetsSeedForModels(); + return new NextResponse( + JSON.stringify({ + message: + "Successfully updated seeds for models where the last revision had DEFAULT_SEED.", + }), + { status: 200 } + ); + } catch (error) { + console.error("An error occurred:", error); + return new NextResponse( + JSON.stringify({ + error: "An error occurred while updating seeds.", + }), + { status: 500 } + ); + } +} diff --git a/packages/hub/src/app/models/[owner]/[slug]/EditSquiggleSnippetModel.tsx b/packages/hub/src/app/models/[owner]/[slug]/EditSquiggleSnippetModel.tsx index 1a766aa3fc..3b8614a839 100644 --- a/packages/hub/src/app/models/[owner]/[slug]/EditSquiggleSnippetModel.tsx +++ b/packages/hub/src/app/models/[owner]/[slug]/EditSquiggleSnippetModel.tsx @@ -31,6 +31,7 @@ import { import { EditModelExports } from "@/components/exports/EditModelExports"; import { ReactRoot } from "@/components/ReactRoot"; import { FormModal } from "@/components/ui/FormModal"; +import { SAMPLE_COUNT_DEFAULT } from "@/constants"; import { useAvailableHeight } from "@/hooks/useAvailableHeight"; import { useMutationForm } from "@/hooks/useMutationForm"; import { extractFromGraphqlErrorUnion } from "@/lib/graphqlHelpers"; @@ -151,6 +152,7 @@ export const EditSquiggleSnippetModel: FC = ({ id code version + seed } } exports { @@ -183,6 +185,8 @@ export const EditSquiggleSnippetModel: FC = ({ "SquiggleSnippet" ); + const seed = content.seed; + const initialFormValues: SquiggleSnippetFormShape = useMemo(() => { return { code: content.code, @@ -232,6 +236,7 @@ export const EditSquiggleSnippetModel: FC = ({ content: { code: formData.code, version, + seed: seed, }, relativeValuesExports: formData.relativeValuesExports, exports: formData.exports, @@ -399,6 +404,11 @@ export const EditSquiggleSnippetModel: FC = ({ }; } + playgroundProps.environment = { + sampleCount: SAMPLE_COUNT_DEFAULT, + seed: seed, + }; + if ( versionSupportsOnOpenExport.propsByVersion<"SquigglePlayground">( squiggle.version, diff --git a/packages/hub/src/app/new/model/NewModel.tsx b/packages/hub/src/app/new/model/NewModel.tsx index fe961efb9e..14c5ffc5fe 100644 --- a/packages/hub/src/app/new/model/NewModel.tsx +++ b/packages/hub/src/app/new/model/NewModel.tsx @@ -6,6 +6,7 @@ import { FormProvider } from "react-hook-form"; import { useLazyLoadQuery } from "react-relay"; import { graphql } from "relay-runtime"; +import { generateSeed } from "@quri/squiggle-lang"; import { Button, CheckboxFormField } from "@quri/ui"; import { defaultSquiggleVersion } from "@quri/versioned-squiggle-components"; @@ -102,6 +103,7 @@ export const NewModel: FC = () => { isPrivate: data.isPrivate, code: defaultCode, version: defaultSquiggleVersion, + seed: generateSeed(), }, }), onCompleted: (result) => { diff --git a/packages/hub/src/constants.ts b/packages/hub/src/constants.ts index 4fafce342f..4555d4d32a 100644 --- a/packages/hub/src/constants.ts +++ b/packages/hub/src/constants.ts @@ -3,3 +3,7 @@ // Note that only `NEXT_PUBLIC_*` vars are affected; others can be used through `process.env.FOO` without issues. export const VERCEL_URL = process.env["NEXT_PUBLIC_VERCEL_URL"]; + +export const SAMPLE_COUNT_DEFAULT = 1000; + +export const DEFAULT_SEED = "DEFAULT_SEED"; diff --git a/packages/hub/src/graphql/mutations/adminUpdateModelVersion.ts b/packages/hub/src/graphql/mutations/adminUpdateModelVersion.ts index ddacc7a7e5..b7fdbd75d7 100644 --- a/packages/hub/src/graphql/mutations/adminUpdateModelVersion.ts +++ b/packages/hub/src/graphql/mutations/adminUpdateModelVersion.ts @@ -54,6 +54,7 @@ builder.mutationField("adminUpdateModelVersion", (t) => create: { code: model.currentRevision.squiggleSnippet.code, version: input.version, + seed: model.currentRevision.squiggleSnippet.seed, }, }, contentType: "SquiggleSnippet", diff --git a/packages/hub/src/graphql/mutations/createSquiggleSnippetModel.ts b/packages/hub/src/graphql/mutations/createSquiggleSnippetModel.ts index 55bf004bf7..45381a4709 100644 --- a/packages/hub/src/graphql/mutations/createSquiggleSnippetModel.ts +++ b/packages/hub/src/graphql/mutations/createSquiggleSnippetModel.ts @@ -36,6 +36,10 @@ builder.mutationField("createSquiggleSnippetModel", (t) => isPrivate: t.input.boolean({ description: "Defaults to false", }), + seed: t.input.string({ + required: true, + description: "A unique seed, used for calculation", + }), }, resolve: async (_, { input }, { session }) => { const model = await prisma.$transaction(async (tx) => { @@ -67,6 +71,7 @@ builder.mutationField("createSquiggleSnippetModel", (t) => create: { code: input.code, version: input.version, + seed: input.seed, }, }, author: { diff --git a/packages/hub/src/graphql/mutations/updateSquiggleSnippetModel.ts b/packages/hub/src/graphql/mutations/updateSquiggleSnippetModel.ts index 7074b387fc..c396a1ad5d 100644 --- a/packages/hub/src/graphql/mutations/updateSquiggleSnippetModel.ts +++ b/packages/hub/src/graphql/mutations/updateSquiggleSnippetModel.ts @@ -35,6 +35,7 @@ const SquiggleSnippetContentInput = builder.inputType( fields: (t) => ({ code: t.string({ required: true }), version: t.string({ required: true }), + seed: t.string({ required: true }), }), } ); @@ -144,6 +145,7 @@ builder.mutationField("updateSquiggleSnippetModel", (t) => create: { code: input.content.code, version: input.content.version, + seed: input.content.seed, }, }, contentType: "SquiggleSnippet", diff --git a/packages/hub/src/graphql/queries/runSquiggle.ts b/packages/hub/src/graphql/queries/runSquiggle.ts index eb86528dcd..dd4a590f03 100644 --- a/packages/hub/src/graphql/queries/runSquiggle.ts +++ b/packages/hub/src/graphql/queries/runSquiggle.ts @@ -3,6 +3,7 @@ import crypto from "crypto"; import { SqProject, SqValue } from "@quri/squiggle-lang"; +import { DEFAULT_SEED, SAMPLE_COUNT_DEFAULT } from "@/constants"; import { builder } from "@/graphql/builder"; import { prisma } from "@/prisma"; @@ -80,9 +81,9 @@ export async function runSquiggle(code: string): Promise { const MAIN = "main"; const env = { - sampleCount: 1000, // int + sampleCount: SAMPLE_COUNT_DEFAULT, // int xyPointLength: 1000, // int - seed: "default_seed", + seed: DEFAULT_SEED, }; const project = SqProject.create({ environment: env }); diff --git a/packages/hub/src/graphql/types/ModelRevision.ts b/packages/hub/src/graphql/types/ModelRevision.ts index 056ee4acec..7baed3f336 100644 --- a/packages/hub/src/graphql/types/ModelRevision.ts +++ b/packages/hub/src/graphql/types/ModelRevision.ts @@ -9,6 +9,7 @@ export const SquiggleSnippet = builder.prismaNode("SquiggleSnippet", { fields: (t) => ({ code: t.exposeString("code"), version: t.exposeString("version"), + seed: t.exposeString("seed"), }), }); diff --git a/packages/squiggle-lang/src/index.ts b/packages/squiggle-lang/src/index.ts index 928f1c9f6a..5005bee76f 100644 --- a/packages/squiggle-lang/src/index.ts +++ b/packages/squiggle-lang/src/index.ts @@ -130,3 +130,5 @@ export { SCALE_POWER_DEFAULT_CONSTANT, SCALE_SYMLOG_DEFAULT_CONSTANT, } from "./value/VScale.js"; + +export { generateSeed } from "./utility/seedGenerator.js"; diff --git a/packages/squiggle-lang/src/utility/seedGenerator.ts b/packages/squiggle-lang/src/utility/seedGenerator.ts new file mode 100644 index 0000000000..d8647d6ddc --- /dev/null +++ b/packages/squiggle-lang/src/utility/seedGenerator.ts @@ -0,0 +1,11 @@ +import sample from "lodash/sample.js"; + +import { seedWords } from "./seedWords.js"; + +export function generateSeed(): string { + return [ + sample(seedWords)?.toUpperCase(), + sample(seedWords)?.toUpperCase(), + sample(seedWords)?.toUpperCase(), + ].join("_"); +} diff --git a/packages/components/src/lib/seedWords.ts b/packages/squiggle-lang/src/utility/seedWords.ts similarity index 100% rename from packages/components/src/lib/seedWords.ts rename to packages/squiggle-lang/src/utility/seedWords.ts