Skip to content

Commit

Permalink
Add support for adding metadata in websockets
Browse files Browse the repository at this point in the history
  • Loading branch information
kielbasa-elp committed Mar 13, 2024
1 parent 03f6731 commit fca9a30
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { usePipelineRun } from "./usePipelineRun";
import { IBlockConfig, IExtendedPipeline } from "./pipeline.types";
import { errorToast } from "~/components/toasts/errorToast";

export type Metadata = Record<string, any>;

export interface IEvent {
block: string;
output: string;
Expand All @@ -34,6 +36,8 @@ interface IRunPipelineContext {
isValid: boolean;
organizationId: number;
pipelineId: number;
metadata: Metadata;
setMetadata: (value: Metadata) => void;
}

const RunPipelineContext = React.createContext<IRunPipelineContext | undefined>(
Expand All @@ -49,6 +53,7 @@ export const RunPipelineProvider: React.FC<RunPipelineProviderProps> = ({
pipeline,
alias,
}) => {
const [metadata, setMetadata] = useState<Metadata>({});
const [errors, setErrors] = useState<Record<string, string[]>>({});
const [events, setEvents] = useState<any[]>([]);
const [blockStatuses, setBlockStatuses] = useState<Record<string, boolean>>(
Expand Down Expand Up @@ -93,7 +98,7 @@ export const RunPipelineProvider: React.FC<RunPipelineProviderProps> = ({

const handleStartRun = useCallback(async () => {
setErrors({});
await startRun([], alias);
await startRun({ initial_inputs: [], alias, metadata });
}, [startRun, alias]);

const handlePush = useCallback(
Expand Down Expand Up @@ -155,6 +160,8 @@ export const RunPipelineProvider: React.FC<RunPipelineProviderProps> = ({
organizationId: pipeline.organization_id,
pipelineId: pipeline.id,
errors,
metadata,
setMetadata,
}),
[
errors,
Expand All @@ -168,6 +175,8 @@ export const RunPipelineProvider: React.FC<RunPipelineProviderProps> = ({
blockStatuses,
blockValidations,
isValid,
metadata,
setMetadata,
]
);
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ import { RunPipelineButton } from "./RunPipelineButton";
import { useRunPipeline } from "../RunPipelineProvider";
import { IPipelineConfig } from "~/components/pages/pipelines/pipeline.types";
import { useDebounce, useIsFirstRender } from "usehooks-ts";
import { MetadataField } from "./Metadata";

export const BuilderHeader: React.FC<PropsWithChildren> = ({ children }) => {
return (
<header className="absolute top-8 left-4 right-4 z-10 flex justify-between pointer-events-none">
<RunPipelineButton />
<div className="flex gap-2 items-center pointer-events-auto">
<RunPipelineButton />

<MetadataField />
</div>

<div className="flex gap-2 items-center pointer-events-auto">
{children}
Expand Down
146 changes: 146 additions & 0 deletions apps/web-remix/app/components/pages/pipelines/build/Metadata.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import React, { ButtonHTMLAttributes, PropsWithChildren, useRef } from "react";
import { Icon } from "@elpassion/taco";
import classNames from "classnames";
import { useBoolean, useOnClickOutside } from "usehooks-ts";
import {
useRunPipeline,
Metadata as IMetadata,
} from "~/components/pages/pipelines/RunPipelineProvider";
import { z } from "zod";
import { withZod } from "@remix-validated-form/with-zod";
import { useFormContext, ValidatedForm } from "remix-validated-form";
import { Field } from "~/components/form/fields/field.context";
import { MonacoEditorField } from "~/components/form/fields/monacoEditor.field";
import { SubmitButton } from "~/components/form/submit";

export const MetadataField = () => {
const { metadata, setMetadata } = useRunPipeline();

return (
<Metadata>
<MetadataForm
defaultValue={JSON.stringify(metadata)}
onSubmit={setMetadata}
/>
</Metadata>
);
};

function Metadata({ children }: PropsWithChildren) {
const wrapperRef = useRef<HTMLDivElement>(null);
const { value: isShown, setFalse, toggle } = useBoolean(false);

const hide = () => {
setFalse();
};

useOnClickOutside(wrapperRef, hide);

return (
<div ref={wrapperRef} className="relative">
<MetadataTrigger onClick={toggle} />

{isShown && (
<MetadataDropdown className="lg:min-w-[400px]">
{children}
</MetadataDropdown>
)}
</div>
);
}

const metadataSchema = z.object({
value: z.string().transform((str, ctx) => {
try {
return JSON.parse(str);
} catch (e) {
ctx.addIssue({ code: "custom", message: "Invalid JSON" });
return z.NEVER;
}
}),
});

type MetadataSchema = z.TypeOf<typeof metadataSchema>;

interface MetadataFormProps {
defaultValue: string;
onSubmit: (value: IMetadata) => void;
}
function MetadataForm({ defaultValue, onSubmit }: MetadataFormProps) {
const validator = React.useMemo(() => withZod(metadataSchema), []);

const handleOnSubmit = (data: MetadataSchema) => {
if (!data.value) return onSubmit({});
onSubmit(data.value);
};

return (
<ValidatedForm
defaultValues={{ value: defaultValue }}
onSubmit={handleOnSubmit}
validator={validator}
noValidate
>
<Field name="value">
<MetadataEditor />
</Field>

<div className="flex justify-end w-full">
<SubmitButton size="xs">Set</SubmitButton>
</div>
</ValidatedForm>
);
}

function MetadataEditor() {
const { fieldErrors } = useFormContext();

return (
<MonacoEditorField
supportingText="Properties that will be accessible in every block."
language="json"
label="Metadata"
defaultValue=""
error={fieldErrors["value"]}
/>
);
}

function MetadataTrigger({
className,
...rest
}: ButtonHTMLAttributes<HTMLButtonElement>) {
return (
<button
className={classNames(
"bg-neutral-950 text-neutral-100 w-8 h-8 rounded-lg text-sm flex items-center justify-center hover:bg-neutral-900 transition",
className
)}
{...rest}
>
<Icon iconName="file-text" />
</button>
);
}

interface MetadataDropdownProps {
className?: string;
}

function MetadataDropdown({
children,

className,
}: PropsWithChildren<MetadataDropdownProps>) {
return (
<div
className={classNames(
"min-w-[250px] absolute z-[11] top-full translate-y-[4px] left-0 bg-neutral-850 border border-neutral-800 rounded-lg p-2 transition",

className
)}
>
{children}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback } from "react";
import { LinksFunction, MetaFunction } from "@remix-run/node";
import { MetaFunction } from "@remix-run/node";
import {
Outlet,
useFetcher,
Expand Down
14 changes: 8 additions & 6 deletions apps/web-remix/app/components/pages/pipelines/usePipelineRun.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { useEffect, useRef, useState } from "react";
import { assert } from "~/utils/assert";
import { BuildelRun, BuildelRunStatus, BuildelSocket } from "@buildel/buildel";
import {
BuildelRun,
BuildelRunStatus,
BuildelSocket,
BuildelRunStartArgs,
} from "@buildel/buildel";

export function usePipelineRun(
organizationId: number,
Expand All @@ -18,12 +23,9 @@ export function usePipelineRun(

const [status, setStatus] = useState<BuildelRunStatus>("idle");

const startRun = async (
initialInputs: { name: string; value: string }[] = [],
alias?: string
) => {
const startRun = async (args: BuildelRunStartArgs) => {
assert(run.current);
await run.current.start(initialInputs, alias);
await run.current.start(args);
};
const stopRun = async () => {
assert(run.current);
Expand Down
2 changes: 1 addition & 1 deletion apps/web-remix/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"typecheck": "tsc"
},
"dependencies": {
"@buildel/buildel": "^0.1.6",
"@buildel/buildel": "^0.2.1",
"@elpassion/taco": "^0.6.0",
"@fastify/early-hints": "^1.0.1",
"@fastify/http-proxy": "^9.4.0",
Expand Down
8 changes: 4 additions & 4 deletions apps/web-remix/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit fca9a30

Please sign in to comment.