Skip to content

Commit

Permalink
chore: add an EvalBoundedInterp component
Browse files Browse the repository at this point in the history
This is more or less a drop-in replacement for `EvalFull` and is ready
to go, save for hackworthltd/primer#1247.
Therefore, we don't yet use it in the `Edit` and `PictureInPicture`
components.

Signed-off-by: Drew Hess <[email protected]>
  • Loading branch information
dhess committed Apr 23, 2024
1 parent 01b2ace commit d4ef501
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 29 deletions.
6 changes: 5 additions & 1 deletion orval.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { defineConfig } from "orval";
// This lists the operation IDs for such requests.
// We configure Orval to generate simple `useQuery`-based code, as it would for a GET request,
// rather than its default `useMutation` approach for POSTs.
const getStylePostRequests = ["getAvailableActions", "eval-full"];
const getStylePostRequests = [
"getAvailableActions",
"eval-full",
"eval-bounded-interp",
];
const useQueryPost: {
[key: string]: OperationOptions;
} = Object.assign(
Expand Down
33 changes: 33 additions & 0 deletions src/components/EvalBoundedInterp/EvalBoundedInterp.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Meta, StoryObj } from "@storybook/react";
import { tree3 } from "../examples/trees";

import { EvalBoundedInterp } from "./";

const meta: Meta<typeof EvalBoundedInterp> = {
title: "Application/Component Library/EvalBoundedInterp",
component: EvalBoundedInterp,
decorators: [
(Story) => (
<div className="h-screen">
<Story />
</div>
),
],
};

export default meta;
type Story = StoryObj<typeof EvalBoundedInterp>;

export const Default: Story = {
args: {
moduleName: ["Primer", "Examples"],
evalBoundedInterp: {
request: () => {
return;
},
result: { tag: "EvalBoundedInterpRespNormal", contents: tree3 },
},
level: "Expert",
defs: ["footballGame", "whatsopposite"],
},
};
105 changes: 105 additions & 0 deletions src/components/EvalBoundedInterp/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { useState } from "react";
import { NodeChange, ReactFlowProvider, useReactFlow } from "reactflow";
import { EvalBoundedInterpResp, GlobalName, Level } from "@/primer-api";
import { SelectMenu, TreeReactFlowOne } from "@/components";
import {
TreeReactFlowOneProps,
defaultTreeReactFlowProps,
} from "../TreeReactFlow";

export type EvalBoundedInterpProps = {
moduleName: string[];
evalBoundedInterp: {
request: (baseName: string | undefined) => void;
result?: EvalBoundedInterpResp;
};
level: Level;
defs: string[];
initialEvalDef: string | undefined;
extraTreeProps: Partial<TreeReactFlowOneProps>;
};

const Evaluated = (p: {
defName: GlobalName;
evaluated?: EvalBoundedInterpResp;
level: Level;
extraTreeProps: Partial<TreeReactFlowOneProps>;
}) => {
const padding = 1.0;
const { fitView } = useReactFlow();
const onNodesChange = (_: NodeChange[]) => {
fitView({ padding });
};
const resultTree = () => {
switch (p?.evaluated?.tag) {
case "EvalBoundedInterpRespNormal":
return { tree: p.evaluated.contents };
default:
// This should be some indication of an error having occurred,
// but our UI is a bit too limited for that at the moment.
return {};
}
};

return (
<TreeReactFlowOne
{...defaultTreeReactFlowProps}
{...resultTree()}
level={p.level}
zoomBarProps={{ padding }}
onNodesChange={onNodesChange}
fitViewOptions={{ padding }}
{...p.extraTreeProps}
/>
);
};

const disableEval = "<disabled>";

// We only offer to evaluate the definitions in the "main" module
export const EvalBoundedInterp = ({
defs,
evalBoundedInterp,
moduleName,
level,
initialEvalDef,
extraTreeProps,
}: EvalBoundedInterpProps): JSX.Element => {
const [evalDef, setEvalDef0] = useState(initialEvalDef ?? disableEval);
const setEvalDef = (e: string) => {
setEvalDef0(e);
evalBoundedInterp.request(e === disableEval ? undefined : e);
};
return (
<div className="flex h-full flex-col overflow-auto">
<div className="mx-2">
<SelectMenu
label="Definition"
selected={evalDef}
options={[disableEval].concat(defs)}
optionType="code"
onChange={(selection: string) => setEvalDef(selection)}
/>
</div>
{evalDef !== disableEval && (
<>
<div className="grow">
<ReactFlowProvider>
<Evaluated
key={evalDef}
defName={{ qualifiedModule: moduleName, baseName: evalDef }}
{...(evalBoundedInterp.result
? { evaluated: evalBoundedInterp.result }
: {})}
level={level}
extraTreeProps={extraTreeProps}
/>
</ReactFlowProvider>
</div>
</>
)}
</div>
);
};

