Skip to content

Commit

Permalink
Merge pull request #2515 from quantified-uncertainty/ModelExports
Browse files Browse the repository at this point in the history
Starting to add Model Exports
  • Loading branch information
berekuk authored Nov 14, 2023
2 parents acf5802 + f8a6f6d commit 51fa4cf
Show file tree
Hide file tree
Showing 29 changed files with 528 additions and 112 deletions.
4 changes: 4 additions & 0 deletions packages/components/src/components/DynamicSquiggleViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { CodeEditorHandle } from "./CodeEditor.js";
import { PartialPlaygroundSettings } from "./PlaygroundSettings.js";
import { SquiggleViewerHandle } from "./SquiggleViewer/index.js";
import { ErrorBoundary } from "./ErrorBoundary.js";
import { SqValuePath } from "@quri/squiggle-lang";

type Props = {
squiggleOutput: SquiggleOutput | undefined;
isRunning: boolean;
showHeader?: boolean;
localSettingsEnabled?: boolean;
editor?: CodeEditorHandle;
rootPathOverride?: SqValuePath;
} & PartialPlaygroundSettings;

/* Wrapper for SquiggleViewer that shows the rendering stats and isRunning state. */
Expand All @@ -25,6 +27,7 @@ export const DynamicSquiggleViewer = forwardRef<SquiggleViewerHandle, Props>(
showHeader = true,
localSettingsEnabled,
editor,
rootPathOverride,
...settings
},
viewerRef
Expand All @@ -43,6 +46,7 @@ export const DynamicSquiggleViewer = forwardRef<SquiggleViewerHandle, Props>(
resultVariables={getResultVariables(squiggleOutput)}
resultItem={getResultValue(squiggleOutput)}
editor={editor}
rootPathOverride={rootPathOverride}
/>
</ErrorBoundary>
</div>
Expand Down
4 changes: 4 additions & 0 deletions packages/components/src/components/SquiggleChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import {
import { DynamicSquiggleViewer } from "./DynamicSquiggleViewer.js";
import { PartialPlaygroundSettings } from "./PlaygroundSettings.js";
import { useRunnerState } from "../lib/hooks/useRunnerState.js";
import { SqValuePath } from "@quri/squiggle-lang";

type Props = {
code: string;
showHeader?: boolean;
localSettingsEnabled?: boolean;
rootPathOverride?: SqValuePath; // Note: This should be static. We don't support rootPathOverride to change once set.
} & (StandaloneExecutionProps | ProjectExecutionProps) &
// `environment` is passed through StandaloneExecutionProps; this way we guarantee that it's not compatible with `project` prop
Omit<PartialPlaygroundSettings, "environment">;
Expand All @@ -26,6 +28,7 @@ export const SquiggleChart: FC<Props> = memo(function SquiggleChart({
project,
continues,
environment,
rootPathOverride,
...settings
}) {
// We go through runnerState to bump executionId on code changes;
Expand All @@ -45,6 +48,7 @@ export const SquiggleChart: FC<Props> = memo(function SquiggleChart({

return (
<DynamicSquiggleViewer
rootPathOverride={rootPathOverride}
squiggleOutput={squiggleOutput}
isRunning={isRunning}
showHeader={showHeader}
Expand Down
20 changes: 20 additions & 0 deletions packages/components/src/components/SquigglePlayground/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ import {
} from "./LeftPlaygroundPanel/index.js";
import { ResizableTwoPanelLayout } from "./ResizableTwoPanelLayout.js";

export type ModelExport = {
variableName: string;
title?: string;
};

/*
* We don't support `project` or `continues` in the playground.
* First, because playground will support multi-file mode by itself.
Expand All @@ -37,6 +42,7 @@ export type SquigglePlaygroundProps = {
sourceId?: string;
linker?: SqLinker;
onCodeChange?(code: string): void;
onExportsChange?(exports: ModelExport[]): void;
/* When settings change */
onSettingsChange?(settings: PlaygroundSettings): void;
/* Height of the playground */
Expand All @@ -62,6 +68,7 @@ export const SquigglePlayground: React.FC<SquigglePlaygroundProps> = (
defaultCode,
linker,
onCodeChange,
onExportsChange,
onSettingsChange,
renderExtraControls,
renderExtraDropdownItems,
Expand Down Expand Up @@ -107,6 +114,19 @@ export const SquigglePlayground: React.FC<SquigglePlaygroundProps> = (
isRunning: boolean;
}>({ output: undefined, isRunning: false });

useEffect(() => {
const _output = output.output?.output;
if (_output && _output.ok) {
const exports = _output.value.exports;
const _exports: ModelExport[] = exports.entries().map((e) => {
return { variableName: e[0], title: e[1].title() };
});
onExportsChange && onExportsChange(_exports);
} else {
onExportsChange && onExportsChange([]);
}
}, [output, onExportsChange]);

const leftPanelRef = useRef<LeftPlaygroundPanelHandle>(null);
const rightPanelRef = useRef<SquiggleViewerHandle>(null);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,22 +227,27 @@ export const ViewerProvider: FC<
localSettingsEnabled: boolean;
editor?: CodeEditorHandle;
beginWithVariablesCollapsed?: boolean;
rootPathOverride?: SqValuePath;
}>
> = ({
partialPlaygroundSettings,
localSettingsEnabled,
editor,
beginWithVariablesCollapsed,
rootPathOverride,
children,
}) => {
// can't store settings in the state because we don't want to rerender the entire tree on every change

const localItemStateStoreRef = useRef<LocalItemStateStore>(
beginWithVariablesCollapsed ? collapsedVariablesDefault : {}
);

const itemHandlesStoreRef = useRef<{ [k: string]: HTMLDivElement }>({});

const [focused, setFocused] = useState<SqValuePath | undefined>();
const [focused, setFocused] = useState<SqValuePath | undefined>(
rootPathOverride
);

const globalSettings = useMemo(() => {
return merge({}, defaultPlaygroundSettings, partialPlaygroundSettings);
Expand Down
44 changes: 32 additions & 12 deletions packages/components/src/components/SquiggleViewer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
useUnfocus,
useViewerContext,
} from "./ViewerProvider.js";
import { extractSubvalueByPath, pathItemFormat } from "./utils.js";
import { extractSubvalueByPath, pathIsEqual, pathItemFormat } from "./utils.js";

export type SquiggleViewerHandle = {
viewValuePath(path: SqValuePath): void;
Expand All @@ -32,37 +32,54 @@ export type SquiggleViewerProps = {
resultItem: result<SqValue, SqError> | undefined;
localSettingsEnabled?: boolean;
editor?: CodeEditorHandle;
rootPathOverride?: SqValuePath;
} & PartialPlaygroundSettings;

const SquiggleViewerOuter = forwardRef<
SquiggleViewerHandle,
SquiggleViewerProps
>(function SquiggleViewerOuter({ resultVariables, resultItem }, ref) {
>(function SquiggleViewerOuter(
{ resultVariables, resultItem, rootPathOverride },
ref
) {
const { focused, dispatch, getCalculator } = useViewerContext();
const unfocus = useUnfocus();
const focus = useFocus();

const navLinkStyle =
"text-sm text-slate-500 hover:text-slate-900 hover:underline font-mono cursor-pointer";

const focusedNavigation = focused && (
const isFocusedOnRootPathOverride =
focused && rootPathOverride && pathIsEqual(focused, rootPathOverride);

// If we're focused on the root path override, we need to adjust the focused path accordingly when presenting the navigation, so that it begins with the root path intead. This is a bit confusing.
const rootPathFocusedAdjustment: number = rootPathOverride
? rootPathOverride.items.length - 1
: 0;

const focusedNavigation = focused && !isFocusedOnRootPathOverride && (
<div className="flex items-center mb-3 pl-1">
<span onClick={unfocus} className={navLinkStyle}>
{focused.root === "bindings" ? "Variables" : focused.root}
</span>
{!rootPathOverride && (
<>
<span onClick={unfocus} className={navLinkStyle}>
{focused.root === "bindings" ? "Variables" : focused.root}
</span>

<ChevronRightIcon className="text-slate-300" size={24} />
</>
)}

{focused
.itemsAsValuePaths({ includeRoot: false })
.slice(0, -1)
.slice(rootPathFocusedAdjustment, -1)
.map((path, i) => (
<div key={i} className="flex items-center">
<ChevronRightIcon className="text-slate-300" size={24} />
<>
<div onClick={() => focus(path)} className={navLinkStyle}>
{pathItemFormat(path.items[i])}
{pathItemFormat(path.items[i + rootPathFocusedAdjustment])}
</div>
</div>
<ChevronRightIcon className="text-slate-300" size={24} />
</>
))}
<ChevronRightIcon className="text-slate-300" size={24} />
</div>
);

Expand Down Expand Up @@ -132,6 +149,7 @@ const innerComponent = forwardRef<SquiggleViewerHandle, SquiggleViewerProps>(
resultItem,
localSettingsEnabled = false,
editor,
rootPathOverride,
...partialPlaygroundSettings
},
ref
Expand All @@ -142,10 +160,12 @@ const innerComponent = forwardRef<SquiggleViewerHandle, SquiggleViewerProps>(
localSettingsEnabled={localSettingsEnabled}
editor={editor}
beginWithVariablesCollapsed={resultItem !== undefined && resultItem.ok}
rootPathOverride={rootPathOverride}
>
<SquiggleViewerOuter
resultVariables={resultVariables}
resultItem={resultItem}
rootPathOverride={rootPathOverride}
ref={ref}
/>
</ViewerProvider>
Expand Down
4 changes: 4 additions & 0 deletions packages/components/src/components/SquiggleViewer/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export function pathAsString(path: SqValuePath) {
}
}

export function pathIsEqual(path1: SqValuePath, path2: SqValuePath) {
return pathAsString(path1) === pathAsString(path2);
}

export function pathToShortName(path: SqValuePath): string | undefined {
if (isTopLevel(path)) {
return topLevelName(path);
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/lib/hooks/useSquiggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export type SquiggleArgs = {
export type SquiggleOutput = {
output: result<
{
exports: SqDict;
result: SqValue;
bindings: SqDict;
},
Expand Down
18 changes: 18 additions & 0 deletions packages/components/src/stories/SquiggleChart/Basic.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Meta, StoryObj } from "@storybook/react";

import { SquiggleChart } from "../../components/SquiggleChart.js";
import { SqValuePath } from "@quri/squiggle-lang";

const meta = {
component: SquiggleChart,
Expand Down Expand Up @@ -31,3 +32,20 @@ export const String: Story = {
code: '"Lucky day!"',
},
};

export const WithPathOverride: Story = {
name: "WithPathOverride",
args: {
code: `
export foo = {bar: {char: {baz: 10}}}
export bar = {baz: 20}
`,
rootPathOverride: new SqValuePath({
root: "bindings",
items: [
{ type: "string", value: "foo" },
{ type: "string", value: "bar" },
],
}),
},
};
12 changes: 12 additions & 0 deletions packages/hub/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ To deploy a migration to the production database:
- obtain the database connection string from Digital Ocean;
- run `DATABASE_URL=... prisma migrate deploy` locally.

## Changing the database

1. Edit `schema.prisma`
2. Run ``pnpm prisma migrate dev --name [migration-name-here]

## Notes on changing the schema

1. Edit `schema.prisma`, and edit the query/mutations functions.
2. Ensure the type has been added in `builder.prismaNode("NameHere", {...types` somewhere that gets read, somewhere in `/graphql/types`.
3. `schema.graphql` is autogenerated. Do not change it manually.
4. The Typescript language server will frequently get out of date when editing the schema. You can update use VS Code (control-shift-p) to force a restart. You might want to update the Prisma language server too. You can also just try to get everything right, run `pnpm run gen`, and reload files, sometimes that fixes things.

# Development notes

[How to load GraphQL data in Next.js pages](/docs/relay-pages.md)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- CreateTable
CREATE TABLE "ModelExport" (
"id" TEXT NOT NULL,
"modelRevisionId" TEXT NOT NULL,
"variableName" TEXT NOT NULL,
"title" TEXT,

CONSTRAINT "ModelExport_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "ModelExport_modelRevisionId_variableName_key" ON "ModelExport"("modelRevisionId", "variableName");

-- AddForeignKey
ALTER TABLE "ModelExport" ADD CONSTRAINT "ModelExport_modelRevisionId_fkey" FOREIGN KEY ("modelRevisionId") REFERENCES "ModelRevision"("id") ON DELETE CASCADE ON UPDATE CASCADE;
11 changes: 11 additions & 0 deletions packages/hub/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ model ModelRevision {
contentId String? @unique
relativeValuesExports RelativeValuesExport[]
exports ModelExport[]
// required by Prisma, but unused since `model` field should point at the same entity
currentRevisionModel Model? @relation("CurrentRevision")
Expand All @@ -228,6 +229,16 @@ model SquiggleSnippet {
revision ModelRevision?
}

model ModelExport {
id String @id @default(cuid())
modelRevision ModelRevision @relation(fields: [modelRevisionId], references: [id], onDelete: Cascade)
modelRevisionId String
variableName String
title String?
@@unique([modelRevisionId, variableName], name: "uniqueKey")
}

// Definitions are polymorphic, but organized differently from Models, without content types.
// Reasoning: https://github.com/quantified-uncertainty/squiggle/issues/1828#issuecomment-1563596166
model RelativeValuesDefinition {
Expand Down
14 changes: 14 additions & 0 deletions packages/hub/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,19 @@ type ModelEdge {
node: Model!
}

type ModelExport implements Node {
id: ID!
modelRevision: ModelRevision!
title: String
variableName: String!
}

type ModelRevision implements Node {
author: User
comment: String!
content: ModelContent!
createdAtTimestamp: Float!
exports: [ModelExport!]!
forRelativeValues(input: ModelRevisionForRelativeValuesInput): RelativeValuesExport
id: ID!
model: Model!
Expand Down Expand Up @@ -371,6 +379,7 @@ union MutationUpdateRelativeValuesDefinitionResult = BaseError | UpdateRelativeV
input MutationUpdateSquiggleSnippetModelInput {
comment: String
content: SquiggleSnippetContentInput!
exports: [SquiggleModelExportInput!]
owner: String!
relativeValuesExports: [RelativeValuesExportInput!]
slug: String!
Expand Down Expand Up @@ -536,6 +545,11 @@ type SquiggleErrorOutput implements SquiggleOutput {
isCached: Boolean!
}

input SquiggleModelExportInput {
title: String
variableName: String!
}

type SquiggleOkOutput implements SquiggleOutput {
bindingsJSON: String!
isCached: Boolean!
Expand Down
Loading

4 comments on commit 51fa4cf

@vercel
Copy link

@vercel vercel bot commented on 51fa4cf Nov 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 51fa4cf Nov 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 51fa4cf Nov 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

quri-ui – ./packages/ui

quri-ui.vercel.app
quri-ui-git-main-quantified-uncertainty.vercel.app
quri-ui-quantified-uncertainty.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 51fa4cf Nov 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.