From 9218d68c1549deb07cd61c372f60c0817228a27f Mon Sep 17 00:00:00 2001 From: shamsmosowi Date: Mon, 18 Oct 2021 10:26:25 +1100 Subject: [PATCH 01/13] basic webhooks --- src/assets/icons/Webhook.tsx | 10 + .../Settings/UserManagement/UserItem.tsx | 1 + src/components/Table/Settings/Webhooks.tsx | 186 ------------ .../Table/TableHeader/Extensions/utils.ts | 2 +- .../TableHeader/Webhooks/Step1Endpoint.tsx | 35 +++ .../Table/TableHeader/Webhooks/Step2Auth.tsx | 16 ++ .../TableHeader/Webhooks/Step3Conditions.tsx | 57 ++++ .../TableHeader/Webhooks/Step4Parser.tsx | 63 ++++ .../TableHeader/Webhooks/WebhookList.tsx | 261 +++++++++++++++++ .../TableHeader/Webhooks/WebhookModal.tsx | 271 ++++++++++++++++++ .../Table/TableHeader/Webhooks/index.tsx | 239 +++++++++++++++ .../Table/TableHeader/Webhooks/utils.ts | 69 +++++ src/components/Table/TableHeader/index.tsx | 3 + src/constants/externalLinks.ts | 1 + src/constants/runRoutes.ts | 1 + src/contexts/ProjectContext.tsx | 4 + src/hooks/useTable/index.ts | 2 +- src/utils/fns.ts | 7 + 18 files changed, 1040 insertions(+), 188 deletions(-) create mode 100644 src/assets/icons/Webhook.tsx delete mode 100644 src/components/Table/Settings/Webhooks.tsx create mode 100644 src/components/Table/TableHeader/Webhooks/Step1Endpoint.tsx create mode 100644 src/components/Table/TableHeader/Webhooks/Step2Auth.tsx create mode 100644 src/components/Table/TableHeader/Webhooks/Step3Conditions.tsx create mode 100644 src/components/Table/TableHeader/Webhooks/Step4Parser.tsx create mode 100644 src/components/Table/TableHeader/Webhooks/WebhookList.tsx create mode 100644 src/components/Table/TableHeader/Webhooks/WebhookModal.tsx create mode 100644 src/components/Table/TableHeader/Webhooks/index.tsx create mode 100644 src/components/Table/TableHeader/Webhooks/utils.ts diff --git a/src/assets/icons/Webhook.tsx b/src/assets/icons/Webhook.tsx new file mode 100644 index 00000000..3e0a79a0 --- /dev/null +++ b/src/assets/icons/Webhook.tsx @@ -0,0 +1,10 @@ +import SvgIcon, { SvgIconProps } from "@mui/material/SvgIcon"; +import { mdiWebhook } from "@mdi/js"; + +export default function Webhook(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/components/Settings/UserManagement/UserItem.tsx b/src/components/Settings/UserManagement/UserItem.tsx index 3fdc086c..8c9c3cb4 100644 --- a/src/components/Settings/UserManagement/UserItem.tsx +++ b/src/components/Settings/UserManagement/UserItem.tsx @@ -20,6 +20,7 @@ import { runRoutes } from "constants/runRoutes"; import { db } from "@src/firebase"; import { USERS } from "config/dbPaths"; import { useConfirmation } from "components/ConfirmationDialog"; +import { analytics } from "@src/analytics"; export default function UserItem({ id, user, roles: rolesProp }: User) { const { enqueueSnackbar, closeSnackbar } = useSnackbar(); diff --git a/src/components/Table/Settings/Webhooks.tsx b/src/components/Table/Settings/Webhooks.tsx deleted file mode 100644 index bb00aa4e..00000000 --- a/src/components/Table/Settings/Webhooks.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { makeStyles, createStyles } from "@mui/styles"; - -import Button from "@mui/material/Button"; -import Dialog, { DialogProps } from "@mui/material/Dialog"; -import DialogActions from "@mui/material/DialogActions"; -import DialogContent from "@mui/material/DialogContent"; - -import DialogTitle from "@mui/material/DialogTitle"; -import FormControl from "@mui/material/FormControl"; -import Typography from "@mui/material/Typography"; - -import FormControlLabel from "@mui/material/FormControlLabel"; -import InputLabel from "@mui/material/InputLabel"; -import MenuItem from "@mui/material/MenuItem"; -import Select from "@mui/material/Select"; -import Switch from "@mui/material/Switch"; -import CodeEditor from "../editors/CodeEditor"; -import { useProjectContext } from "contexts/ProjectContext"; -import { makeId } from "../../../utils/fns"; - -const useStyles = makeStyles((theme) => - createStyles({ - form: { - display: "flex", - flexDirection: "column", - margin: "auto", - width: "fit-content", - }, - formControl: { - marginTop: theme.spacing(2), - minWidth: 120, - }, - formControlLabel: { - marginTop: theme.spacing(1), - }, - }) -); - -enum WebhookTypes { - custom = "CUSTOM", - typeForm = "TYPE_FORM", -} -const EmptyState = { - enabled: false, - type: WebhookTypes.custom, - secret: "", - customParser: "", -}; -export default function WebhooksDialog({ open, handleClose }) { - const classes = useStyles(); - - const { tableState, tableActions } = useProjectContext(); - - const [state, setState] = useState<{ - enabled: boolean; - type: WebhookTypes; - secret: string; - customParser: string; - }>(EmptyState); - const tableFields = Object.keys(tableState?.columns as any); - const fullWidth = true; - const maxWidth: DialogProps["maxWidth"] = "xl"; - const handleChange = (key: string) => (value: any) => { - setState((s) => ({ ...s, [key]: value })); - }; - const initializeWebhooksConfig = () => { - const secret = makeId(32); - handleChange("secret")(secret); - setState({ ...EmptyState, secret }); - tableActions?.table.updateConfig("webhooks", { - enabled: false, - type: WebhookTypes.custom, - secret, - customParser: "", // TODO: add a boilerplate/example - }); - }; - useEffect(() => { - if ( - tableState && - !tableState.config.tableConfig.loading && - !tableState?.config.webhooks && - !state.secret - ) { - initializeWebhooksConfig(); - } else if (tableState?.config.webhooks) { - setState({ ...tableState?.config.webhooks }); - } - }, [tableState?.config]); - - const handleWebhookTypeChange = ( - event: React.ChangeEvent<{ value: unknown }> - ) => { - handleChange("type")(event.target.value as WebhookTypes); - }; - - const handleSave = async () => { - handleClose(); - await tableActions?.table.updateConfig("webhooks", { - ...state, - }); - }; - const handleCancel = () => { - handleClose(); - setState({ ...tableState?.config.webhooks }); - }; - return ( - - - Webhooks - - - } - label={"Enable webhooks for this table"} - labelPlacement="end" - checked={state.enabled} - onChange={() => { - handleChange("enabled")(!state.enabled); - }} - sx={{ - alignItems: "center", - "& .MuiFormControlLabel-label": { mt: 0 }, - }} - // classes={{ root: classes.formControlLabel, label: classes.label }} - /> - Webhook type - - - {state.type === WebhookTypes.custom && ( - - )} -
- {state.type === WebhookTypes.typeForm && ( - <> - Web hook url: - - {/* {WEBHOOK_URL}?tablePath={tableState?.tablePath} - &type=TYPE_FORM&secret={state.secret} */} - - instructions: - - please set the question reference in typeform to the following - field keys :{" "} - {tableFields.map((key) => ( - <> - {" "} - {key}, - - ))} - - - )} -
- - - - -
-
- ); -} diff --git a/src/components/Table/TableHeader/Extensions/utils.ts b/src/components/Table/TableHeader/Extensions/utils.ts index 61366df4..3b657400 100644 --- a/src/components/Table/TableHeader/Extensions/utils.ts +++ b/src/components/Table/TableHeader/Extensions/utils.ts @@ -40,7 +40,7 @@ export interface IExtension { active: boolean; lastEditor: IExtensionEditor; - // ft build fields + // build fields triggers: ExtensionTrigger[]; type: ExtensionType; requiredFields: string[]; diff --git a/src/components/Table/TableHeader/Webhooks/Step1Endpoint.tsx b/src/components/Table/TableHeader/Webhooks/Step1Endpoint.tsx new file mode 100644 index 00000000..5a32b69e --- /dev/null +++ b/src/components/Table/TableHeader/Webhooks/Step1Endpoint.tsx @@ -0,0 +1,35 @@ +import { IWebhookModalStepProps } from "./WebhookModal"; +import { useProjectContext } from "contexts/ProjectContext"; +import { FormControl, FormLabel, TextField, Typography } from "@mui/material"; + +export default function Step1Endpoint({ + webhookObject, + setWebhookObject, +}: IWebhookModalStepProps) { + const { settings, tableState } = useProjectContext(); + return ( + + + Endpoint + +
+ {" "} + + {" "} + {`${settings?.rowyRunUrl}/whs/${tableState?.tablePath}/`} + {" "} + + setWebhookObject({ ...webhookObject, endpoint: e.target.value }) + } + /> +
+
+ ); +} diff --git a/src/components/Table/TableHeader/Webhooks/Step2Auth.tsx b/src/components/Table/TableHeader/Webhooks/Step2Auth.tsx new file mode 100644 index 00000000..8196e2e9 --- /dev/null +++ b/src/components/Table/TableHeader/Webhooks/Step2Auth.tsx @@ -0,0 +1,16 @@ +import { IWebhookModalStepProps } from "./WebhookModal"; +import _sortBy from "lodash/sortBy"; + +import MultiSelect from "@rowy/multiselect"; +import { ListItemIcon } from "@mui/material"; + +import { useProjectContext } from "contexts/ProjectContext"; +import { FieldType } from "constants/fields"; +import { getFieldProp } from "components/fields"; + +export default function Step2Auth({ + webhookObject, + setWebhookObject, +}: IWebhookModalStepProps) { + return <>provides different fields based on the webhookType; +} diff --git a/src/components/Table/TableHeader/Webhooks/Step3Conditions.tsx b/src/components/Table/TableHeader/Webhooks/Step3Conditions.tsx new file mode 100644 index 00000000..41efaa76 --- /dev/null +++ b/src/components/Table/TableHeader/Webhooks/Step3Conditions.tsx @@ -0,0 +1,57 @@ +import { IWebhookModalStepProps } from "./WebhookModal"; +import useStateRef from "react-usestateref"; + +import CodeEditor from "components/Table/editors/CodeEditor"; +import CodeEditorHelper from "components/CodeEditorHelper"; + +import { WIKI_LINKS } from "constants/externalLinks"; + +const additionalVariables = [ + { + key: "req", + description: "webhook request", + }, +]; + +export default function Step3Conditions({ + webhookObject, + setWebhookObject, + setValidation, + validationRef, +}: IWebhookModalStepProps) { + const [, setConditionEditorActive, conditionEditorActiveRef] = + useStateRef(false); + + return ( + <> +
+ { + setWebhookObject({ + ...webhookObject, + conditions: newValue, + }); + }} + onValidStatusUpdate={({ isValid }) => { + if (!conditionEditorActiveRef.current) return; + setValidation({ ...validationRef.current!, condition: isValid }); + }} + diagnosticsOptions={{ + noSemanticValidation: false, + noSyntaxValidation: false, + noSuggestionDiagnostics: true, + }} + onMount={() => setConditionEditorActive(true)} + onUnmount={() => setConditionEditorActive(false)} + /> +
+ + + + ); +} diff --git a/src/components/Table/TableHeader/Webhooks/Step4Parser.tsx b/src/components/Table/TableHeader/Webhooks/Step4Parser.tsx new file mode 100644 index 00000000..cbeb8dc4 --- /dev/null +++ b/src/components/Table/TableHeader/Webhooks/Step4Parser.tsx @@ -0,0 +1,63 @@ +import { IWebhookModalStepProps } from "./WebhookModal"; +import _upperFirst from "lodash/upperFirst"; +import useStateRef from "react-usestateref"; + +import CodeEditor from "components/Table/editors/CodeEditor"; +import CodeEditorHelper from "components/CodeEditorHelper"; + +import { WIKI_LINKS } from "constants/externalLinks"; + +const additionalVariables = [ + { + key: "req", + description: "webhook request", + }, +]; + +export default function Step4Body({ + webhookObject, + setWebhookObject, + setValidation, + validationRef, +}: IWebhookModalStepProps) { + const [, setBodyEditorActive, bodyEditorActiveRef] = useStateRef(false); + + return ( + <> +
+ { + setWebhookObject({ + ...webhookObject, + parser: newValue, + }); + }} + onValidStatusUpdate={({ isValid }) => { + if (!bodyEditorActiveRef.current) return; + setValidation({ + ...validationRef.current!, + parser: isValid, + }); + }} + diagnosticsOptions={{ + noSemanticValidation: false, + noSyntaxValidation: false, + noSuggestionDiagnostics: true, + }} + onMount={() => setBodyEditorActive(true)} + onUnmount={() => setBodyEditorActive(false)} + /> +
+ + + + ); +} diff --git a/src/components/Table/TableHeader/Webhooks/WebhookList.tsx b/src/components/Table/TableHeader/Webhooks/WebhookList.tsx new file mode 100644 index 00000000..69abb319 --- /dev/null +++ b/src/components/Table/TableHeader/Webhooks/WebhookList.tsx @@ -0,0 +1,261 @@ +import { useState, useRef } from "react"; +import { format, formatRelative } from "date-fns"; +import CopyIcon from "assets/icons/Copy"; + +import { + Stack, + ButtonBase, + List, + ListItem, + ListItemText, + Avatar, + Button, + IconButton, + Menu, + MenuItem, + Switch, + Tooltip, + Typography, +} from "@mui/material"; +import AddIcon from "@mui/icons-material/Add"; +import WebhookIcon from "assets/icons/Webhook"; +import DuplicateIcon from "assets/icons/Copy"; +import EditIcon from "@mui/icons-material/EditOutlined"; +import DeleteIcon from "@mui/icons-material/DeleteOutlined"; + +import EmptyState from "components/EmptyState"; +import { webhookTypes, webhookNames, IWebhook, WebhookType } from "./utils"; +import { DATE_TIME_FORMAT } from "constants/dates"; +import { useProjectContext } from "@src/contexts/ProjectContext"; + +export interface IWebhookListProps { + webhooks: IWebhook[]; + handleAddWebhook: (type: WebhookType) => void; + handleUpdateActive: (index: number, active: boolean) => void; + handleDuplicate: (index: number) => void; + handleEdit: (index: number) => void; + handleDelete: (index: number) => void; +} + +export default function WebhookList({ + webhooks, + handleAddWebhook, + handleUpdateActive, + handleDuplicate, + handleEdit, + handleDelete, +}: IWebhookListProps) { + const { settings, tableState } = useProjectContext(); + const [anchorEl, setAnchorEl] = useState(null); + const addButtonRef = useRef(null); + + const activeWebhookCount = webhooks.filter( + (webhook) => webhook.active + ).length; + + const handleAddButton = () => { + setAnchorEl(addButtonRef.current); + }; + + const handleChooseAddType = (type: WebhookType) => { + handleClose(); + handleAddWebhook(type); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const baseUrl = `${settings?.rowyRunUrl}/whs/${tableState?.tablePath}/`; + return ( + <> + + + Webhooks ({activeWebhookCount} / {webhooks.length}) + + + + + {webhookTypes.map((type) => ( + handleChooseAddType(type)}> + {webhookNames[type]} + + ))} + + + + {webhooks.length === 0 ? ( + + + + ) : ( + + {webhooks.map((webhook, index) => ( + + {webhook.name} {webhookNames[webhook.type]} + + } + secondary={ +
+
+ + + {baseUrl} + {webhook.endpoint} + + +
+ + + navigator.clipboard.writeText( + `${baseUrl}${webhook.endpoint}` + ) + } + > + + + +
+ } + //secondary={webhookNames[webhook.type]} + primaryTypographyProps={{ + style: { + minHeight: 40, + display: "flex", + alignItems: "center", + }, + }} + /> + } + secondaryAction={ + + + + + handleUpdateActive(index, !webhook.active) + } + inputProps={{ "aria-label": "Activate" }} + sx={{ mr: 1 }} + /> + + + + handleDuplicate(index)} + > + + + + + handleEdit(index)} + > + + + + + handleDelete(index)} + sx={{ "&&": { mr: -1.5 } }} + > + + + + + + + Last updated +
+ by {webhook.lastEditor.displayName} +
+ at{" "} + {format( + webhook.lastEditor.lastUpdate, + DATE_TIME_FORMAT + )} + + } + > + + + {formatRelative( + webhook.lastEditor.lastUpdate, + new Date() + )} + + + +
+
+ } + /> + ))} +
+ )} + + ); +} diff --git a/src/components/Table/TableHeader/Webhooks/WebhookModal.tsx b/src/components/Table/TableHeader/Webhooks/WebhookModal.tsx new file mode 100644 index 00000000..395a6c40 --- /dev/null +++ b/src/components/Table/TableHeader/Webhooks/WebhookModal.tsx @@ -0,0 +1,271 @@ +import { useState } from "react"; +import _isEqual from "lodash/isEqual"; +import _upperFirst from "lodash/upperFirst"; +import useStateRef from "react-usestateref"; + +import { + Grid, + TextField, + FormControlLabel, + Switch, + Stepper, + Step, + StepButton, + StepContent, + Typography, + Link, +} from "@mui/material"; +import ExpandIcon from "@mui/icons-material/KeyboardArrowDown"; +import InlineOpenInNewIcon from "components/InlineOpenInNewIcon"; + +import Modal, { IModalProps } from "components/Modal"; +import Step1Triggers from "./Step1Endpoint"; +import Step2RequiredFields from "./Step2Auth"; +import Step3Conditions from "./Step3Conditions"; +import Step4Body from "./Step4Parser"; + +import { useConfirmation } from "components/ConfirmationDialog"; + +import { webhookNames, IWebhook } from "./utils"; +import { WIKI_LINKS } from "constants/externalLinks"; + +type StepValidation = Record<"condition" | "parser", boolean>; +export interface IWebhookModalStepProps { + webhookObject: IWebhook; + setWebhookObject: React.Dispatch>; + validation: StepValidation; + setValidation: React.Dispatch>; + validationRef: React.RefObject; +} + +export interface IWebhookModalProps { + handleClose: IModalProps["onClose"]; + handleAdd: (webhookObject: IWebhook) => void; + handleUpdate: (webhookObject: IWebhook) => void; + mode: "add" | "update"; + webhookObject: IWebhook; +} + +export default function WebhookModal({ + handleClose, + handleAdd, + handleUpdate, + mode, + webhookObject: initialObject, +}: IWebhookModalProps) { + const { requestConfirmation } = useConfirmation(); + + const [webhookObject, setWebhookObject] = useState(initialObject); + + const [activeStep, setActiveStep] = useState(0); + + const [validation, setValidation, validationRef] = + useStateRef({ condition: true, parser: true }); + + const edited = !_isEqual(initialObject, webhookObject); + + const handleAddOrUpdate = () => { + if (mode === "add") handleAdd(webhookObject); + if (mode === "update") handleUpdate(webhookObject); + }; + + const stepProps = { + webhookObject, + setWebhookObject, + validation, + setValidation, + validationRef, + }; + + return ( + + + + { + setWebhookObject({ + ...webhookObject, + name: event.target.value, + }); + }} + /> + + + + + setWebhookObject((webhookObject) => ({ + ...webhookObject, + active: e.target.checked, + })) + } + size="medium" + /> + } + label={`Webhook endpoint is ${ + !webhookObject.active ? "de" : "" + }activated`} + /> + + + + theme.transitions.create("transform"), + }, + "& .Mui-active svg": { + transform: "rotate(180deg)", + }, + }} + > + + setActiveStep(0)}> + Endpoint URL + + + + + Set the endpoint URL for the webhook. + + + + + + + setActiveStep(1)}> + Authentication + + + + + Set the authentication configuration for the webhook. + + + + + + + setActiveStep(2)}> + Conditions (optional) + + + + + Optionally, write a function that determines if the webhook + call should be processed. Leave the function to always return{" "} + true if you do not want to write additional + logic. + + + + + + + setActiveStep(3)}> + Parser + + + + + Write the webhook parsed function. The returned object of the + parser will be added as new row{" "} + + Docs + + + + + + + + + } + actions={{ + primary: { + children: mode === "add" ? "Add" : "Update", + disabled: !edited || !webhookObject.name.length, + onClick: () => { + let warningMessage; + if (!validation.condition && !validation.parser) { + warningMessage = "Condition and webhook body are not valid"; + } else if (!validation.condition) { + warningMessage = "Condition is not valid"; + } else if (!validation.parser) { + warningMessage = "Webhook body is not valid"; + } + + if (warningMessage) { + requestConfirmation({ + title: "Validation failed", + body: `${warningMessage}. Continue?`, + confirm: "Yes, I know what I’m doing", + cancel: "No, I’ll fix the errors", + handleConfirm: handleAddOrUpdate, + }); + } else { + handleAddOrUpdate(); + } + }, + }, + }} + /> + ); +} diff --git a/src/components/Table/TableHeader/Webhooks/index.tsx b/src/components/Table/TableHeader/Webhooks/index.tsx new file mode 100644 index 00000000..efe8a96d --- /dev/null +++ b/src/components/Table/TableHeader/Webhooks/index.tsx @@ -0,0 +1,239 @@ +import { useState } from "react"; +import _isEqual from "lodash/isEqual"; + +import { Breadcrumbs } from "@mui/material"; + +import TableHeaderButton from "../TableHeaderButton"; +import WebhookIcon from "assets/icons/Webhook"; +import Modal from "components/Modal"; +import WebhookList from "./WebhookList"; +import WebhookModal from "./WebhookModal"; + +import { useProjectContext } from "contexts/ProjectContext"; +import { useAppContext } from "contexts/AppContext"; +import { useConfirmation } from "components/ConfirmationDialog"; + +import { emptyWebhookObject, IWebhook, WebhookType } from "./utils"; +import { runRoutes } from "constants/runRoutes"; +import { analytics } from "@src/analytics"; +import { useSnackbar } from "notistack"; + +export default function Webhooks() { + const { tableState, tableActions, rowyRun } = useProjectContext(); + const appContext = useAppContext(); + const { requestConfirmation } = useConfirmation(); + const { enqueueSnackbar } = useSnackbar(); + + const currentwebhooks = (tableState?.config.webhooks ?? []) as IWebhook[]; + const [localWebhooksObjects, setLocalWebhooksObjects] = + useState(currentwebhooks); + const [openWebhookList, setOpenWebhookList] = useState(false); + const [webhookModal, setWebhookModal] = useState<{ + mode: "add" | "update"; + webhookObject: IWebhook; + index?: number; + } | null>(null); + + const edited = !_isEqual(currentwebhooks, localWebhooksObjects); + + const tablePathTokens = + tableState?.tablePath?.split("/").filter(function (_, i) { + // replace IDs with dash that appears at even indexes + return i % 2 === 0; + }) ?? []; + + const handleOpen = () => { + setOpenWebhookList(true); + }; + + const handleClose = () => { + if (edited) { + requestConfirmation({ + title: "Discard changes", + body: "You will lose changes you have made to webhooks", + confirm: "Discard", + handleConfirm: () => { + setLocalWebhooksObjects(currentwebhooks); + setOpenWebhookList(false); + }, + }); + } else { + setOpenWebhookList(false); + } + }; + + const handleSaveWebhooks = async () => { + tableActions?.table.updateConfig("webhooks", localWebhooksObjects); + setOpenWebhookList(false); + // TODO: convert to async function that awaits for the document write to complete + await new Promise((resolve) => setTimeout(resolve, 500)); + }; + + const handleSaveDeploy = async () => { + await handleSaveWebhooks(); + try { + if (rowyRun) { + const resp = await rowyRun({ + route: runRoutes.publishWebhooks, + body: { + tableConfigPath: tableState?.config.tableConfig.path, + tablePath: tableState?.tablePath, + }, + }); + enqueueSnackbar(resp.message, { + variant: resp.success ? "success" : "error", + }); + + analytics.logEvent("published_webhooks"); + } + } catch (e) { + console.error(e); + } + }; + + const handleAddWebhook = (webhookObject: IWebhook) => { + setLocalWebhooksObjects([...localWebhooksObjects, webhookObject]); + analytics.logEvent("created_webhook", { type: webhookObject.type }); + setWebhookModal(null); + }; + + const handleUpdateWebhook = (webhookObject: IWebhook) => { + setLocalWebhooksObjects( + localWebhooksObjects.map((webhook, index) => { + if (index === webhookModal?.index) { + return { + ...webhookObject, + lastEditor: currentEditor(), + }; + } else { + return webhook; + } + }) + ); + analytics.logEvent("updated_webhook", { type: webhookObject.type }); + setWebhookModal(null); + }; + + const handleUpdateActive = (index: number, active: boolean) => { + setLocalWebhooksObjects( + localWebhooksObjects.map((webhookObject, i) => { + if (i === index) { + return { + ...webhookObject, + active, + lastEditor: currentEditor(), + }; + } else { + return webhookObject; + } + }) + ); + }; + + const handleDuplicate = (index: number) => { + setLocalWebhooksObjects([ + ...localWebhooksObjects, + { + ...localWebhooksObjects[index], + name: `${localWebhooksObjects[index].name} (duplicate)`, + active: false, + lastEditor: currentEditor(), + }, + ]); + analytics.logEvent("duplicated_webhook", { + type: localWebhooksObjects[index].type, + }); + }; + + const handleEdit = (index: number) => { + setWebhookModal({ + mode: "update", + webhookObject: localWebhooksObjects[index], + index, + }); + }; + + const handleDelete = (index: number) => { + requestConfirmation({ + title: `Delete ${localWebhooksObjects[index].name}?`, + body: "This webhook will be permanently deleted.", + confirm: "Confirm", + handleConfirm: () => { + setLocalWebhooksObjects( + localWebhooksObjects.filter((_, i) => i !== index) + ); + }, + }); + }; + + const currentEditor = () => ({ + displayName: appContext?.currentUser?.displayName ?? "Unknown user", + photoURL: appContext?.currentUser?.photoURL ?? "", + lastUpdate: Date.now(), + }); + + return ( + <> + } + /> + + {openWebhookList && !!tableState && ( + + + {tablePathTokens.map((pathToken) => ( + {pathToken} + ))} + + { + setWebhookModal({ + mode: "add", + webhookObject: emptyWebhookObject(type, currentEditor()), + }); + }} + handleUpdateActive={handleUpdateActive} + handleEdit={handleEdit} + handleDuplicate={handleDuplicate} + handleDelete={handleDelete} + /> + + } + actions={{ + primary: { + children: "Save & Deploy", + onClick: handleSaveDeploy, + disabled: !edited, + }, + secondary: { + children: "Save", + onClick: handleSaveWebhooks, + disabled: !edited, + }, + }} + /> + )} + + {webhookModal && ( + { + setWebhookModal(null); + }} + handleAdd={handleAddWebhook} + handleUpdate={handleUpdateWebhook} + mode={webhookModal.mode} + webhookObject={webhookModal.webhookObject} + /> + )} + + ); +} diff --git a/src/components/Table/TableHeader/Webhooks/utils.ts b/src/components/Table/TableHeader/Webhooks/utils.ts new file mode 100644 index 00000000..1d658189 --- /dev/null +++ b/src/components/Table/TableHeader/Webhooks/utils.ts @@ -0,0 +1,69 @@ +import { generateRandomId } from "@src/utils/fns"; + +export const webhookTypes = [ + "basic", + "typeform", + "sendgrid", + "shopify", + "twitter", + "stripe", +] as const; + +export type WebhookType = typeof webhookTypes[number]; + +export const webhookNames: Record = { + sendgrid: "Sendgrid", + typeform: "Typeform", + shopify: "Shopify", + twitter: "Twitter", + stripe: "Stripe", + basic: "Basic", +}; + +export interface IWebhookEditor { + displayName: string; + photoURL: string; + lastUpdate: number; +} + +export interface IWebhook { + // rowy meta fields + name: string; + active: boolean; + lastEditor: IWebhookEditor; + // webhook specific fields + endpoint: string; + type: WebhookType; + parser: string; + conditions: string; +} + +const parserTemplates = { + basic: `const basicParser: BasicParser = async({req, db,ref}) => { + // request is the request object from the webhook + // db is the database object + // ref is the reference to collection of the table + // the returned object will be added as a new row to the table + // eg: adding the webhook body as row + const {body} = req; + return body; +}`, +}; + +export function emptyWebhookObject( + type: WebhookType, + user: IWebhookEditor +): IWebhook { + return { + name: "Untitled webhook", + active: false, + endpoint: generateRandomId(), + type, + parser: parserTemplates[type] ?? parserTemplates["basic"], + conditions: `const condition: Condition = async({ref,req,db}) => { + // feel free to add your own code logic here + return true; +}`, + lastEditor: user, + }; +} diff --git a/src/components/Table/TableHeader/index.tsx b/src/components/Table/TableHeader/index.tsx index bb25c146..7fd0bea6 100644 --- a/src/components/Table/TableHeader/index.tsx +++ b/src/components/Table/TableHeader/index.tsx @@ -12,6 +12,7 @@ import TableLogs from "./TableLogs"; import HiddenFields from "../HiddenFields"; import RowHeight from "./RowHeight"; import Extensions from "./Extensions"; +import Webhooks from "./Webhooks"; import ReExecute from "./ReExecute"; import { useAppContext } from "contexts/AppContext"; @@ -97,6 +98,8 @@ export default function TableHeader() { {userClaims?.roles?.includes("ADMIN") && ( <> + {/* Spacer */}
+ {/* Spacer */}
diff --git a/src/constants/externalLinks.ts b/src/constants/externalLinks.ts index 43d1e2d6..6ffbc6c9 100644 --- a/src/constants/externalLinks.ts +++ b/src/constants/externalLinks.ts @@ -47,6 +47,7 @@ const WIKI_PATHS = { extensionsSlackMessage: "/extensions/slack-message", extensionsSendgridEmail: "/extensions/sendgrid-email", extensionsTwilioMessage: "/extensions/twilio-message", + webhooks: "/webhooks", }; export const WIKI_LINKS = _mapValues( WIKI_PATHS, diff --git a/src/constants/runRoutes.ts b/src/constants/runRoutes.ts index 2cc5a295..8e3e209e 100644 --- a/src/constants/runRoutes.ts +++ b/src/constants/runRoutes.ts @@ -44,6 +44,7 @@ export const runRoutes = { migrateFT2Rowy: { path: "/migrateFT2Rowy", method: "GET" } as RunRoute, actionScript: { path: "/actionScript", method: "POST" } as RunRoute, buildFunction: { path: "/buildFunction", method: "POST" } as RunRoute, + publishWebhooks: { path: "/publishWebhooks", method: "POST" } as RunRoute, projectOwner: { path: "/projectOwner", method: "GET" } as RunRoute, setOwnerRoles: { path: "/setOwnerRoles", method: "GET" } as RunRoute, inviteUser: { path: "/inviteUser", method: "POST" } as RunRoute, diff --git a/src/contexts/ProjectContext.tsx b/src/contexts/ProjectContext.tsx index 013fb4e5..cf13811d 100644 --- a/src/contexts/ProjectContext.tsx +++ b/src/contexts/ProjectContext.tsx @@ -34,6 +34,9 @@ export type Table = { }; interface IProjectContext { + settings: { + rowyRunUrl?: string; + }; tables: Table[]; table: Table; roles: string[]; @@ -251,6 +254,7 @@ export const ProjectContextProvider: React.FC = ({ children }) => { addRow, updateCell, settingsActions, + settings: settings.doc, roles, tables, table, diff --git a/src/hooks/useTable/index.ts b/src/hooks/useTable/index.ts index 22b25a73..6922e187 100644 --- a/src/hooks/useTable/index.ts +++ b/src/hooks/useTable/index.ts @@ -26,10 +26,10 @@ export type TableState = { id: string; rowHeight: number; tableConfig: any; - webhooks: any; sparks: string; compiledExtension: string; extensionObjects?: any[]; + webhooks?: any[]; functionConfigPath?: string; }; columns: any[]; diff --git a/src/utils/fns.ts b/src/utils/fns.ts index 5edcf8d6..936b2189 100644 --- a/src/utils/fns.ts +++ b/src/utils/fns.ts @@ -168,3 +168,10 @@ export const rowyUser = ( ...data, }; }; +// generate Random ID +export const generateRandomId = () => { + return ( + Math.random().toString(36).substring(2, 15) + + Math.random().toString(36).substring(2, 15) + ); +}; From ac52141bbda077bd5019d79b9a5db0cb2023310f Mon Sep 17 00:00:00 2001 From: shamsmosowi Date: Wed, 20 Oct 2021 10:10:19 +1100 Subject: [PATCH 02/13] removed endpoint step --- .../TableHeader/Webhooks/Step1Endpoint.tsx | 35 ------------------- .../TableHeader/Webhooks/Step1Secret.tsx | 23 ++++++++++++ .../Table/TableHeader/Webhooks/Step2Auth.tsx | 16 --------- ...tep3Conditions.tsx => Step2Conditions.tsx} | 0 .../{Step4Parser.tsx => Step3Parser.tsx} | 0 .../TableHeader/Webhooks/WebhookModal.tsx | 34 +++++------------- 6 files changed, 32 insertions(+), 76 deletions(-) delete mode 100644 src/components/Table/TableHeader/Webhooks/Step1Endpoint.tsx create mode 100644 src/components/Table/TableHeader/Webhooks/Step1Secret.tsx delete mode 100644 src/components/Table/TableHeader/Webhooks/Step2Auth.tsx rename src/components/Table/TableHeader/Webhooks/{Step3Conditions.tsx => Step2Conditions.tsx} (100%) rename src/components/Table/TableHeader/Webhooks/{Step4Parser.tsx => Step3Parser.tsx} (100%) diff --git a/src/components/Table/TableHeader/Webhooks/Step1Endpoint.tsx b/src/components/Table/TableHeader/Webhooks/Step1Endpoint.tsx deleted file mode 100644 index 5a32b69e..00000000 --- a/src/components/Table/TableHeader/Webhooks/Step1Endpoint.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { IWebhookModalStepProps } from "./WebhookModal"; -import { useProjectContext } from "contexts/ProjectContext"; -import { FormControl, FormLabel, TextField, Typography } from "@mui/material"; - -export default function Step1Endpoint({ - webhookObject, - setWebhookObject, -}: IWebhookModalStepProps) { - const { settings, tableState } = useProjectContext(); - return ( - - - Endpoint - -
- {" "} - - {" "} - {`${settings?.rowyRunUrl}/whs/${tableState?.tablePath}/`} - {" "} - - setWebhookObject({ ...webhookObject, endpoint: e.target.value }) - } - /> -
-
- ); -} diff --git a/src/components/Table/TableHeader/Webhooks/Step1Secret.tsx b/src/components/Table/TableHeader/Webhooks/Step1Secret.tsx new file mode 100644 index 00000000..38070adc --- /dev/null +++ b/src/components/Table/TableHeader/Webhooks/Step1Secret.tsx @@ -0,0 +1,23 @@ +import { IWebhookModalStepProps } from "./WebhookModal"; +import { useProjectContext } from "contexts/ProjectContext"; +import { FormControl, FormLabel, TextField, Typography } from "@mui/material"; + +export default function Step1Endpoint({ + webhookObject, + setWebhookObject, +}: IWebhookModalStepProps) { + return ( + + + Secret + + + setWebhookObject({ ...webhookObject, secret: e.target.value }) + } + /> + + ); +} diff --git a/src/components/Table/TableHeader/Webhooks/Step2Auth.tsx b/src/components/Table/TableHeader/Webhooks/Step2Auth.tsx deleted file mode 100644 index 8196e2e9..00000000 --- a/src/components/Table/TableHeader/Webhooks/Step2Auth.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { IWebhookModalStepProps } from "./WebhookModal"; -import _sortBy from "lodash/sortBy"; - -import MultiSelect from "@rowy/multiselect"; -import { ListItemIcon } from "@mui/material"; - -import { useProjectContext } from "contexts/ProjectContext"; -import { FieldType } from "constants/fields"; -import { getFieldProp } from "components/fields"; - -export default function Step2Auth({ - webhookObject, - setWebhookObject, -}: IWebhookModalStepProps) { - return <>provides different fields based on the webhookType; -} diff --git a/src/components/Table/TableHeader/Webhooks/Step3Conditions.tsx b/src/components/Table/TableHeader/Webhooks/Step2Conditions.tsx similarity index 100% rename from src/components/Table/TableHeader/Webhooks/Step3Conditions.tsx rename to src/components/Table/TableHeader/Webhooks/Step2Conditions.tsx diff --git a/src/components/Table/TableHeader/Webhooks/Step4Parser.tsx b/src/components/Table/TableHeader/Webhooks/Step3Parser.tsx similarity index 100% rename from src/components/Table/TableHeader/Webhooks/Step4Parser.tsx rename to src/components/Table/TableHeader/Webhooks/Step3Parser.tsx diff --git a/src/components/Table/TableHeader/Webhooks/WebhookModal.tsx b/src/components/Table/TableHeader/Webhooks/WebhookModal.tsx index 395a6c40..c9b38052 100644 --- a/src/components/Table/TableHeader/Webhooks/WebhookModal.tsx +++ b/src/components/Table/TableHeader/Webhooks/WebhookModal.tsx @@ -19,10 +19,9 @@ import ExpandIcon from "@mui/icons-material/KeyboardArrowDown"; import InlineOpenInNewIcon from "components/InlineOpenInNewIcon"; import Modal, { IModalProps } from "components/Modal"; -import Step1Triggers from "./Step1Endpoint"; -import Step2RequiredFields from "./Step2Auth"; -import Step3Conditions from "./Step3Conditions"; -import Step4Body from "./Step4Parser"; +import Step1Secret from "./Step1Secret"; +import Step2Conditions from "./Step2Conditions"; +import Step3Body from "./Step3Parser"; import { useConfirmation } from "components/ConfirmationDialog"; @@ -121,7 +120,6 @@ export default function WebhookModal({ }} /> - setActiveStep(0)}> - Endpoint URL + Verification - Set the endpoint URL for the webhook. + Set the verification secret for the webhook. - + setActiveStep(1)}> - Authentication - - - - - Set the authentication configuration for the webhook. - - - - - - - setActiveStep(2)}> Conditions (optional) @@ -206,12 +191,12 @@ export default function WebhookModal({ true if you do not want to write additional logic. - + - setActiveStep(3)}> + setActiveStep(2)}> Parser @@ -232,7 +217,7 @@ export default function WebhookModal({ - + @@ -251,7 +236,6 @@ export default function WebhookModal({ } else if (!validation.parser) { warningMessage = "Webhook body is not valid"; } - if (warningMessage) { requestConfirmation({ title: "Validation failed", From 32a57e439839d858d9e4bb434d3240e14bb6a3ab Mon Sep 17 00:00:00 2001 From: shamsmosowi Date: Wed, 20 Oct 2021 10:10:54 +1100 Subject: [PATCH 03/13] typeform template --- .../Table/TableHeader/Webhooks/utils.ts | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/components/Table/TableHeader/Webhooks/utils.ts b/src/components/Table/TableHeader/Webhooks/utils.ts index 1d658189..c191859e 100644 --- a/src/components/Table/TableHeader/Webhooks/utils.ts +++ b/src/components/Table/TableHeader/Webhooks/utils.ts @@ -36,6 +36,7 @@ export interface IWebhook { type: WebhookType; parser: string; conditions: string; + secret?: string; } const parserTemplates = { @@ -48,8 +49,43 @@ const parserTemplates = { const {body} = req; return body; }`, + typeform: `const typeformParser: TypeformParser = async({req, db,ref}) =>{ + // this reduces the form submission into a single object of key value pairs + // eg: {name: "John", age: 20} + // ⚠️ ensure that you have assigned ref values of the fields + // set the ref value to field key you would like to sync to + // docs: https://help.typeform.com/hc/en-us/articles/360050447552-Block-reference-format-restrictions + const {submitted_at,hidden,answers} = req.body.form_response + return ({ + _createdAt: submitted_at, + ...hidden, + ...answers.reduce((accRow, currAnswer) => { + switch (currAnswer.type) { + case "date": + return { + ...accRow, + [currAnswer.field.ref]: new Date(currAnswer[currAnswer.type]), + }; + case "choice": + return { + ...accRow, + [currAnswer.field.ref]: currAnswer[currAnswer.type].label, + }; + case "choices": + return { + ...accRow, + [currAnswer.field.ref]: currAnswer[currAnswer.type].labels, + }; + case "file_url": + default: + return { + ...accRow, + [currAnswer.field.ref]: currAnswer[currAnswer.type], + }; + } + }, {}), + })};`, }; - export function emptyWebhookObject( type: WebhookType, user: IWebhookEditor From 7afd409141831c037d8674b6eb162afa70a23eeb Mon Sep 17 00:00:00 2001 From: shamsmosowi Date: Mon, 25 Oct 2021 13:34:51 +1100 Subject: [PATCH 04/13] basic logs --- .../TableHeader/Webhooks/WebhookList.tsx | 14 ++-- .../TableHeader/Webhooks/WebhookLogs.tsx | 74 +++++++++++++++++++ .../Table/TableHeader/Webhooks/index.tsx | 30 ++++---- 3 files changed, 98 insertions(+), 20 deletions(-) create mode 100644 src/components/Table/TableHeader/Webhooks/WebhookLogs.tsx diff --git a/src/components/Table/TableHeader/Webhooks/WebhookList.tsx b/src/components/Table/TableHeader/Webhooks/WebhookList.tsx index 69abb319..106011dd 100644 --- a/src/components/Table/TableHeader/Webhooks/WebhookList.tsx +++ b/src/components/Table/TableHeader/Webhooks/WebhookList.tsx @@ -19,7 +19,7 @@ import { } from "@mui/material"; import AddIcon from "@mui/icons-material/Add"; import WebhookIcon from "assets/icons/Webhook"; -import DuplicateIcon from "assets/icons/Copy"; +import LogsIcon from "assets/icons/CloudLogs"; import EditIcon from "@mui/icons-material/EditOutlined"; import DeleteIcon from "@mui/icons-material/DeleteOutlined"; @@ -32,7 +32,7 @@ export interface IWebhookListProps { webhooks: IWebhook[]; handleAddWebhook: (type: WebhookType) => void; handleUpdateActive: (index: number, active: boolean) => void; - handleDuplicate: (index: number) => void; + handleOpenLogs: (index: number) => void; handleEdit: (index: number) => void; handleDelete: (index: number) => void; } @@ -41,7 +41,7 @@ export default function WebhookList({ webhooks, handleAddWebhook, handleUpdateActive, - handleDuplicate, + handleOpenLogs, handleEdit, handleDelete, }: IWebhookListProps) { @@ -190,12 +190,12 @@ export default function WebhookList({ /> - + handleDuplicate(index)} + aria-label="Logs" + onClick={() => handleOpenLogs(index)} > - + diff --git a/src/components/Table/TableHeader/Webhooks/WebhookLogs.tsx b/src/components/Table/TableHeader/Webhooks/WebhookLogs.tsx new file mode 100644 index 00000000..d933cdda --- /dev/null +++ b/src/components/Table/TableHeader/Webhooks/WebhookLogs.tsx @@ -0,0 +1,74 @@ +import _isEqual from "lodash/isEqual"; +import _upperFirst from "lodash/upperFirst"; + +import Modal, { IModalProps } from "components/Modal"; + +import { IWebhook } from "./utils"; +import useCollection from "@src/hooks/useCollection"; +import { useEffect } from "react"; +import { useProjectContext } from "contexts/ProjectContext"; +import { Typography } from "@mui/material"; +import { orderBy } from "lodash"; + +export interface IWebhookLogsProps { + handleClose: IModalProps["onClose"]; + webhookObject: IWebhook; +} + +export default function WebhookModal({ + handleClose, + webhookObject, +}: IWebhookLogsProps) { + const { tableState } = useProjectContext(); + const [logsCollection, logsDispatch] = useCollection({}); + useEffect(() => { + if (webhookObject && tableState?.tablePath) { + logsDispatch({ + path: "_rowy_/webhooks/logs", + filters: [ + { + field: "params.endpoint", + operator: "==", + value: webhookObject.endpoint, + }, + { + field: "params.tablePath", + operator: "==", + value: tableState?.tablePath, + }, + ], + orderBy: { key: "createdAt", direction: "desc" }, + limit: 50, + }); + } + }, [webhookObject, tableState?.tablePath]); + return ( + + {logsCollection.documents.map((doc) => ( + {`${doc.createdAt.toDate()} - ${ + doc.response + }`} + ))} + + } + actions={{ + primary: { + onClick: () => {}, + }, + }} + /> + ); +} diff --git a/src/components/Table/TableHeader/Webhooks/index.tsx b/src/components/Table/TableHeader/Webhooks/index.tsx index efe8a96d..513a1027 100644 --- a/src/components/Table/TableHeader/Webhooks/index.tsx +++ b/src/components/Table/TableHeader/Webhooks/index.tsx @@ -8,6 +8,7 @@ import WebhookIcon from "assets/icons/Webhook"; import Modal from "components/Modal"; import WebhookList from "./WebhookList"; import WebhookModal from "./WebhookModal"; +import WebhookLogs from "./WebhookLogs"; import { useProjectContext } from "contexts/ProjectContext"; import { useAppContext } from "contexts/AppContext"; @@ -33,6 +34,7 @@ export default function Webhooks() { webhookObject: IWebhook; index?: number; } | null>(null); + const [webhookLogs, setWebhookLogs] = useState(); const edited = !_isEqual(currentwebhooks, localWebhooksObjects); @@ -130,18 +132,12 @@ export default function Webhooks() { ); }; - const handleDuplicate = (index: number) => { - setLocalWebhooksObjects([ - ...localWebhooksObjects, - { - ...localWebhooksObjects[index], - name: `${localWebhooksObjects[index].name} (duplicate)`, - active: false, - lastEditor: currentEditor(), - }, - ]); - analytics.logEvent("duplicated_webhook", { - type: localWebhooksObjects[index].type, + const handleOpenLogs = (index: number) => { + const _webhook = localWebhooksObjects[index]; + + setWebhookLogs(_webhook); + analytics.logEvent("view_webhook_logs", { + type: _webhook.type, }); }; @@ -203,7 +199,7 @@ export default function Webhooks() { }} handleUpdateActive={handleUpdateActive} handleEdit={handleEdit} - handleDuplicate={handleDuplicate} + handleOpenLogs={handleOpenLogs} handleDelete={handleDelete} /> @@ -234,6 +230,14 @@ export default function Webhooks() { webhookObject={webhookModal.webhookObject} /> )} + {webhookLogs && ( + { + setWebhookLogs(null); + }} + /> + )} ); } From e10d5976f19b055bb81537f9a436a1965d141f24 Mon Sep 17 00:00:00 2001 From: shamsmosowi Date: Wed, 27 Oct 2021 23:59:29 +1100 Subject: [PATCH 05/13] fns log --- src/constants/runRoutes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/constants/runRoutes.ts b/src/constants/runRoutes.ts index 8e3e209e..3bfc40eb 100644 --- a/src/constants/runRoutes.ts +++ b/src/constants/runRoutes.ts @@ -52,4 +52,5 @@ export const runRoutes = { deleteUser: { path: "/deleteUser", method: "DELETE" } as RunRoute, algoliaSearchKey: { path: `/algoliaSearchKey`, method: "GET" } as RunRoute, algoliaAppId: { path: `/algoliaAppId`, method: "GET" } as RunRoute, + functionLogs: { path: `/functionLogs`, method: "GET" } as RunRoute, } as const; From d645db9cbfb9ca1c514e014304016d2a44b237f1 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Fri, 29 Oct 2021 11:52:13 +1100 Subject: [PATCH 06/13] standardize imports to use "@src/ --- .../Table/TableHeader/Webhooks/Step1Secret.tsx | 2 +- .../Table/TableHeader/Webhooks/Step2Conditions.tsx | 6 +++--- .../Table/TableHeader/Webhooks/Step3Parser.tsx | 6 +++--- .../Table/TableHeader/Webhooks/WebhookList.tsx | 10 +++++----- .../Table/TableHeader/Webhooks/WebhookLogs.tsx | 4 ++-- .../Table/TableHeader/Webhooks/WebhookModal.tsx | 8 ++++---- src/components/Table/TableHeader/Webhooks/index.tsx | 12 ++++++------ 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/components/Table/TableHeader/Webhooks/Step1Secret.tsx b/src/components/Table/TableHeader/Webhooks/Step1Secret.tsx index 38070adc..8459c03a 100644 --- a/src/components/Table/TableHeader/Webhooks/Step1Secret.tsx +++ b/src/components/Table/TableHeader/Webhooks/Step1Secret.tsx @@ -1,5 +1,5 @@ import { IWebhookModalStepProps } from "./WebhookModal"; -import { useProjectContext } from "contexts/ProjectContext"; +import { useProjectContext } from "@src/contexts/ProjectContext"; import { FormControl, FormLabel, TextField, Typography } from "@mui/material"; export default function Step1Endpoint({ diff --git a/src/components/Table/TableHeader/Webhooks/Step2Conditions.tsx b/src/components/Table/TableHeader/Webhooks/Step2Conditions.tsx index 3bde07af..09903405 100644 --- a/src/components/Table/TableHeader/Webhooks/Step2Conditions.tsx +++ b/src/components/Table/TableHeader/Webhooks/Step2Conditions.tsx @@ -1,10 +1,10 @@ import { IWebhookModalStepProps } from "./WebhookModal"; import useStateRef from "react-usestateref"; -import CodeEditor from "components/CodeEditor"; -import CodeEditorHelper from "components/CodeEditor/CodeEditorHelper"; +import CodeEditor from "@src/components/CodeEditor"; +import CodeEditorHelper from "@src/components/CodeEditor/CodeEditorHelper"; -import { WIKI_LINKS } from "constants/externalLinks"; +import { WIKI_LINKS } from "@src/constants/externalLinks"; const additionalVariables = [ { diff --git a/src/components/Table/TableHeader/Webhooks/Step3Parser.tsx b/src/components/Table/TableHeader/Webhooks/Step3Parser.tsx index 7939dedb..73526035 100644 --- a/src/components/Table/TableHeader/Webhooks/Step3Parser.tsx +++ b/src/components/Table/TableHeader/Webhooks/Step3Parser.tsx @@ -2,10 +2,10 @@ import { IWebhookModalStepProps } from "./WebhookModal"; import _upperFirst from "lodash/upperFirst"; import useStateRef from "react-usestateref"; -import CodeEditor from "components/CodeEditor"; -import CodeEditorHelper from "components/CodeEditor/CodeEditorHelper"; +import CodeEditor from "@src/components/CodeEditor"; +import CodeEditorHelper from "@src/components/CodeEditor/CodeEditorHelper"; -import { WIKI_LINKS } from "constants/externalLinks"; +import { WIKI_LINKS } from "@src/constants/externalLinks"; const additionalVariables = [ { diff --git a/src/components/Table/TableHeader/Webhooks/WebhookList.tsx b/src/components/Table/TableHeader/Webhooks/WebhookList.tsx index fd8af180..761f6d13 100644 --- a/src/components/Table/TableHeader/Webhooks/WebhookList.tsx +++ b/src/components/Table/TableHeader/Webhooks/WebhookList.tsx @@ -1,6 +1,6 @@ import { useState, useRef } from "react"; import { format, formatRelative } from "date-fns"; -import CopyIcon from "assets/icons/Copy"; +import CopyIcon from "@src/assets/icons/Copy"; import { Stack, @@ -18,14 +18,14 @@ import { Typography, } from "@mui/material"; import AddIcon from "@mui/icons-material/Add"; -import WebhookIcon from "assets/icons/Webhook"; -import LogsIcon from "assets/icons/CloudLogs"; +import WebhookIcon from "@src/assets/icons/Webhook"; +import LogsIcon from "@src/assets/icons/CloudLogs"; import EditIcon from "@mui/icons-material/EditOutlined"; import DeleteIcon from "@mui/icons-material/DeleteOutlined"; -import EmptyState from "components/EmptyState"; +import EmptyState from "@src/components/EmptyState"; import { webhookTypes, webhookNames, IWebhook, WebhookType } from "./utils"; -import { DATE_TIME_FORMAT } from "constants/dates"; +import { DATE_TIME_FORMAT } from "@src/constants/dates"; import { useProjectContext } from "@src/contexts/ProjectContext"; export interface IWebhookListProps { diff --git a/src/components/Table/TableHeader/Webhooks/WebhookLogs.tsx b/src/components/Table/TableHeader/Webhooks/WebhookLogs.tsx index 9dcb702e..a7bf1c1c 100644 --- a/src/components/Table/TableHeader/Webhooks/WebhookLogs.tsx +++ b/src/components/Table/TableHeader/Webhooks/WebhookLogs.tsx @@ -1,12 +1,12 @@ import _isEqual from "lodash/isEqual"; import _upperFirst from "lodash/upperFirst"; -import Modal, { IModalProps } from "components/Modal"; +import Modal, { IModalProps } from "@src/components/Modal"; import { IWebhook } from "./utils"; import useCollection from "@src/hooks/useCollection"; import { useEffect } from "react"; -import { useProjectContext } from "contexts/ProjectContext"; +import { useProjectContext } from "@src/contexts/ProjectContext"; import { Typography } from "@mui/material"; import { orderBy } from "lodash"; diff --git a/src/components/Table/TableHeader/Webhooks/WebhookModal.tsx b/src/components/Table/TableHeader/Webhooks/WebhookModal.tsx index 8cf99b57..8aef63e6 100644 --- a/src/components/Table/TableHeader/Webhooks/WebhookModal.tsx +++ b/src/components/Table/TableHeader/Webhooks/WebhookModal.tsx @@ -16,17 +16,17 @@ import { Link, } from "@mui/material"; import ExpandIcon from "@mui/icons-material/KeyboardArrowDown"; -import InlineOpenInNewIcon from "components/InlineOpenInNewIcon"; +import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; -import Modal, { IModalProps } from "components/Modal"; +import Modal, { IModalProps } from "@src/components/Modal"; import Step1Secret from "./Step1Secret"; import Step2Conditions from "./Step2Conditions"; import Step3Body from "./Step3Parser"; -import { useConfirmation } from "components/ConfirmationDialog"; +import { useConfirmation } from "@src/components/ConfirmationDialog"; import { webhookNames, IWebhook } from "./utils"; -import { WIKI_LINKS } from "constants/externalLinks"; +import { WIKI_LINKS } from "@src/constants/externalLinks"; type StepValidation = Record<"condition" | "parser", boolean>; export interface IWebhookModalStepProps { diff --git a/src/components/Table/TableHeader/Webhooks/index.tsx b/src/components/Table/TableHeader/Webhooks/index.tsx index 44c761b1..22a3437f 100644 --- a/src/components/Table/TableHeader/Webhooks/index.tsx +++ b/src/components/Table/TableHeader/Webhooks/index.tsx @@ -4,18 +4,18 @@ import _isEqual from "lodash/isEqual"; import { Breadcrumbs } from "@mui/material"; import TableHeaderButton from "../TableHeaderButton"; -import WebhookIcon from "assets/icons/Webhook"; -import Modal from "components/Modal"; +import WebhookIcon from "@src/assets/icons/Webhook"; +import Modal from "@src/components/Modal"; import WebhookList from "./WebhookList"; import WebhookModal from "./WebhookModal"; import WebhookLogs from "./WebhookLogs"; -import { useProjectContext } from "contexts/ProjectContext"; -import { useAppContext } from "contexts/AppContext"; -import { useConfirmation } from "components/ConfirmationDialog"; +import { useProjectContext } from "@src/contexts/ProjectContext"; +import { useAppContext } from "@src/contexts/AppContext"; +import { useConfirmation } from "@src/components/ConfirmationDialog"; import { emptyWebhookObject, IWebhook, WebhookType } from "./utils"; -import { runRoutes } from "constants/runRoutes"; +import { runRoutes } from "@src/constants/runRoutes"; import { analytics } from "@src/analytics"; import { useSnackbar } from "notistack"; From 5cf9c4d0f8c9a690334e2f641068acaf44e32e15 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Tue, 2 Nov 2021 16:56:51 +1100 Subject: [PATCH 07/13] remove old Table/Settings/Webhooks file --- src/components/Table/Settings/Webhooks.tsx | 186 --------------------- 1 file changed, 186 deletions(-) delete mode 100644 src/components/Table/Settings/Webhooks.tsx diff --git a/src/components/Table/Settings/Webhooks.tsx b/src/components/Table/Settings/Webhooks.tsx deleted file mode 100644 index 7bc09cb4..00000000 --- a/src/components/Table/Settings/Webhooks.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { makeStyles, createStyles } from "@mui/styles"; - -import Button from "@mui/material/Button"; -import Dialog, { DialogProps } from "@mui/material/Dialog"; -import DialogActions from "@mui/material/DialogActions"; -import DialogContent from "@mui/material/DialogContent"; - -import DialogTitle from "@mui/material/DialogTitle"; -import FormControl from "@mui/material/FormControl"; -import Typography from "@mui/material/Typography"; - -import FormControlLabel from "@mui/material/FormControlLabel"; -import InputLabel from "@mui/material/InputLabel"; -import MenuItem from "@mui/material/MenuItem"; -import Select from "@mui/material/Select"; -import Switch from "@mui/material/Switch"; -// import CodeEditor from "../editors/CodeEditor"; -import { useProjectContext } from "@src/contexts/ProjectContext"; -import { makeId } from "../../../utils/fns"; - -const useStyles = makeStyles((theme) => - createStyles({ - form: { - display: "flex", - flexDirection: "column", - margin: "auto", - width: "fit-content", - }, - formControl: { - marginTop: theme.spacing(2), - minWidth: 120, - }, - formControlLabel: { - marginTop: theme.spacing(1), - }, - }) -); - -enum WebhookTypes { - custom = "CUSTOM", - typeForm = "TYPE_FORM", -} -const EmptyState = { - enabled: false, - type: WebhookTypes.custom, - secret: "", - customParser: "", -}; -export default function WebhooksDialog({ open, handleClose }) { - const classes = useStyles(); - - const { tableState, tableActions } = useProjectContext(); - - const [state, setState] = useState<{ - enabled: boolean; - type: WebhookTypes; - secret: string; - customParser: string; - }>(EmptyState); - const tableFields = Object.keys(tableState?.columns as any); - const fullWidth = true; - const maxWidth: DialogProps["maxWidth"] = "xl"; - const handleChange = (key: string) => (value: any) => { - setState((s) => ({ ...s, [key]: value })); - }; - const initializeWebhooksConfig = () => { - const secret = makeId(32); - handleChange("secret")(secret); - setState({ ...EmptyState, secret }); - tableActions?.table.updateConfig("webhooks", { - enabled: false, - type: WebhookTypes.custom, - secret, - customParser: "", // TODO: add a boilerplate/example - }); - }; - useEffect(() => { - if ( - tableState && - !tableState.config.tableConfig.loading && - !tableState?.config.webhooks && - !state.secret - ) { - initializeWebhooksConfig(); - } else if (tableState?.config.webhooks) { - setState({ ...tableState?.config.webhooks }); - } - }, [tableState?.config]); - - const handleWebhookTypeChange = ( - event: React.ChangeEvent<{ value: unknown }> - ) => { - handleChange("type")(event.target.value as WebhookTypes); - }; - - const handleSave = async () => { - handleClose(); - await tableActions?.table.updateConfig("webhooks", { - ...state, - }); - }; - const handleCancel = () => { - handleClose(); - setState({ ...tableState?.config.webhooks }); - }; - return ( - - - Webhooks - - - } - label={"Enable webhooks for this table"} - labelPlacement="end" - checked={state.enabled} - onChange={() => { - handleChange("enabled")(!state.enabled); - }} - sx={{ - alignItems: "center", - "& .MuiFormControlLabel-label": { mt: 0 }, - }} - // classes={{ root: classes.formControlLabel, label: classes.label }} - /> - Webhook type - - - {/* {state.type === WebhookTypes.custom && ( - - )} */} -
- {state.type === WebhookTypes.typeForm && ( - <> - Web hook url: - - {/* {WEBHOOK_URL}?tablePath={tableState?.tablePath} - &type=TYPE_FORM&secret={state.secret} */} - - instructions: - - please set the question reference in typeform to the following - field keys :{" "} - {tableFields.map((key) => ( - <> - {" "} - {key}, - - ))} - - - )} -
- - - - -
-
- ); -} From 4c6365fccbb6d85c1b382d8af471404e48399851 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Tue, 2 Nov 2021 16:57:25 +1100 Subject: [PATCH 08/13] remove components/Table/Settings --- src/components/Table/Settings/Menu.tsx | 61 ------------------------- src/components/Table/Settings/index.tsx | 17 ------- 2 files changed, 78 deletions(-) delete mode 100644 src/components/Table/Settings/Menu.tsx delete mode 100644 src/components/Table/Settings/index.tsx diff --git a/src/components/Table/Settings/Menu.tsx b/src/components/Table/Settings/Menu.tsx deleted file mode 100644 index 010a03d8..00000000 --- a/src/components/Table/Settings/Menu.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from "react"; -import IconButton from "@mui/material/IconButton"; -import Menu from "@mui/material/Menu"; -import MenuItem from "@mui/material/MenuItem"; -import MoreVertIcon from "@mui/icons-material/MoreVert"; - -const options = ["Webhooks", "Rules", "Algolia", "CollectionSync"]; - -const ITEM_HEIGHT = 48; - -export default function SettingsMenu({ modal, setModal }) { - const [anchorEl, setAnchorEl] = React.useState(null); - const open = Boolean(anchorEl); - - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = (option: string) => () => { - setModal(option); - setAnchorEl(null); - }; - - return ( -
- - - - - {options.map((option) => ( - - {option} - - ))} - -
- ); -} diff --git a/src/components/Table/Settings/index.tsx b/src/components/Table/Settings/index.tsx deleted file mode 100644 index fedfbda9..00000000 --- a/src/components/Table/Settings/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useState } from "react"; -import SettingsMenu from "./Menu"; -//import Webhooks from "./Webhooks"; -export default function Settings() { - const [modal, setModal] = useState(""); - return ( - <> - - {/* { - setModal(""); - }} - /> */} - - ); -} From b128c9b17c23e5181e8bd55b859128e47750022e Mon Sep 17 00:00:00 2001 From: shamsmosowi Date: Wed, 3 Nov 2021 09:10:24 +1100 Subject: [PATCH 09/13] update cell audit logs --- src/constants/runRoutes.ts | 1 + src/contexts/ProjectContext.tsx | 27 +++++++++++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/constants/runRoutes.ts b/src/constants/runRoutes.ts index 3bfc40eb..f6d7c2fa 100644 --- a/src/constants/runRoutes.ts +++ b/src/constants/runRoutes.ts @@ -53,4 +53,5 @@ export const runRoutes = { algoliaSearchKey: { path: `/algoliaSearchKey`, method: "GET" } as RunRoute, algoliaAppId: { path: `/algoliaAppId`, method: "GET" } as RunRoute, functionLogs: { path: `/functionLogs`, method: "GET" } as RunRoute, + auditChange: { path: `/auditChange`, method: "POST" } as RunRoute, } as const; diff --git a/src/contexts/ProjectContext.tsx b/src/contexts/ProjectContext.tsx index f298aebe..9f5769bd 100644 --- a/src/contexts/ProjectContext.tsx +++ b/src/contexts/ProjectContext.tsx @@ -19,6 +19,7 @@ import { rowyRun, IRowyRunRequestProps } from "@src/utils/rowyRun"; import { FieldType } from "@src/constants/fields"; import { rowyUser } from "@src/utils/fns"; import { WIKI_LINKS } from "@src/constants/externalLinks"; +import { runRoutes } from "@src/constants/runRoutes"; export type Table = { id: string; @@ -169,6 +170,14 @@ export const ProjectContextProvider: React.FC = ({ children }) => { initialData[table?.auditFieldUpdatedBy || "_updatedBy"] = rowyUser( currentUser! ); + // _rowyRun({route:runRoutes.auditChange,body:{ + // rowyUser, + // eventType:"ADD_ROW", + // eventData:{ + // rowPath:ref.path, + // tableId:table?.id, + // } + // }}) } tableActions.row.add( @@ -188,10 +197,20 @@ export const ProjectContextProvider: React.FC = ({ children }) => { const update = { [fieldName]: value }; if (table?.audit !== false) { - update[table?.auditFieldUpdatedBy || "_updatedBy"] = rowyUser( - currentUser!, - { updatedField: fieldName } - ); + const _rowyUser = rowyUser(currentUser!, { updatedField: fieldName }); + update[table?.auditFieldUpdatedBy || "_updatedBy"] = _rowyUser; + _rowyRun({ + route: runRoutes.auditChange, + body: { + rowyUser: _rowyUser, + eventType: "UPDATE_CELL", + eventData: { + rowPath: ref.path, + tableId: table?.id, + updatedField: fieldName, + }, + }, + }); } tableActions.row.update( From f10eb6e65acb779363a421ac6bd6429df51efaae Mon Sep 17 00:00:00 2001 From: shamsmosowi Date: Wed, 3 Nov 2021 19:13:41 +1100 Subject: [PATCH 10/13] auditing --- .../Table/formatters/FinalColumn.tsx | 11 ++-- src/contexts/ProjectContext.tsx | 60 +++++++++++-------- src/firebase/firebaseui.ts | 8 --- src/hooks/useTable/useTableData.tsx | 17 ++++-- 4 files changed, 53 insertions(+), 43 deletions(-) diff --git a/src/components/Table/formatters/FinalColumn.tsx b/src/components/Table/formatters/FinalColumn.tsx index fa7d4278..04134d0b 100644 --- a/src/components/Table/formatters/FinalColumn.tsx +++ b/src/components/Table/formatters/FinalColumn.tsx @@ -28,11 +28,11 @@ export default function FinalColumn({ row }: FormatterProps) { useStyles(); const { requestConfirmation } = useConfirmation(); - const { tableActions, addRow } = useProjectContext(); + const { deleteRow, addRow } = useProjectContext(); const altPress = useKeyPress("Alt"); - - const handleDelete = () => tableActions!.row.delete(row.id); - + const handleDelete = () => { + if (deleteRow) deleteRow(row.id); + }; return ( @@ -48,7 +48,7 @@ export default function FinalColumn({ row }: FormatterProps) { Object.keys(clonedRow).forEach((key) => { if (clonedRow[key] === undefined) delete clonedRow[key]; }); - if (tableActions) addRow!(clonedRow); + if (addRow) addRow!(clonedRow); }} aria-label="Duplicate row" className="row-hover-iconButton" @@ -61,7 +61,6 @@ export default function FinalColumn({ row }: FormatterProps) { , ignoreRequiredFields?: boolean) => void; + deleteRow: (rowId) => void; updateCell: ( ref: firebase.firestore.DocumentReference, fieldName: string, @@ -137,7 +138,28 @@ export const ProjectContextProvider: React.FC = ({ children }) => { : [], [tables] ); - + const auditChange = ( + type: "ADD_ROW" | "UPDATE_CELL" | "DELETE_ROW", + rowId, + data + ) => { + if (table?.audit !== false) { + _rowyRun({ + route: runRoutes.auditChange, + body: { + rowyUser: rowyUser(currentUser!), + type, + ref: { + rowPath: tableState.tablePath, + rowId, + tableId: table?.id, + collectionPath: tableState.tablePath, + }, + data, + }, + }); + } + }; const addRow: IProjectContext["addRow"] = (data, ignoreRequiredFields) => { const valuesFromFilter = tableState.filters.reduce((acc, curr) => { if (curr.operator === "==") { @@ -170,19 +192,12 @@ export const ProjectContextProvider: React.FC = ({ children }) => { initialData[table?.auditFieldUpdatedBy || "_updatedBy"] = rowyUser( currentUser! ); - // _rowyRun({route:runRoutes.auditChange,body:{ - // rowyUser, - // eventType:"ADD_ROW", - // eventData:{ - // rowPath:ref.path, - // tableId:table?.id, - // } - // }}) } tableActions.row.add( { ...valuesFromFilter, ...initialData, ...data }, - ignoreRequiredFields ? [] : requiredFields + ignoreRequiredFields ? [] : requiredFields, + (rowId: string) => auditChange("ADD_ROW", rowId, {}) ); }; @@ -197,26 +212,16 @@ export const ProjectContextProvider: React.FC = ({ children }) => { const update = { [fieldName]: value }; if (table?.audit !== false) { - const _rowyUser = rowyUser(currentUser!, { updatedField: fieldName }); - update[table?.auditFieldUpdatedBy || "_updatedBy"] = _rowyUser; - _rowyRun({ - route: runRoutes.auditChange, - body: { - rowyUser: _rowyUser, - eventType: "UPDATE_CELL", - eventData: { - rowPath: ref.path, - tableId: table?.id, - updatedField: fieldName, - }, - }, - }); + update[table?.auditFieldUpdatedBy || "_updatedBy"] = rowyUser( + currentUser!, + { updatedField: fieldName } + ); } - tableActions.row.update( ref, update, () => { + auditChange("UPDATE_CELL", ref.id, { updatedField: fieldName }); if (onSuccess) onSuccess(ref, fieldName, value); }, (error) => { @@ -232,6 +237,10 @@ export const ProjectContextProvider: React.FC = ({ children }) => { } ); }; + + const deleteRow = (rowId) => { + tableActions.row.delete(rowId, () => auditChange("DELETE_ROW", rowId, {})); + }; // rowyRun access const _rowyRun: IProjectContext["rowyRun"] = async (args) => { const authToken = await getAuthToken(); @@ -272,6 +281,7 @@ export const ProjectContextProvider: React.FC = ({ children }) => { tableActions, addRow, updateCell, + deleteRow, settingsActions, settings: settings.doc, roles, diff --git a/src/firebase/firebaseui.ts b/src/firebase/firebaseui.ts index 64a75289..b7a9ae52 100644 --- a/src/firebase/firebaseui.ts +++ b/src/firebase/firebaseui.ts @@ -7,14 +7,6 @@ import githubLogo from "@src/assets/logos/github.svg"; import appleLogo from "@src/assets/logos/apple.svg"; import yahooLogo from "@src/assets/logos/yahoo.svg"; -import { mdiGoogle } from "@mdi/js"; -console.log( - `data:image/svg+xml;utf8,` + - encodeURIComponent( - `` - ) -); - export const authOptions = { google: { provider: firebase.auth.GoogleAuthProvider.PROVIDER_ID, diff --git a/src/hooks/useTable/useTableData.tsx b/src/hooks/useTable/useTableData.tsx index 5369dc1d..d8625916 100644 --- a/src/hooks/useTable/useTableData.tsx +++ b/src/hooks/useTable/useTableData.tsx @@ -231,12 +231,12 @@ const useTableData = () => { * @param rowIndex local position * @param documentId firestore document id */ - const deleteRow = (rowId: string) => { + const deleteRow = async (rowId: string, onSuccess: () => void) => { // Remove row locally rowsDispatch({ type: "delete", rowId }); // Delete document try { - db.collection(tableState.path).doc(rowId).delete(); + await db.collection(tableState.path).doc(rowId).delete().then(onSuccess); } catch (error: any) { console.log(error); if (error.code === "permission-denied") { @@ -264,7 +264,11 @@ const useTableData = () => { /** creating new document/row * @param data(optional: default will create empty row) */ - const addRow = async (data: any, requiredFields: string[]) => { + const addRow = ( + data: any, + requiredFields: string[], + onSuccess: (rowId: string) => void + ) => { const missingRequiredFields = requiredFields ? requiredFields.reduce(missingFieldsReducer(data), []) : []; @@ -274,7 +278,12 @@ const useTableData = () => { if (missingRequiredFields.length === 0) { try { - await db.collection(path).doc(newId).set(data, { merge: true }); + db.collection(path) + .doc(newId) + .set(data, { merge: true }) + .then(() => { + onSuccess(newId); + }); } catch (error: any) { if (error.code === "permission-denied") { enqueueSnackbar("You do not have the permissions to add new rows.", { From 4a8122c88ce29499dee85038f7f6cb7e79ec1981 Mon Sep 17 00:00:00 2001 From: shamsmosowi Date: Thu, 4 Nov 2021 12:48:49 +1100 Subject: [PATCH 11/13] fix monaco row definition --- src/components/CodeEditor/useMonacoCustomizations.ts | 5 ++++- .../Table/TableHeader/Extensions/Step4Body.tsx | 10 +++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/CodeEditor/useMonacoCustomizations.ts b/src/components/CodeEditor/useMonacoCustomizations.ts index 3c5b0943..a7445b7e 100644 --- a/src/components/CodeEditor/useMonacoCustomizations.ts +++ b/src/components/CodeEditor/useMonacoCustomizations.ts @@ -110,7 +110,10 @@ export default function useMonacoCustomizations({ Object.keys(tableState?.columns!) .map((columnKey: string) => { const column = tableState?.columns[columnKey]; - return `static ${columnKey}: ${getFieldProp("type", column.type)}`; + return `static "${columnKey}": ${getFieldProp( + "dataType", + column.type + )}`; }) .join(";\n") + ";"; diff --git a/src/components/Table/TableHeader/Extensions/Step4Body.tsx b/src/components/Table/TableHeader/Extensions/Step4Body.tsx index bcc27c19..bcb88357 100644 --- a/src/components/Table/TableHeader/Extensions/Step4Body.tsx +++ b/src/components/Table/TableHeader/Extensions/Step4Body.tsx @@ -61,11 +61,11 @@ export default function Step4Body({ extensionBody: isValid, }); }} - diagnosticsOptions={{ - noSemanticValidation: false, - noSyntaxValidation: false, - noSuggestionDiagnostics: true, - }} + // diagnosticsOptions={{ + // noSemanticValidation: false, + // noSyntaxValidation: false, + // noSuggestionDiagnostics: true, + // }} onMount={() => setBodyEditorActive(true)} onUnmount={() => setBodyEditorActive(false)} /> From 745e5d96de1263ac1751126dd145acdec22e90ee Mon Sep 17 00:00:00 2001 From: shamsmosowi Date: Thu, 4 Nov 2021 12:49:36 +1100 Subject: [PATCH 12/13] rowyRun version requirement --- package.json | 1 + .../Table/TableHeader/Webhooks/index.tsx | 5 +-- src/contexts/ProjectContext.tsx | 34 +++++++++++++++++-- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index ad8cf3cc..759a315a 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "react-router-hash-link": "^2.4.3", "react-scripts": "^4.0.3", "react-usestateref": "^1.0.5", + "semver": "^7.3.5", "serve": "^11.3.2", "swr": "^1.0.1", "tinymce": "^5.9.2", diff --git a/src/components/Table/TableHeader/Webhooks/index.tsx b/src/components/Table/TableHeader/Webhooks/index.tsx index 22a3437f..d9832855 100644 --- a/src/components/Table/TableHeader/Webhooks/index.tsx +++ b/src/components/Table/TableHeader/Webhooks/index.tsx @@ -20,7 +20,8 @@ import { analytics } from "@src/analytics"; import { useSnackbar } from "notistack"; export default function Webhooks() { - const { tableState, tableActions, rowyRun } = useProjectContext(); + const { tableState, tableActions, rowyRun, compatibleRowyRunVersion } = + useProjectContext(); const appContext = useAppContext(); const { requestConfirmation } = useConfirmation(); const { enqueueSnackbar } = useSnackbar(); @@ -35,7 +36,7 @@ export default function Webhooks() { index?: number; } | null>(null); const [webhookLogs, setWebhookLogs] = useState(); - + if (!compatibleRowyRunVersion?.({ minVersion: "1.1.1" })) return <>; const edited = !_isEqual(currentWebhooks, localWebhooksObjects); const tablePathTokens = diff --git a/src/contexts/ProjectContext.tsx b/src/contexts/ProjectContext.tsx index 44cdb175..52b3ab19 100644 --- a/src/contexts/ProjectContext.tsx +++ b/src/contexts/ProjectContext.tsx @@ -16,11 +16,10 @@ import { ColumnMenuRef } from "@src/components/Table/ColumnMenu"; import { ImportWizardRef } from "@src/components/Wizards/ImportWizard"; import { rowyRun, IRowyRunRequestProps } from "@src/utils/rowyRun"; -import { FieldType } from "@src/constants/fields"; import { rowyUser } from "@src/utils/fns"; import { WIKI_LINKS } from "@src/constants/externalLinks"; import { runRoutes } from "@src/constants/runRoutes"; - +import semver from "semver"; export type Table = { id: string; collection: string; @@ -76,6 +75,10 @@ interface IProjectContext { deleteTable: (id: string) => void; }; + compatibleRowyRunVersion: (args: { + minVersion?: string; + maxVersion?: string; + }) => boolean; // A ref to the data grid. Contains data grid functions dataGridRef: React.RefObject; // A ref to the side drawer state. Prevents unnecessary re-renders @@ -104,6 +107,17 @@ export const ProjectContextProvider: React.FC = ({ children }) => { const [settings, settingsActions] = useSettings(); const table = _find(tables, (table) => table.id === tableState.config.id); + const [rowyRunVersion, setRowyRunVersion] = useState(""); + useEffect(() => { + if (settings?.doc?.rowyRunUrl) { + _rowyRun({ + route: runRoutes.version, + }).then((resp) => { + if (resp.version) setRowyRunVersion(resp.version); + }); + } + }, [settings?.doc?.rowyRunUrl]); + useEffect(() => { const { tables } = settings; if (tables && userRoles) { @@ -268,6 +282,21 @@ export const ProjectContextProvider: React.FC = ({ children }) => { } }; + const compatibleRowyRunVersion = ({ + minVersion, + maxVersion, + }: { + minVersion?: string; + maxVersion?: string; + }) => { + // example: "1.0.0", "1.0.0-beta.1", "1.0.0-rc.1+1" + const version = rowyRunVersion.split("-")[0]; + console.log(version, minVersion, maxVersion); + if (!version) return false; + if (minVersion && semver.lt(version, minVersion)) return false; + if (maxVersion && semver.gt(version, maxVersion)) return false; + return true; + }; // A ref to the data grid. Contains data grid functions const dataGridRef = useRef(null); const sideDrawerRef = useRef(); @@ -292,6 +321,7 @@ export const ProjectContextProvider: React.FC = ({ children }) => { columnMenuRef, importWizardRef, rowyRun: _rowyRun, + compatibleRowyRunVersion, }} > {children} From 38f709ee83923075829d5064e6295deca1a12f32 Mon Sep 17 00:00:00 2001 From: shamsmosowi Date: Thu, 4 Nov 2021 13:09:05 +1100 Subject: [PATCH 13/13] auditing rr min version --- src/contexts/ProjectContext.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/contexts/ProjectContext.tsx b/src/contexts/ProjectContext.tsx index 52b3ab19..de71fac6 100644 --- a/src/contexts/ProjectContext.tsx +++ b/src/contexts/ProjectContext.tsx @@ -157,7 +157,10 @@ export const ProjectContextProvider: React.FC = ({ children }) => { rowId, data ) => { - if (table?.audit !== false) { + if ( + table?.audit !== false && + compatibleRowyRunVersion({ minVersion: "1.1.1" }) + ) { _rowyRun({ route: runRoutes.auditChange, body: { @@ -291,7 +294,6 @@ export const ProjectContextProvider: React.FC = ({ children }) => { }) => { // example: "1.0.0", "1.0.0-beta.1", "1.0.0-rc.1+1" const version = rowyRunVersion.split("-")[0]; - console.log(version, minVersion, maxVersion); if (!version) return false; if (minVersion && semver.lt(version, minVersion)) return false; if (maxVersion && semver.gt(version, maxVersion)) return false;