From 7b80691f4ab1251ca50f2ea7b89d6409bb4b14b1 Mon Sep 17 00:00:00 2001 From: shamsmosowi Date: Fri, 4 Mar 2022 09:54:37 +0800 Subject: [PATCH] new function wrappers with defs --- src/components/CodeEditor/rowy.d.ts | 6 -- .../CodeEditor/useMonacoCustomizations.ts | 48 ++++++++--- .../FieldSettings/DefaultValueInput.tsx | 48 ++++++++++- .../FieldSettings/defaultValue.d.ts | 8 ++ src/components/Table/ContextMenu/index.tsx | 1 - src/components/fields/Action/Settings.tsx | 86 +++++++++++++++++-- src/components/fields/Action/action.d.ts | 26 ++++++ .../fields/Derivative/ContextMenuActions.tsx | 1 + src/components/fields/Derivative/Settings.tsx | 22 +++-- .../Derivative}/derivative.d.ts | 2 +- src/components/fields/Image/index.tsx | 3 +- .../BasicCellContextMenuActions.tsx | 1 + src/constants/runRoutes.ts | 1 + src/utils/rowyRun.ts | 2 +- 14 files changed, 217 insertions(+), 38 deletions(-) create mode 100644 src/components/Table/ColumnMenu/FieldSettings/defaultValue.d.ts create mode 100644 src/components/fields/Action/action.d.ts rename src/components/{CodeEditor => fields/Derivative}/derivative.d.ts (72%) diff --git a/src/components/CodeEditor/rowy.d.ts b/src/components/CodeEditor/rowy.d.ts index cd2bbdc2..86d7b144 100644 --- a/src/components/CodeEditor/rowy.d.ts +++ b/src/components/CodeEditor/rowy.d.ts @@ -96,9 +96,3 @@ interface Rowy { } declare const rowy: Rowy; -type SecretNames = "sendgrid" | "unsplash" | "twilio"; -enum secrets { - sendgrid = "sendgrid", - unsplash = "unsplash", - twilio = "twilio", -} diff --git a/src/components/CodeEditor/useMonacoCustomizations.ts b/src/components/CodeEditor/useMonacoCustomizations.ts index 1d8999e5..b1a9dce9 100644 --- a/src/components/CodeEditor/useMonacoCustomizations.ts +++ b/src/components/CodeEditor/useMonacoCustomizations.ts @@ -23,7 +23,8 @@ import firebaseStorageDefs from "!!raw-loader!./firebaseStorage.d.ts"; import utilsDefs from "!!raw-loader!./utils.d.ts"; import rowyUtilsDefs from "!!raw-loader!./rowy.d.ts"; import extensionsDefs from "!!raw-loader!./extensions.d.ts"; -import derivativeDefs from "!!raw-loader!./derivative.d.ts"; +import defaultValueDefs from "!!raw-loader!./defaultValue.d.ts"; +import { runRoutes } from "@src/constants/runRoutes"; export interface IUseMonacoCustomizationsProps { minHeight?: number; @@ -53,7 +54,7 @@ export default function useMonacoCustomizations({ fullScreen, }: IUseMonacoCustomizationsProps) { const theme = useTheme(); - const { tableState } = useProjectContext(); + const { tableState, rowyRun } = useProjectContext(); const monaco = useMonaco(); @@ -104,9 +105,6 @@ export default function useMonacoCustomizations({ "ts:filename/utils.d.ts" ); monaco.languages.typescript.javascriptDefaults.addExtraLib(rowyUtilsDefs); - monaco.languages.typescript.javascriptDefaults.addExtraLib( - derivativeDefs - ); } catch (error) { console.error( "An error occurred during initialization of Monaco: ", @@ -144,7 +142,6 @@ export default function useMonacoCustomizations({ }, [monaco, stringifiedDiagnosticsOptions]); const addJsonFieldDefinition = async (columnKey, interfaceName) => { - // add delay const samples = tableState?.rows .map((row) => row[columnKey]) .filter((entry) => entry !== undefined) @@ -168,19 +165,38 @@ export default function useMonacoCustomizations({ rendererOptions: { "just-types": "true" }, }); const newLib = result.lines.join("\n").replaceAll("export ", ""); - console.log(newLib); + // console.log(newLib); monaco?.languages.typescript.javascriptDefaults.addExtraLib(newLib); } }; + + const setSecrets = async (monaco, rowyRun) => { + // set secret options + try { + const listSecrets = await rowyRun({ + route: runRoutes.listSecrets, + }); + const secretsDef = `type SecretNames = ${listSecrets + .map((secret) => `"${secret}"`) + .join(" | ")} + enum secrets { + ${listSecrets.map((secret) => `${secret} = "${secret}"`).join("\n")} + } + `; + monaco.languages.typescript.javascriptDefaults.addExtraLib(secretsDef); + } catch (error) { + console.error("Could not set secret definitions: ", error); + } + }; // Set row definitions useEffect(() => { - if (!monaco) return; - + if (!monaco || !rowyRun || !tableState?.columns) return; + console.log("setting row definitions"); try { const rowDefinition = - Object.keys(tableState?.columns!) + Object.keys(tableState.columns) .map((columnKey: string) => { - const column = tableState?.columns[columnKey]; + const column = tableState.columns[columnKey]; if (getColumnType(column) === "JSON") { const interfaceName = columnKey[0].toUpperCase() + columnKey.slice(1); @@ -194,7 +210,7 @@ export default function useMonacoCustomizations({ }) .join(";\n") + ";"; - const availableFields = Object.keys(tableState?.columns!) + const availableFields = Object.keys(tableState.columns) .map((columnKey: string) => `"${columnKey}"`) .join("|\n"); @@ -232,7 +248,13 @@ export default function useMonacoCustomizations({ } catch (error) { console.error("Could not set row definitions: ", error); } - }, [monaco, tableState?.columns]); + // set available secrets from secretManager + try { + setSecrets(monaco, rowyRun); + } catch (error) { + console.error("Could not set secrets: ", error); + } + }, [monaco, tableState?.columns, rowyRun]); let boxSx: SystemStyleObject = { minWidth: 400, diff --git a/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx b/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx index 78e74b71..730bad8e 100644 --- a/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx +++ b/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx @@ -13,16 +13,54 @@ import FormAutosave from "./FormAutosave"; import { FieldType } from "@src/constants/fields"; import { WIKI_LINKS } from "@src/constants/externalLinks"; import { name } from "@root/package.json"; - -const CodeEditor = lazy( +/* eslint-disable import/no-webpack-loader-syntax */ +import defaultValueDefs from "!!raw-loader!./defaultValue.d.ts"; +const _CodeEditor = lazy( () => import("@src/components/CodeEditor" /* webpackChunkName: "CodeEditor" */) ); +const diagnosticsOptions = { + noSemanticValidation: false, + noSyntaxValidation: false, + noSuggestionDiagnostics: true, +}; + export interface IDefaultValueInputProps extends IMenuModalProps { handleChange: (key: any) => (update: any) => void; } +const CodeEditor = ({ type, config, handleChange }) => { + const returnType = getFieldProp("dataType", type) ?? "any"; + const defaultValueFn = config.defaultValue?.defaultValueFn + ? config.defaultValue?.defaultValueFn + : config.defaultValue?.script + ? `const defaultValueFn : DefaultValue = async ({row,ref,db,storage,auth})=>{ + ${config.defaultValue.script} + }` + : `const defaultValueFn: DefaultValue = async ({row,ref,db,storage,auth})=>{ + // Write your default value code here + // for example: + // generate random hex color + // const color = "#" + Math.floor(Math.random() * 16777215).toString(16); + // return color; + // checkout the documentation for more info: https://docs.rowy.io/how-to/default-values#dynamic + }`; + return ( + <_CodeEditor + value={defaultValueFn} + diagnosticsOptions={diagnosticsOptions} + extraLibs={[ + defaultValueDefs.replace( + `"PLACEHOLDER_OUTPUT_TYPE"`, + `${returnType} | Promise<${returnType}>` + ), + ]} + onChange={handleChange("defaultValue.defaultValueFn")} + /> + ); +}; + export default function DefaultValueInput({ config, handleChange, @@ -42,6 +80,7 @@ export default function DefaultValueInput({ config.defaultValue?.value ?? getFieldProp("initialValue", _type), }, }); + return ( <> }> diff --git a/src/components/Table/ColumnMenu/FieldSettings/defaultValue.d.ts b/src/components/Table/ColumnMenu/FieldSettings/defaultValue.d.ts new file mode 100644 index 00000000..0e50bb2d --- /dev/null +++ b/src/components/Table/ColumnMenu/FieldSettings/defaultValue.d.ts @@ -0,0 +1,8 @@ +type DefaultValueContext = { + row: Row; + ref: FirebaseFirestore.DocumentReference; + storage: firebasestorage.Storage; + db: FirebaseFirestore.Firestore; + auth: firebaseauth.BaseAuth; +}; +type DefaultValue = (context: DefaultValueContext) => "PLACEHOLDER_OUTPUT_TYPE"; diff --git a/src/components/Table/ContextMenu/index.tsx b/src/components/Table/ContextMenu/index.tsx index 38f1b6e8..620b27be 100644 --- a/src/components/Table/ContextMenu/index.tsx +++ b/src/components/Table/ContextMenu/index.tsx @@ -14,7 +14,6 @@ export default function ContextMenu() { const configActions = getFieldProp("contextMenuActions", selectedColumn.type) || function empty() {}; - console.log(configActions); const actions = configActions(selectedCell, resetContextMenu) || []; if (!anchorEle || actions.length === 0) return <>; diff --git a/src/components/fields/Action/Settings.tsx b/src/components/fields/Action/Settings.tsx index 2e8d46f8..a776efff 100644 --- a/src/components/fields/Action/Settings.tsx +++ b/src/components/fields/Action/Settings.tsx @@ -32,6 +32,14 @@ import FormFieldSnippets from "./FormFieldSnippets"; import { useProjectContext } from "@src/contexts/ProjectContext"; import { WIKI_LINKS } from "@src/constants/externalLinks"; import { useAppContext } from "@src/contexts/AppContext"; +/* eslint-disable import/no-webpack-loader-syntax */ +import actionDefs from "!!raw-loader!./action.d.ts"; + +const diagnosticsOptions = { + noSemanticValidation: false, + noSyntaxValidation: false, + noSuggestionDiagnostics: true, +}; const CodeEditor = lazy( () => @@ -62,7 +70,7 @@ const Settings = ({ config, onChange }) => { const scriptExtraLibs = [ [ - "declare class actionParams {", + "declare class ActionParams {", " /**", " * actionParams are provided by dialog popup form", " */", @@ -76,6 +84,7 @@ const Settings = ({ config, onChange }) => { }), "}", ].join("\n"), + actionDefs, ]; // Backwards-compatibility: previously user could set `confirmation` without @@ -86,6 +95,72 @@ const Settings = ({ config, onChange }) => { typeof config.confirmation === "string" && config.confirmation !== ""); + const runFn = config.runFn + ? config.derivativeFn + : config?.script + ? `const action:Action = async ({row,ref,db,storage,auth,actionParams,user}) => { + ${config.script.replace(/utilFns.getSecret/g, "rowy.secrets.getSecret")} + }` + : `const action:Action = async ({row,ref,db,storage,auth,actionParams,user}) => { + // Write your action code here + // for example: + // const authToken = await rowy.secrets.getSecret("service") + // try { + // const resp = await fetch('https://example.com/api/v1/users/'+ref.id,{ + // method: 'PUT', + // headers: { + // 'Content-Type': 'application/json', + // 'Authorization': authToken + // }, + // body: JSON.stringify(row) + // }) + // + // return { + // success: true, + // message: 'User updated successfully on example service', + // status: "upto date" + // } + // } catch (error) { + // return { + // success: false, + // message: 'User update failed on example service', + // } + // } + // checkout the documentation for more info: https://docs.rowy.io/field-types/action#script + }`; + + const undoFn = config.undoFn + ? config.undoFn + : _get(config, "undo.script") + ? `const action:Action = async ({row,ref,db,storage,auth,actionParams,user}) => { + ${_get(config, "undo.script")} + }` + : `const action:Action = async ({row,ref,db,storage,auth,actionParams,user}) => { + // Write your undo code here + // for example: + // const authToken = await rowy.secrets.getSecret("service") + // try { + // const resp = await fetch('https://example.com/api/v1/users/'+ref.id,{ + // method: 'DELETE', + // headers: { + // 'Content-Type': 'application/json', + // 'Authorization': authToken + // }, + // body: JSON.stringify(row) + // }) + // + // return { + // success: true, + // message: 'User deleted successfully on example service', + // status: null + // } + // } catch (error) { + // return { + // success: false, + // message: 'User delete failed on example service', + // } + // } + }`; return ( { }> { Undo script }> diff --git a/src/components/fields/Action/action.d.ts b/src/components/fields/Action/action.d.ts new file mode 100644 index 00000000..82ecf5e9 --- /dev/null +++ b/src/components/fields/Action/action.d.ts @@ -0,0 +1,26 @@ +type ActionUser = { + timestamp: Date; + displayName: string; + email: string; + uid: string; + emailVerified: boolean; + photoURL: string; + roles: string[]; +}; +type ActionContext = { + row: Row; + ref: FirebaseFirestore.DocumentReference; + storage: firebasestorage.Storage; + db: FirebaseFirestore.Firestore; + auth: firebaseauth.BaseAuth; + actionParams: ActionParams; + user: ActionUser; +}; + +type ActionResult = { + success: boolean; + message?: any; + status?: string | number | null | undefined; +}; + +type Action = (context: ActionContext) => Promise | ActionResult; diff --git a/src/components/fields/Derivative/ContextMenuActions.tsx b/src/components/fields/Derivative/ContextMenuActions.tsx index cafb7247..e05f22f1 100644 --- a/src/components/fields/Derivative/ContextMenuActions.tsx +++ b/src/components/fields/Derivative/ContextMenuActions.tsx @@ -29,6 +29,7 @@ export default function ContextMenuActions( const selectedRowIndex = selectedCell.rowIndex as number; const selectedColIndex = selectedCell?.colIndex; const selectedCol = _find(columns, { index: selectedColIndex }); + if (!selectedCol) return []; const selectedRow = rows?.[selectedRowIndex]; const cellValue = _get(selectedRow, selectedCol.key); console.log({ diff --git a/src/components/fields/Derivative/Settings.tsx b/src/components/fields/Derivative/Settings.tsx index 4dfa2151..0385ed49 100644 --- a/src/components/fields/Derivative/Settings.tsx +++ b/src/components/fields/Derivative/Settings.tsx @@ -11,10 +11,15 @@ import { FieldType } from "@src/constants/fields"; import { useProjectContext } from "@src/contexts/ProjectContext"; import { WIKI_LINKS } from "@src/constants/externalLinks"; +import { getFieldProp } from "@src/components/fields"; +/* eslint-disable import/no-webpack-loader-syntax */ +import derivativeDefs from "!!raw-loader!./derivative.d.ts"; + const CodeEditor = lazy( () => import("@src/components/CodeEditor" /* webpackChunkName: "CodeEditor" */) ); + const diagnosticsOptions = { noSemanticValidation: false, noSyntaxValidation: false, @@ -30,14 +35,15 @@ export default function Settings({ }: ISettingsProps) { const { tableState } = useProjectContext(); if (!tableState?.columns) return <>; - - const columnOptions = Object.values(tableState.columns) + const columns = Object.values(tableState.columns); + const returnType = getFieldProp("dataType", config.renderFieldType) ?? "any"; + console.log({ returnType, r: config.renderFieldType }); + const columnOptions = columns .filter((column) => column.fieldName !== fieldName) .filter((column) => column.type !== FieldType.subTable) .map((c) => ({ label: c.name, value: c.key })); - console.log({ config }); - const code = config.code - ? config.code + const code = config.derivativeFn + ? config.derivativeFn : config?.script ? `const derivative:Derivative = async ({row,ref,db,storage,auth})=>{ ${config.script.replace(/utilFns.getSecret/g, "rowy.secrets.getSecret")} @@ -114,6 +120,12 @@ export default function Settings({ ` + ), + ]} onChange={onChange("code")} /> diff --git a/src/components/CodeEditor/derivative.d.ts b/src/components/fields/Derivative/derivative.d.ts similarity index 72% rename from src/components/CodeEditor/derivative.d.ts rename to src/components/fields/Derivative/derivative.d.ts index 07a363a6..3af58e11 100644 --- a/src/components/CodeEditor/derivative.d.ts +++ b/src/components/fields/Derivative/derivative.d.ts @@ -7,4 +7,4 @@ type DerivativeContext = { change: any; }; -type Derivative = (context: DerivativeContext) => Promise; +type Derivative = (context: DerivativeContext) => "PLACEHOLDER_OUTPUT_TYPE"; diff --git a/src/components/fields/Image/index.tsx b/src/components/fields/Image/index.tsx index 7409a002..0b5ce6fc 100644 --- a/src/components/fields/Image/index.tsx +++ b/src/components/fields/Image/index.tsx @@ -18,8 +18,7 @@ export const config: IFieldConfig = { type: FieldType.image, name: "Image", group: "File", - dataType: - "{ downloadURL: string; lastModifiedTS: number; name: string; type: string; ref: string; }[]", + dataType: "RowyFile[]", initialValue: [], icon: , description: diff --git a/src/components/fields/_BasicCell/BasicCellContextMenuActions.tsx b/src/components/fields/_BasicCell/BasicCellContextMenuActions.tsx index b9da942d..27dda356 100644 --- a/src/components/fields/_BasicCell/BasicCellContextMenuActions.tsx +++ b/src/components/fields/_BasicCell/BasicCellContextMenuActions.tsx @@ -27,6 +27,7 @@ export default function BasicContextMenuActions( const selectedRowIndex = selectedCell.rowIndex as number; const selectedColIndex = selectedCell?.colIndex; const selectedCol = _find(columns, { index: selectedColIndex }); + if (!selectedCol) return []; const selectedRow = rows?.[selectedRowIndex]; const cellValue = _get(selectedRow, selectedCol.key); diff --git a/src/constants/runRoutes.ts b/src/constants/runRoutes.ts index 3d5abc17..b1504c3e 100644 --- a/src/constants/runRoutes.ts +++ b/src/constants/runRoutes.ts @@ -36,6 +36,7 @@ export const runRoutes = { firestoreRules: { path: "/firestoreRules", method: "GET" } as RunRoute, setFirestoreRules: { path: "/setFirestoreRules", method: "POST" } as RunRoute, listCollections: { path: "/listCollections", method: "GET" } as RunRoute, + listSecrets: { path: "/listSecrets", method: "GET" } as RunRoute, serviceAccountAccess: { path: "/serviceAccountAccess", method: "GET", diff --git a/src/utils/rowyRun.ts b/src/utils/rowyRun.ts index 22cf7520..ec08ee2f 100644 --- a/src/utils/rowyRun.ts +++ b/src/utils/rowyRun.ts @@ -17,7 +17,7 @@ export const rowyRun = async ({ route, body, params, - localhost = false, + localhost = true, json = true, signal, }: IRowyRunRequestProps) => {