export default EvalBoundedInterp;
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { default as CreateDefModal } from "./CreateDefModal";
export { default as DefsToolbar } from "./DefsToolbar";
export { default as Edit } from "./Edit";
export { default as Error } from "./Error";
export { default as EvalBoundedInterp } from "./EvalBoundedInterp";
export { default as EvalFull } from "./EvalFull";
export { default as GroupedButtonList } from "./GroupedButtonList";
export { default as NoMatch } from "./NoMatch";
Expand Down
67 changes: 39 additions & 28 deletions src/primer-api/primer-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,7 @@ export const useEvalFull = <TData = Awaited<ReturnType<ReturnType<typeof useEval
/**
* @summary Using the interpreter, evaluate the named definition to normal form (or time out)
*/
export const useEvalBoundedInterpHook = () => {
const useEvalBoundedInterpHook = () => {
const evalBoundedInterp = useCustomInstance<EvalBoundedInterpResp>();

return (
Expand All @@ -714,47 +714,58 @@ export const useEvalBoundedInterpHook = () => {
}


export const getEvalBoundedInterpQueryKey = (sessionId: string,
globalName: GlobalName,
params?: EvalBoundedInterpParams,) => {
return [`/openapi/sessions/${sessionId}/eval-bounded-interp`, ...(params ? [params]: []), globalName] as const;
}

export const useEvalBoundedInterpMutationOptions = <TError = ErrorType<void>,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<ReturnType<typeof useEvalBoundedInterpHook>>>, TError,{sessionId: string;data: GlobalName;params?: EvalBoundedInterpParams}, TContext>, }
): UseMutationOptions<Awaited<ReturnType<ReturnType<typeof useEvalBoundedInterpHook>>>, TError,{sessionId: string;data: GlobalName;params?: EvalBoundedInterpParams}, TContext> => {
const {mutation: mutationOptions} = options ?? {};

export const useEvalBoundedInterpQueryOptions = <TData = Awaited<ReturnType<ReturnType<typeof useEvalBoundedInterpHook>>>, TError = ErrorType<void>>(sessionId: string,
globalName: GlobalName,
params?: EvalBoundedInterpParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<ReturnType<typeof useEvalBoundedInterpHook>>>, TError, TData>>, }
) => {

const evalBoundedInterp = useEvalBoundedInterpHook()
const {query: queryOptions} = options ?? {};

const queryKey = queryOptions?.queryKey ?? getEvalBoundedInterpQueryKey(sessionId,globalName,params);

const mutationFn: MutationFunction<Awaited<ReturnType<ReturnType<typeof useEvalBoundedInterpHook>>>, {sessionId: string;data: GlobalName;params?: EvalBoundedInterpParams}> = (props) => {
const {sessionId,data,params} = props ?? {};
const evalBoundedInterp = useEvalBoundedInterpHook();

return evalBoundedInterp(sessionId,data,params,)
}
const queryFn: QueryFunction<Awaited<ReturnType<ReturnType<typeof useEvalBoundedInterpHook>>>> = () => evalBoundedInterp(sessionId,globalName,params, );





return { mutationFn, ...mutationOptions }}
return { queryKey, queryFn, enabled: !!(sessionId), ...queryOptions} as UseQueryOptions<Awaited<ReturnType<ReturnType<typeof useEvalBoundedInterpHook>>>, TError, TData> & { queryKey: QueryKey }
}

export type EvalBoundedInterpMutationResult = NonNullable<Awaited<ReturnType<ReturnType<typeof useEvalBoundedInterpHook>>>>
export type EvalBoundedInterpMutationBody = GlobalName
export type EvalBoundedInterpMutationError = ErrorType<void>
export type EvalBoundedInterpQueryResult = NonNullable<Awaited<ReturnType<ReturnType<typeof useEvalBoundedInterpHook>>>>
export type EvalBoundedInterpQueryError = ErrorType<void>

/**
/**
* @summary Using the interpreter, evaluate the named definition to normal form (or time out)
*/
export const useEvalBoundedInterp = <TError = ErrorType<void>,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<ReturnType<typeof useEvalBoundedInterpHook>>>, TError,{sessionId: string;data: GlobalName;params?: EvalBoundedInterpParams}, TContext>, }
): UseMutationResult<
Awaited<ReturnType<ReturnType<typeof useEvalBoundedInterpHook>>>,
TError,
{sessionId: string;data: GlobalName;params?: EvalBoundedInterpParams},
TContext
> => {
export const useEvalBoundedInterp = <TData = Awaited<ReturnType<ReturnType<typeof useEvalBoundedInterpHook>>>, TError = ErrorType<void>>(
sessionId: string,
globalName: GlobalName,
params?: EvalBoundedInterpParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<ReturnType<typeof useEvalBoundedInterpHook>>>, TError, TData>>, }

): UseQueryResult<TData, TError> & { queryKey: QueryKey } => {

const queryOptions = useEvalBoundedInterpQueryOptions(sessionId,globalName,params,options)

const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & { queryKey: QueryKey };

query.queryKey = queryOptions.queryKey ;

return query;
}



const mutationOptions = useEvalBoundedInterpMutationOptions(options);

return useMutation(mutationOptions);
}

/**
* @summary Get the specified session's name
*/
Expand Down

0 comments on commit d4ef501

Please sign in to comment.