vorteil/direktiv

View on GitHub
ui/src/pages/namespace/Explorer/Workflow/Edit/WorkflowEditor.tsx

Summary

Maintainability
A
25 mins
Test Coverage
import { Dialog, DialogContent, DialogTrigger } from "~/design/Dialog";
import { FC, useState } from "react";
import { Play, Save } from "lucide-react";
import { decode, encode } from "js-base64";
import {
  useSetUnsavedChanges,
  useUnsavedChanges,
} from "../store/unsavedChangesContext";

import Button from "~/design/Button";
import { CodeEditor } from "./CodeEditor";
import { Diagram } from "./Diagram";
import { EditorLayoutSwitcher } from "~/components/EditorLayoutSwitcher";
import { FileSchemaType } from "~/api/files/schema";
import RunWorkflow from "../components/RunWorkflow";
import { WorkspaceLayout } from "~/components/WorkspaceLayout";
import { useEditorLayout } from "~/util/store/editor";
import { useNamespace } from "~/util/store/namespace";
import { useNotifications } from "~/api/notifications/query/get";
import { useTranslation } from "react-i18next";
import { useUpdateFile } from "~/api/files/mutate/updateFile";

const WorkflowEditor: FC<{
  data: NonNullable<FileSchemaType>;
}> = ({ data }) => {
  const currentLayout = useEditorLayout();
  const { t } = useTranslation();
  const namespace = useNamespace();
  const [error, setError] = useState<string | undefined>();
  const { refetch: updateNotificationBell } = useNotifications();

  const hasUnsavedChanges = useUnsavedChanges();
  const setHasUnsavedChanges = useSetUnsavedChanges();

  const workflowDataFromServer = decode(data?.data ?? "");

  const { mutate: updateFile, isPending } = useUpdateFile({
    onError: (error) => {
      error && setError(error);
    },
    onSuccess: () => {
      /**
       * updating a workflow might introduce an uninitialized secret. We need
       * to update the notification bell, to see potential new messages.
       */
      updateNotificationBell();
      setHasUnsavedChanges(false);
    },
  });

  const [editorContent, setEditorContent] = useState(workflowDataFromServer);

  const onEditorContentUpdate = (newData: string) => {
    setHasUnsavedChanges(workflowDataFromServer !== newData);
    setEditorContent(newData ?? "");
  };

  const onSave = (toSave: string | undefined) => {
    if (toSave) {
      setError(undefined);
      updateFile({
        path: data.path,
        payload: { data: encode(toSave) },
      });
    }
  };

  if (!namespace) return null;

  return (
    <div className="relative flex grow flex-col space-y-4 p-5">
      <WorkspaceLayout
        layout={currentLayout}
        diagramComponent={
          <Diagram workflowData={editorContent} layout={currentLayout} />
        }
        editorComponent={
          <CodeEditor
            value={editorContent}
            onValueChange={onEditorContentUpdate}
            updatedAt={data.updatedAt}
            error={error}
            hasUnsavedChanges={hasUnsavedChanges}
            onSave={onSave}
          />
        }
      />

      <div className="flex flex-col justify-end gap-4 sm:flex-row sm:items-center">
        <EditorLayoutSwitcher />
        <Dialog>
          <DialogTrigger asChild>
            <Button
              variant="outline"
              data-testid="workflow-editor-btn-run"
              disabled={hasUnsavedChanges}
            >
              <Play />
              {t("pages.explorer.workflow.editor.runBtn")}
            </Button>
          </DialogTrigger>
          <DialogContent className="sm:max-w-2xl">
            <RunWorkflow path={data.path} />
          </DialogContent>
        </Dialog>
        <Button
          variant="outline"
          disabled={isPending}
          onClick={() => {
            onSave(editorContent);
          }}
          data-testid="workflow-editor-btn-save"
        >
          <Save />
          {t("pages.explorer.workflow.editor.saveBtn")}
        </Button>
      </div>
    </div>
  );
};

export default WorkflowEditor;