handleMousedown(event)}
- className={classes.dragger}
- />
-
- )}
- >
+ )}
+ >
+ );
}
diff --git a/ui/src/pages/execution/ExecutionInputOutput.jsx b/ui/src/pages/execution/ExecutionInputOutput.jsx
index 6663b8a2e5..65a6202dce 100644
--- a/ui/src/pages/execution/ExecutionInputOutput.jsx
+++ b/ui/src/pages/execution/ExecutionInputOutput.jsx
@@ -16,11 +16,11 @@ const useStyles = makeStyles({
gap: 15,
flex: 2,
marginBottom: 15,
- overflow: "hidden"
+ overflow: "hidden",
},
paper: {
flex: 1,
- overflow: "hidden"
+ overflow: "hidden",
},
});
diff --git a/ui/src/pages/execution/ExecutionJson.jsx b/ui/src/pages/execution/ExecutionJson.jsx
index 34d8ed6ca4..85eb0f4905 100644
--- a/ui/src/pages/execution/ExecutionJson.jsx
+++ b/ui/src/pages/execution/ExecutionJson.jsx
@@ -4,15 +4,15 @@ import ReactJson from "../../components/ReactJson";
import { makeStyles } from "@material-ui/styles";
const useStyles = makeStyles({
- paper: {
+ paper: {
margin: 30,
- flex: 1
+ flex: 1,
},
wrapper: {
flex: 1,
display: "flex",
- flexDirection: "column"
- }
+ flexDirection: "column",
+ },
});
export default function ExecutionJson({ execution }) {
@@ -20,9 +20,9 @@ export default function ExecutionJson({ execution }) {
return (
);
}
diff --git a/ui/src/pages/execution/ExecutionSummary.jsx b/ui/src/pages/execution/ExecutionSummary.jsx
index b04c506d47..37ebeacd50 100644
--- a/ui/src/pages/execution/ExecutionSummary.jsx
+++ b/ui/src/pages/execution/ExecutionSummary.jsx
@@ -4,11 +4,11 @@ import { makeStyles } from "@material-ui/styles";
const useStyles = makeStyles({
paper: {
- margin: 30
+ margin: 30,
},
wrapper: {
- overflowY: "auto"
- }
+ overflowY: "auto",
+ },
});
export default function ExecutionSummary({ execution }) {
@@ -55,9 +55,9 @@ export default function ExecutionSummary({ execution }) {
return (
);
}
diff --git a/ui/src/pages/execution/RightPanel.jsx b/ui/src/pages/execution/RightPanel.jsx
index 32298a92dd..4a7f6a8e07 100644
--- a/ui/src/pages/execution/RightPanel.jsx
+++ b/ui/src/pages/execution/RightPanel.jsx
@@ -17,7 +17,7 @@ const useStyles = makeStyles({
},
tabContent: {
flex: 1,
- overflowY: 'auto'
+ overflowY: "auto",
},
});
@@ -115,10 +115,7 @@ export default function RightPanel({ selectedTask, dag, onTaskChange }) {
{tabIndex === 0 &&
}
{tabIndex === 1 && (
-
+
)}
{tabIndex === 2 && (
<>
@@ -129,10 +126,7 @@ export default function RightPanel({ selectedTask, dag, onTaskChange }) {
location.
)}
-
+
>
)}
{tabIndex === 3 &&
}
diff --git a/ui/src/pages/execution/TaskDetails.jsx b/ui/src/pages/execution/TaskDetails.jsx
index d3fbee4fdf..379dd23f70 100644
--- a/ui/src/pages/execution/TaskDetails.jsx
+++ b/ui/src/pages/execution/TaskDetails.jsx
@@ -7,10 +7,10 @@ import { makeStyles } from "@material-ui/styles";
const useStyles = makeStyles({
taskWrapper: {
- overflowY: 'auto',
+ overflowY: "auto",
padding: 30,
- height: '100%'
- }
+ height: "100%",
+ },
});
export default function TaskDetails({
@@ -24,38 +24,38 @@ export default function TaskDetails({
return (
-
-
- setTabIndex(0)} />
- setTabIndex(1)} />
- setTabIndex(2)} />
-
+
+
+ setTabIndex(0)} />
+ setTabIndex(1)} />
+ setTabIndex(2)} />
+
- {tabIndex === 0 && (
-
- )}
- {tabIndex === 1 && (
-
- )}
- {tabIndex === 2 && (
-
- )}
-
+ {tabIndex === 0 && (
+
+ )}
+ {tabIndex === 1 && (
+
+ )}
+ {tabIndex === 2 && (
+
+ )}
+
);
}
diff --git a/ui/src/pages/workbench/ExecutionHistory.jsx b/ui/src/pages/workbench/ExecutionHistory.jsx
new file mode 100644
index 0000000000..90a8acaf8c
--- /dev/null
+++ b/ui/src/pages/workbench/ExecutionHistory.jsx
@@ -0,0 +1,91 @@
+import {
+ List,
+ ListItem,
+ ListItemText,
+ Toolbar,
+ IconButton,
+} from "@material-ui/core";
+import { StatusBadge, Text, NavLink } from "../../components";
+import { makeStyles } from "@material-ui/styles";
+import { colors } from "../../theme/variables";
+import _ from "lodash";
+import { useInvalidateWorkflows, useWorkflows } from "../../data/workflow";
+import { formatRelative } from "date-fns";
+import RefreshIcon from "@material-ui/icons/Refresh";
+
+const useStyles = makeStyles({
+ sidebar: {
+ width: 360,
+ border: "0px solid rgba(0, 0, 0, 0)",
+ zIndex: 1,
+ boxShadow: "0 2px 4px 0 rgb(0 0 0 / 10%), 0 0 2px 0 rgb(0 0 0 / 10%)",
+ background: "#fff",
+ display: "flex",
+ flexDirection: "column",
+ },
+ toolbar: {
+ backgroundColor: colors.gray14,
+ },
+ list: {
+ overflowY: "auto",
+ flex: 1,
+ },
+});
+
+export default function ExecutionHistory({ run }) {
+ const classes = useStyles();
+ const workflowRecords = run ? run.workflowRecords : [];
+ const workflowIds = workflowRecords.map((record) => `${record.workflowId}`);
+ const results =
+ useWorkflows(workflowIds, {
+ staleTime: 60000,
+ }) || [];
+ const resultsMap = new Map(
+ results
+ .filter((r) => r.isSuccess)
+ .map((result) => [result.data.workflowId, result.data])
+ );
+ const invalidateWorkflows = useInvalidateWorkflows();
+
+ function handleRefresh() {
+ invalidateWorkflows(workflowIds);
+ }
+
+ return (
+
+
+
+ Execution History
+
+
+
+
+
+
+ {Array.from(resultsMap.values()).map((workflow) => (
+
+
+ {workflow.workflowId}
+
+ }
+ secondary={
+
+ {" "}
+ {formatRelative(new Date(workflow.startTime), new Date())}
+
+ }
+ secondaryTypographyProps={{ component: "div" }}
+ />
+
+ ))}
+ {_.isEmpty(workflowRecords) && (
+
+ No execution history.
+
+ )}
+
+
+ );
+}
diff --git a/ui/src/pages/workbench/RunHistory.tsx b/ui/src/pages/workbench/RunHistory.tsx
new file mode 100644
index 0000000000..9ae149604e
--- /dev/null
+++ b/ui/src/pages/workbench/RunHistory.tsx
@@ -0,0 +1,167 @@
+import { useImperativeHandle, useState, forwardRef } from "react";
+import { useLocalStorage } from "../../utils/localstorage";
+import { Text } from "../../components";
+import {
+ List,
+ ListItem,
+ ListItemText,
+ ListItemSecondaryAction,
+ Toolbar,
+ IconButton,
+} from "@material-ui/core";
+import { makeStyles } from "@material-ui/styles";
+import { immutableReplaceAt } from "../../utils/helpers";
+import { formatRelative } from "date-fns";
+import DeleteIcon from "@material-ui/icons/DeleteForever";
+import { colors } from "../../theme/variables";
+import CloseIcon from "@material-ui/icons/Close";
+import _ from "lodash";
+import { useEnv } from "../../plugins/env";
+
+const useStyles = makeStyles({
+ sidebar: {
+ width: 300,
+ border: "0px solid rgba(0, 0, 0, 0)",
+ zIndex: 1,
+ boxShadow: "0 2px 4px 0 rgb(0 0 0 / 10%), 0 0 2px 0 rgb(0 0 0 / 10%)",
+ background: "#fff",
+ display: "flex",
+ flexDirection: "column",
+ },
+ toolbar: {
+ backgroundColor: colors.gray14,
+ },
+ title: {
+ fontWeight: "bold",
+ flex: 1,
+ },
+ list: {
+ overflowY: "auto",
+ cursor: "pointer",
+ flex: 1,
+ },
+});
+type RunPayload = any;
+type RunEntry = {
+ runPayload: RunPayload;
+ workflowRecords: WorkflowRecord[];
+ createTime: number;
+};
+type WorkflowRecord = {
+ workflowId: string;
+};
+
+type RunHistoryProps = {
+ onRunSelected: (run: RunEntry | undefined) => void;
+};
+
+const RUN_HISTORY_SCHEMA_VER = 1;
+
+const RunHistory = forwardRef((props: RunHistoryProps, ref) => {
+ const { onRunSelected } = props;
+ const { stack } = useEnv();
+ const classes = useStyles();
+ const [selectedCreateTime, setSelectedCreateTime] = useState<
+ number | undefined
+ >(undefined);
+ const [runHistory, setRunHistory]: readonly [
+ RunEntry[],
+ (v: RunEntry[]) => void
+ ] = useLocalStorage(`runHistory_${stack}_${RUN_HISTORY_SCHEMA_VER}`, []);
+
+ useImperativeHandle(ref, () => ({
+ pushNewRun: (runPayload: RunPayload) => {
+ const createTime = new Date().getTime();
+ const newRun = {
+ runPayload: runPayload,
+ workflowRecords: [],
+ createTime: createTime,
+ };
+ setRunHistory([newRun, ...runHistory]);
+ setSelectedCreateTime(createTime);
+
+ return newRun;
+ },
+ updateRun: (createTime: number, workflowId: string) => {
+ console.log("updating run", createTime, workflowId);
+
+ const idx = runHistory.findIndex((v) => v.createTime === createTime);
+ const currRun = runHistory[idx];
+ const oldRecords = currRun.workflowRecords;
+ const updatedRun = {
+ runPayload: currRun.runPayload,
+ workflowRecords: [
+ {
+ workflowId: workflowId,
+ },
+ ...oldRecords,
+ ],
+ createTime: currRun.createTime,
+ };
+
+ setRunHistory(immutableReplaceAt(runHistory, idx, updatedRun));
+ onRunSelected(updatedRun);
+ },
+ }));
+
+ function handleSelectRun(run: RunEntry) {
+ if (onRunSelected) onRunSelected(run);
+ setSelectedCreateTime(run.createTime);
+ }
+
+ function handleDeleteAll() {
+ if (window.confirm("Delete all run history in this browser?")) {
+ setRunHistory([]);
+ }
+ }
+
+ function handleDeleteItem(run: RunEntry) {
+ const newHistory = runHistory.filter(
+ (v) => v.createTime !== run.createTime
+ );
+ if (newHistory.length > 0) {
+ setSelectedCreateTime(newHistory[0].createTime);
+ onRunSelected(newHistory[0]);
+ } else {
+ console.log("Empty history");
+ setSelectedCreateTime(undefined);
+ onRunSelected(undefined);
+ }
+ setRunHistory(newHistory);
+ }
+
+ return (
+
+
+
+ Run History
+
+
+
+
+
+
+ {runHistory.map((run) => (
+ handleSelectRun(run)}
+ >
+
+
+ handleDeleteItem(run)}>
+
+
+
+
+ ))}
+ {_.isEmpty(runHistory) && No saved runs.}
+
+
+ );
+});
+
+export default RunHistory;
diff --git a/ui/src/pages/workbench/Workbench.jsx b/ui/src/pages/workbench/Workbench.jsx
new file mode 100644
index 0000000000..17e58d0dc0
--- /dev/null
+++ b/ui/src/pages/workbench/Workbench.jsx
@@ -0,0 +1,98 @@
+import { useState, useRef } from "react";
+import { makeStyles } from "@material-ui/styles";
+import { Helmet } from "react-helmet";
+import RunHistory from "./RunHistory";
+import WorkbenchForm from "./WorkbenchForm";
+import { colors } from "../../theme/variables";
+import { useStartWorkflow } from "../../data/workflow";
+import ExecutionHistory from "./ExecutionHistory";
+
+const useStyles = makeStyles({
+ wrapper: {
+ height: "100%",
+ overflow: "hidden",
+ display: "flex",
+ flexDirection: "row",
+ position: "relative",
+ },
+ name: {
+ width: "50%",
+ },
+ submitButton: {
+ float: "right",
+ },
+ toolbar: {
+ backgroundColor: colors.gray14,
+ },
+ workflowName: {
+ fontWeight: "bold",
+ },
+ main: {
+ flex: 1,
+ display: "flex",
+ flexDirection: "column",
+ },
+ row: {
+ display: "flex",
+ flexDirection: "row",
+ },
+ fields: {
+ margin: 30,
+ flex: 1,
+ display: "flex",
+ flexDirection: "column",
+ gap: 15,
+ },
+ runInfo: {
+ marginLeft: -350,
+ },
+});
+
+export default function Workbench() {
+ const classes = useStyles();
+
+ const runHistoryRef = useRef();
+ const [run, setRun] = useState(undefined);
+
+ const { mutate: startWorkflow } = useStartWorkflow({
+ onSuccess: (workflowId, variables) => {
+ runHistoryRef.current.updateRun(variables.createTime, workflowId);
+ },
+ });
+
+ const handleRunSelect = (run) => {
+ setRun(run);
+ };
+
+ const handleSaveRun = (runPayload) => {
+ const newRun = runHistoryRef.current.pushNewRun(runPayload);
+ setRun(newRun);
+ return newRun;
+ };
+
+ const handleExecuteRun = (createTime, runPayload) => {
+ startWorkflow({
+ createTime,
+ body: runPayload,
+ });
+ };
+
+ return (
+ <>
+
+ Conductor UI - Workbench
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/ui/src/pages/workbench/WorkbenchForm.jsx b/ui/src/pages/workbench/WorkbenchForm.jsx
new file mode 100644
index 0000000000..c723b4338f
--- /dev/null
+++ b/ui/src/pages/workbench/WorkbenchForm.jsx
@@ -0,0 +1,245 @@
+import { useMemo } from "react";
+import { Text, Pill } from "../../components";
+import { Toolbar, IconButton } from "@material-ui/core";
+import FormikInput from "../../components/formik/FormikInput";
+import FormikJsonInput from "../../components/formik/FormikJsonInput";
+import FormikDropdown from "../../components/formik/FormikDropdown";
+import { makeStyles } from "@material-ui/styles";
+import _ from "lodash";
+import { Form, setNestedObjectValues, withFormik } from "formik";
+import {
+ useWorkflowNamesAndVersions,
+ useWorkflowDef,
+} from "../../data/workflow";
+import FormikVersionDropdown from "../../components/formik/FormikVersionDropdown";
+import PlayArrowIcon from "@material-ui/icons/PlayArrow";
+import PlaylistAddIcon from "@material-ui/icons/PlaylistAdd";
+import SaveIcon from "@material-ui/icons/Save";
+import { colors } from "../../theme/variables";
+import { timestampRenderer } from "../../utils/helpers";
+import * as Yup from "yup";
+
+const useStyles = makeStyles({
+ name: {
+ width: "50%",
+ },
+ submitButton: {
+ float: "right",
+ },
+ toolbar: {
+ backgroundColor: colors.gray14,
+ },
+ workflowName: {
+ fontWeight: "bold",
+ },
+ main: {
+ flex: 1,
+ display: "flex",
+ flexDirection: "column",
+ overflow: "auto",
+ },
+ fields: {
+ width: "100%",
+ padding: 30,
+ flex: 1,
+ display: "flex",
+ flexDirection: "column",
+ overflowX: "hidden",
+ overflowY: "auto",
+ gap: 15,
+ },
+});
+
+Yup.addMethod(Yup.string, "isJson", function () {
+ return this.test("is-json", "is not valid json", (value) => {
+ if (_.isEmpty(value)) return true;
+
+ try {
+ JSON.parse(value);
+ } catch (e) {
+ return false;
+ }
+ return true;
+ });
+});
+const validationSchema = Yup.object({
+ workflowName: Yup.string().required("Workflow Name is required"),
+ workflowInput: Yup.string().isJson(),
+ taskToDomain: Yup.string().isJson(),
+});
+
+export default withFormik({
+ enableReinitialize: true,
+ mapPropsToValues: ({ selectedRun }) =>
+ runPayloadToFormData(_.get(selectedRun, "runPayload")),
+ validationSchema: validationSchema,
+})(WorkbenchForm);
+
+function WorkbenchForm(props) {
+ const {
+ values,
+ validateForm,
+ setTouched,
+ setFieldValue,
+ dirty,
+ selectedRun,
+ saveRun,
+ executeRun,
+ } = props;
+ const classes = useStyles();
+ const { workflowName, workflowVersion } = values;
+ const createTime = selectedRun ? selectedRun.createTime : undefined;
+
+ const { data: namesAndVersions } = useWorkflowNamesAndVersions();
+ const workflowNames = useMemo(
+ () => (namesAndVersions ? Array.from(namesAndVersions.keys()) : []),
+ [namesAndVersions]
+ );
+ const { refetch } = useWorkflowDef(workflowName, workflowVersion, null, {
+ onSuccess: populateInput,
+ enabled: false,
+ });
+
+ function triggerPopulateInput() {
+ refetch();
+ }
+
+ function populateInput(workflowDef) {
+ let bootstrap = {};
+
+ if (!_.isEmpty(values.workflowInput)) {
+ const existing = JSON.parse(values.workflowInput);
+ bootstrap = _.pickBy(existing, (v) => v !== "");
+ }
+
+ if (workflowDef.inputParameters) {
+ for (let param of workflowDef.inputParameters) {
+ if (!_.has(bootstrap, param)) {
+ bootstrap[param] = "";
+ }
+ }
+
+ setFieldValue("workflowInput", JSON.stringify(bootstrap, null, 2));
+ }
+ }
+
+ function handleRun() {
+ validateForm().then((errors) => {
+ if (Object.keys(errors).length === 0) {
+ const payload = formDataToRunPayload(values);
+ if (!dirty && createTime) {
+ console.log("Executing pre-existing run. Append workflowRecord");
+ executeRun(createTime, payload);
+ } else {
+ console.log("Executing new run. Save first then execute");
+ const newRun = saveRun(payload);
+ executeRun(newRun.createTime, payload);
+ }
+ } else {
+ // Handle validation error manually (not using handleSubmit)
+ setTouched(setNestedObjectValues(errors, true));
+ }
+ });
+ }
+
+ function handleSave() {
+ validateForm().then((errors) => {
+ if (Object.keys(errors).length === 0) {
+ const payload = formDataToRunPayload(values);
+ console.log(payload);
+ saveRun(payload);
+ } else {
+ setTouched(setNestedObjectValues(errors, true));
+ }
+ });
+ }
+
+ return (
+
+ );
+}
+
+function runPayloadToFormData(runPayload) {
+ return {
+ workflowName: _.get(runPayload, "name", ""),
+ workflowVersion: _.get(runPayload, "version", ""),
+ workflowInput: _.has(runPayload, "input")
+ ? JSON.stringify(runPayload.input, null, 2)
+ : "",
+ correlationId: _.get(runPayload, "correlationId", ""),
+ taskToDomain: _.has(runPayload, "taskToDomain")
+ ? JSON.stringify(runPayload.taskToDomain, null, 2)
+ : "",
+ };
+}
+
+function formDataToRunPayload(form) {
+ let runPayload = {
+ name: form.workflowName,
+ };
+ if (form.workflowVersion) {
+ runPayload.version = form.workflowVersion;
+ }
+ if (form.workflowInput) {
+ runPayload.input = JSON.parse(form.workflowInput);
+ }
+ if (form.correlationId) {
+ runPayload.correlationId = form.correlationId;
+ }
+ if (form.taskToDomain) {
+ runPayload.taskToDomain = JSON.parse(form.taskToDomain);
+ }
+ return runPayload;
+}
+
+// runHistoryRef.current.pushRun(runPayload);
diff --git a/ui/src/plugins/fetch.js b/ui/src/plugins/fetch.js
index 0ac96fe1a2..2a15b41c2d 100644
--- a/ui/src/plugins/fetch.js
+++ b/ui/src/plugins/fetch.js
@@ -7,7 +7,7 @@ export function useFetchContext() {
ready: true,
};
}
-export function fetchWithContext(path, context, fetchParams) {
+export function fetchWithContext(path, context, fetchParams, isJsonResponse) {
const newParams = { ...fetchParams };
const newPath = `/api/${path}`;
@@ -22,6 +22,8 @@ export function fetchWithContext(path, context, fetchParams) {
return Promise.reject(error);
} else if (!text || text.length === 0) {
return null;
+ } else if (!isJsonResponse) {
+ return text;
} else {
try {
return JSON.parse(text);
diff --git a/ui/src/react-app-env.d.ts b/ui/src/react-app-env.d.ts
new file mode 100644
index 0000000000..6431bc5fc6
--- /dev/null
+++ b/ui/src/react-app-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/ui/src/theme/theme.js b/ui/src/theme/theme.js
index 697c34169c..b86795b1da 100644
--- a/ui/src/theme/theme.js
+++ b/ui/src/theme/theme.js
@@ -196,6 +196,9 @@ const overrides = {
root: {
fontSize: fontSizes.fontSize6,
},
+ fontSizeSmall: {
+ fontSize: fontSizes.fontSize1,
+ },
},
MuiAvatar: {
root: {
@@ -297,6 +300,10 @@ const overrides = {
paddingLeft: baseTheme.spacing("space1"),
paddingRight: baseTheme.spacing("space1"),
},
+ sizeSmall: {
+ fontSize: fontSizes.fontSize0,
+ height: 20,
+ },
deleteIcon: {
height: "100%",
padding: 3,
@@ -506,8 +513,8 @@ const overrides = {
height: 4,
},
root: {
- minHeight: 0
- }
+ minHeight: 0,
+ },
},
MuiListItemText: {
secondary: {
@@ -584,7 +591,7 @@ const overrides = {
color: colors.gray00,
},
root: {
- zIndex: 0,
+ zIndex: 999,
paddingLeft: 20,
paddingRight: 20,
boxShadow: "0 4px 8px 0 rgb(0 0 0 / 10%), 0 0 2px 0 rgb(0 0 0 / 10%)",
diff --git a/ui/src/utils/helpers.js b/ui/src/utils/helpers.js
index a1ef51f4c4..5682a52de4 100644
--- a/ui/src/utils/helpers.js
+++ b/ui/src/utils/helpers.js
@@ -66,3 +66,9 @@ export function defaultCompare(x, y) {
return 0;
}
+
+export function immutableReplaceAt(array, index, value) {
+ const ret = array.slice(0);
+ ret[index] = value;
+ return ret;
+}
diff --git a/ui/src/utils/localstorage.ts b/ui/src/utils/localstorage.ts
new file mode 100644
index 0000000000..3d7e785599
--- /dev/null
+++ b/ui/src/utils/localstorage.ts
@@ -0,0 +1,34 @@
+import { useState } from "react";
+
+// If key is null/undefined, hook behaves exactly like useState
+export const useLocalStorage = (key: string, initialValue: any) => {
+ const initialString = JSON.stringify(initialValue);
+
+ const [storedValue, setStoredValue] = useState(() => {
+ if (key) {
+ const item = window.localStorage.getItem(key);
+ return item ? JSON.parse(item) : initialValue;
+ } else {
+ return initialValue;
+ }
+ });
+
+ const setValue = (value: any) => {
+ // Allow value to be a function so we have same API as useState
+ const valueToStore = value instanceof Function ? value(storedValue) : value;
+
+ // Save state
+ setStoredValue(valueToStore);
+
+ if (key) {
+ const stringToStore = JSON.stringify(valueToStore);
+ if (stringToStore === initialString) {
+ window.localStorage.removeItem(key);
+ } else {
+ window.localStorage.setItem(key, stringToStore);
+ }
+ }
+ };
+
+ return [storedValue, setValue] as const;
+};
diff --git a/ui/tsconfig.json b/ui/tsconfig.json
new file mode 100644
index 0000000000..9d379a3c4a
--- /dev/null
+++ b/ui/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src"]
+}
diff --git a/ui/yarn.lock b/ui/yarn.lock
index 3d9cefa80e..5ba8a393af 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -13330,6 +13330,11 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
+typescript@^4.6.3:
+ version "4.6.3"
+ resolved "https://artifacts.netflix.net/api/npm/npm-netflix/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c"
+ integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==
+
unbox-primitive@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"