diff --git a/src/App.tsx b/src/App.tsx
index 653b6a88..9612ff03 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -41,6 +41,12 @@ const SetupPage = lazy(() => import("@src/pages/SetupPage" /* webpackChunkName:
const Navigation = lazy(() => import("@src/layouts/Navigation" /* webpackChunkName: "Navigation" */));
// prettier-ignore
const TableSettingsDialog = lazy(() => import("@src/components/TableSettingsDialog" /* webpackChunkName: "TableSettingsDialog" */));
+const ProjectSettingsDialog = lazy(
+ () =>
+ import(
+ "@src/components/ProjectSettingsDialog" /* webpackChunkName: "ProjectSettingsDialog" */
+ )
+);
// prettier-ignore
const TablesPage = lazy(() => import("@src/pages/TablesPage" /* webpackChunkName: "TablesPage" */));
@@ -99,6 +105,7 @@ export default function App() {
+
}
diff --git a/src/atoms/projectScope/ui.ts b/src/atoms/projectScope/ui.ts
index 48af64b0..01fc1499 100644
--- a/src/atoms/projectScope/ui.ts
+++ b/src/atoms/projectScope/ui.ts
@@ -131,6 +131,26 @@ export const tableSettingsDialogAtom = atom(
}
);
+export type ProjectSettingsDialogTab =
+ | "general"
+ | "rowy-run"
+ | "services"
+ | "secrets";
+export type ProjectSettingsDialogState = {
+ open: boolean;
+ tab: ProjectSettingsDialogTab;
+};
+export const projectSettingsDialogAtom = atom(
+ { open: false, tab: "secrets" } as ProjectSettingsDialogState,
+ (_, set, update?: Partial) => {
+ set(projectSettingsDialogAtom, {
+ open: true,
+ tab: "secrets",
+ ...update,
+ });
+ }
+);
+
/**
* Store the current ID of the table being edited in tableSettingsDialog
* to derive tableSettingsDialogSchemaAtom
diff --git a/src/atoms/projectScope/user.ts b/src/atoms/projectScope/user.ts
index 62bcc175..5c03281e 100644
--- a/src/atoms/projectScope/user.ts
+++ b/src/atoms/projectScope/user.ts
@@ -30,6 +30,12 @@ export const themeOverriddenAtom = atomWithStorage(
false
);
+/** User's default table settings (affecting saving and popup behavior) */
+export const defaultTableSettingsAtom = atom((get) => {
+ const userSettings = get(userSettingsAtom);
+ return userSettings.defaultTableSettings;
+});
+
/** Customized base theme based on project and user settings */
export const customizedThemesAtom = atom((get) => {
const publicSettings = get(publicSettingsAtom);
diff --git a/src/components/CodeEditor/extensions.d.ts b/src/components/CodeEditor/extensions.d.ts
index 22af5756..d57c5e21 100644
--- a/src/components/CodeEditor/extensions.d.ts
+++ b/src/components/CodeEditor/extensions.d.ts
@@ -128,4 +128,15 @@ type PushNotificationRequest = {
type PushNotificationBody = (
context: ExtensionContext
) => Message | Message[] | Promise;
+
type TaskBody = (context: ExtensionContext) => Promise;
+
+type BuildshipAuthenticatedTriggerBody = (
+ context: ExtensionContext
+) => Promise<{
+ buildshipConfig: {
+ projectId: string;
+ workflowId: string;
+ };
+ body: string;
+}>;
diff --git a/src/components/ProjectSettingsDialog/ProjectSettingsDialog.tsx b/src/components/ProjectSettingsDialog/ProjectSettingsDialog.tsx
new file mode 100644
index 00000000..49e9c1c8
--- /dev/null
+++ b/src/components/ProjectSettingsDialog/ProjectSettingsDialog.tsx
@@ -0,0 +1,282 @@
+import React from "react";
+import { useAtom } from "jotai";
+import {
+ projectScope,
+ projectSettingsDialogAtom,
+ ProjectSettingsDialogTab,
+ rowyRunAtom,
+ secretNamesAtom,
+ updateSecretNamesAtom,
+} from "@src/atoms/projectScope";
+import Modal from "@src/components/Modal";
+import { Box, Button, Paper, Tab, Tooltip, Typography } from "@mui/material";
+import { TabContext, TabPanel, TabList } from "@mui/lab";
+import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
+import EditIcon from "@mui/icons-material/Edit";
+import SecretDetailsModal from "./SecretDetailsModal";
+import { runRoutes } from "@src/constants/runRoutes";
+
+export default function ProjectSettingsDialog() {
+ const [{ open, tab }, setProjectSettingsDialog] = useAtom(
+ projectSettingsDialogAtom,
+ projectScope
+ );
+ const [secretNames] = useAtom(secretNamesAtom, projectScope);
+ const [secretDetailsModal, setSecretDetailsModal] = React.useState<{
+ open: boolean;
+ loading?: boolean;
+ mode?: "add" | "edit" | "delete";
+ secretName?: string;
+ error?: string;
+ }>({
+ open: false,
+ });
+ const [rowyRun] = useAtom(rowyRunAtom, projectScope);
+ const [updateSecretNames] = useAtom(updateSecretNamesAtom, projectScope);
+
+ if (!open) return null;
+
+ const handleClose = () => {
+ setProjectSettingsDialog({ open: false });
+ };
+
+ const handleTabChange = (
+ event: React.SyntheticEvent,
+ newTab: ProjectSettingsDialogTab
+ ) => {
+ setProjectSettingsDialog({ tab: newTab });
+ };
+
+ console.log(secretDetailsModal);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ Secrets
+
+
+
+ {secretNames.secretNames?.map((secretName) => (
+
+
+ {secretName}
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+ >
+ }
+ />
+ {
+ setSecretDetailsModal({ ...secretDetailsModal, open: false });
+ }}
+ handleAdd={async (newSecretName, secretValue) => {
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ loading: true,
+ });
+ try {
+ await rowyRun({
+ route: runRoutes.addSecret,
+ body: {
+ name: newSecretName,
+ value: secretValue,
+ },
+ });
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ open: false,
+ loading: false,
+ });
+ // update secret name causes an unknown modal-related bug, to be fixed
+ // updateSecretNames?.();
+ } catch (error: any) {
+ console.error(error);
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ error: error.message,
+ });
+ }
+ }}
+ handleEdit={async (secretName, secretValue) => {
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ loading: true,
+ });
+ try {
+ await rowyRun({
+ route: runRoutes.editSecret,
+ body: {
+ name: secretName,
+ value: secretValue,
+ },
+ });
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ open: false,
+ loading: false,
+ });
+ // update secret name causes an unknown modal-related bug, to be fixed
+ // updateSecretNames?.();
+ } catch (error: any) {
+ console.error(error);
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ error: error.message,
+ });
+ }
+ }}
+ handleDelete={async (secretName) => {
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ loading: true,
+ });
+ try {
+ await rowyRun({
+ route: runRoutes.deleteSecret,
+ body: {
+ name: secretName,
+ },
+ });
+ console.log("Setting", {
+ ...secretDetailsModal,
+ open: false,
+ loading: false,
+ });
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ open: false,
+ loading: false,
+ });
+ // update secret name causes an unknown modal-related bug, to be fixed
+ // updateSecretNames?.();
+ } catch (error: any) {
+ console.error(error);
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ error: error.message,
+ });
+ }
+ }}
+ />
+ >
+ );
+}
diff --git a/src/components/ProjectSettingsDialog/SecretDetailsModal.tsx b/src/components/ProjectSettingsDialog/SecretDetailsModal.tsx
new file mode 100644
index 00000000..1e156429
--- /dev/null
+++ b/src/components/ProjectSettingsDialog/SecretDetailsModal.tsx
@@ -0,0 +1,157 @@
+import React, { useState } from "react";
+import Modal from "@src/components/Modal";
+import { Box, Button, TextField, Typography } from "@mui/material";
+import { capitalize } from "lodash-es";
+import LoadingButton from "@mui/lab/LoadingButton";
+
+export interface ISecretDetailsModalProps {
+ open: boolean;
+ loading?: boolean;
+ mode?: "add" | "edit" | "delete";
+ error?: string;
+ secretName?: string;
+ handleClose: () => void;
+ handleAdd: (secretName: string, secretValue: string) => void;
+ handleEdit: (secretName: string, secretValue: string) => void;
+ handleDelete: (secretName: string) => void;
+}
+
+export default function SecretDetailsModal({
+ open,
+ loading,
+ mode,
+ error,
+ secretName,
+ handleClose,
+ handleAdd,
+ handleEdit,
+ handleDelete,
+}: ISecretDetailsModalProps) {
+ const [newSecretName, setNewSecretName] = useState("");
+ const [secretValue, setSecretValue] = useState("");
+
+ return (
+
+ {mode === "add" && (
+
+ Secret Name
+ setNewSecretName(e.target.value)}
+ />
+
+ This will create a secret key on Google Cloud.
+
+
+ )}
+ {mode === "delete" ? (
+
+ Are you sure you want to delete this secret key {secretName}?
+
+ ) : (
+
+ Secret Value
+ setSecretValue(e.target.value)}
+ />
+
+ Paste your secret key here.
+
+
+ )}
+ {error?.length && (
+
+ {error}
+
+ )}
+
+
+ {
+ switch (mode) {
+ case "add":
+ handleAdd(newSecretName, secretValue);
+ break;
+ case "edit":
+ handleEdit(secretName ?? "", secretValue);
+ break;
+ case "delete":
+ handleDelete(secretName ?? "");
+ break;
+ }
+ }}
+ >
+ {mode === "delete" ? "Delete" : "Save"}
+
+
+
+ }
+ />
+ );
+}
diff --git a/src/components/ProjectSettingsDialog/index.ts b/src/components/ProjectSettingsDialog/index.ts
new file mode 100644
index 00000000..e57386dd
--- /dev/null
+++ b/src/components/ProjectSettingsDialog/index.ts
@@ -0,0 +1,2 @@
+export * from "./ProjectSettingsDialog";
+export { default } from "./ProjectSettingsDialog";
diff --git a/src/components/Settings/UserSettings/TableSettings.tsx b/src/components/Settings/UserSettings/TableSettings.tsx
new file mode 100644
index 00000000..6c4bfcb9
--- /dev/null
+++ b/src/components/Settings/UserSettings/TableSettings.tsx
@@ -0,0 +1,103 @@
+import { merge } from "lodash-es";
+import { IUserSettingsChildProps } from "@src/pages/Settings/UserSettingsPage";
+
+import {
+ FormControl,
+ FormControlLabel,
+ Divider,
+ Checkbox,
+ Collapse,
+} from "@mui/material";
+
+export default function TableSettings({
+ settings,
+ updateSettings,
+}: IUserSettingsChildProps) {
+ return (
+ <>
+
+ {
+ updateSettings({
+ defaultTableSettings: merge(settings.defaultTableSettings, {
+ saveSortsPopupDisabled: e.target.checked,
+ }),
+ });
+ }}
+ />
+ }
+ label="Disable popup - to save sorting changes to the team"
+ style={{ marginLeft: -11, marginBottom: 13 }}
+ />
+
+ {
+ updateSettings({
+ defaultTableSettings: merge(settings.defaultTableSettings, {
+ automaticallyApplySorts: e.target.checked,
+ }),
+ });
+ }}
+ />
+ }
+ label="Automatically apply sorting changes to all users"
+ style={{ marginLeft: 20, marginBottom: 10, marginTop: -13 }}
+ />
+
+
+
+
+ {
+ updateSettings({
+ defaultTableSettings: merge(settings.defaultTableSettings, {
+ saveColumnSizingPopupDisabled: e.target.checked,
+ }),
+ });
+ }}
+ />
+ }
+ label="Disable popup - to save column width changes to the team"
+ style={{ marginLeft: -11, marginTop: 13 }}
+ />
+
+ {
+ updateSettings({
+ defaultTableSettings: merge(settings.defaultTableSettings, {
+ automaticallyApplyColumnSizing: e.target.checked,
+ }),
+ });
+ }}
+ />
+ }
+ label="Automatically apply column width changes to all users"
+ style={{ marginLeft: 20 }}
+ />
+
+
+ >
+ );
+}
diff --git a/src/components/Table/ColumnHeader/useSaveTableSorts.tsx b/src/components/Table/ColumnHeader/useSaveTableSorts.tsx
index c0c239ed..76efdbef 100644
--- a/src/components/Table/ColumnHeader/useSaveTableSorts.tsx
+++ b/src/components/Table/ColumnHeader/useSaveTableSorts.tsx
@@ -1,5 +1,5 @@
import { useCallback, useState } from "react";
-import { useAtom } from "jotai";
+import { useAtom, useAtomValue } from "jotai";
import { SnackbarKey, useSnackbar } from "notistack";
import LoadingButton from "@mui/lab/LoadingButton";
@@ -11,17 +11,25 @@ import {
tableScope,
updateTableSchemaAtom,
} from "@src/atoms/tableScope";
-import { projectScope, updateUserSettingsAtom } from "@src/atoms/projectScope";
+import {
+ defaultTableSettingsAtom,
+ projectScope,
+ updateUserSettingsAtom,
+} from "@src/atoms/projectScope";
import { TableSort } from "@src/types/table";
function useSaveTableSorts(canEditColumns: boolean) {
const [updateTableSchema] = useAtom(updateTableSchemaAtom, tableScope);
const [updateUserSettings] = useAtom(updateUserSettingsAtom, projectScope);
const [tableId] = useAtom(tableIdAtom, tableScope);
+ const defaultTableSettings = useAtomValue(
+ defaultTableSettingsAtom,
+ projectScope
+ );
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
const [snackbarId, setSnackbarId] = useState(null);
- // Offer to save when table sorts changes
+ // Offer to save when table sorts changes, depending on user settings
const trigger = useCallback(
(sorts: TableSort[]) => {
if (!updateTableSchema) throw new Error("Cannot update table schema");
@@ -33,6 +41,15 @@ function useSaveTableSorts(canEditColumns: boolean) {
});
}
if (!canEditColumns) return;
+ // If the user has disabled the popup, return early
+ if (defaultTableSettings?.saveSortsPopupDisabled) {
+ // If the user has `automaticallyApplySorts` set to true, apply the sorting before returning
+ if (defaultTableSettings?.automaticallyApplySorts) {
+ const updateTable = async () => await updateTableSchema({ sorts });
+ updateTable();
+ }
+ return;
+ }
if (snackbarId) {
closeSnackbar(snackbarId);
}
@@ -43,7 +60,7 @@ function useSaveTableSorts(canEditColumns: boolean) {
updateTable={async () => await updateTableSchema({ sorts })}
/>
),
- anchorOrigin: { horizontal: "center", vertical: "top" },
+ anchorOrigin: { horizontal: "left", vertical: "bottom" },
})
);
@@ -57,6 +74,7 @@ function useSaveTableSorts(canEditColumns: boolean) {
tableId,
closeSnackbar,
updateTableSchema,
+ defaultTableSettings,
]
);
diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx
index a4b85957..ba17786b 100644
--- a/src/components/Table/Table.tsx
+++ b/src/components/Table/Table.tsx
@@ -285,7 +285,7 @@ export default function Table({
const { handler: hotKeysHandler } = useHotKeys([
["mod+C", handleCopy],
["mod+X", handleCut],
- ["mod+V", handlePaste],
+ ["mod+V", (e) => handlePaste], // So the event isn't passed to the handler
]);
// Handle prompt to save local column sizes if user `canEditColumns`
@@ -324,6 +324,14 @@ export default function Table({
fetchMoreOnBottomReached(containerRef.current);
}, [fetchMoreOnBottomReached, tableNextPage.loading, containerRef]);
+ useEffect(() => {
+ document.addEventListener("paste", handlePaste);
+
+ return () => {
+ document.removeEventListener("paste", handlePaste);
+ };
+ }, [handlePaste]);
+
// apply user default sort on first render
const [applySort, setApplySort] = useState(true);
useEffect(() => {
diff --git a/src/components/Table/useHotKey.tsx b/src/components/Table/useHotKey.tsx
index cf74808e..e57d9c84 100644
--- a/src/components/Table/useHotKey.tsx
+++ b/src/components/Table/useHotKey.tsx
@@ -12,7 +12,6 @@ export default function useHotKeys(actions: HotKeysAction[]) {
const event_ = "nativeEvent" in event ? event.nativeEvent : event;
actions.forEach(([hotkey, handler_]) => {
if (getHotkeyMatcher(hotkey)(event_)) {
- event.preventDefault();
handler_(event_);
}
});
diff --git a/src/components/Table/useMenuAction.tsx b/src/components/Table/useMenuAction.tsx
index dbe55907..3a1d4599 100644
--- a/src/components/Table/useMenuAction.tsx
+++ b/src/components/Table/useMenuAction.tsx
@@ -161,71 +161,96 @@ export function useMenuAction(
handleClose,
]);
- const handlePaste = useCallback(async () => {
- try {
- if (!selectedCell || !selectedCol) return;
- let text;
+ const handlePaste = useCallback(
+ async (e?: ClipboardEvent) => {
try {
- text = await navigator.clipboard.readText();
- } catch (e) {
- enqueueSnackbar(`Read clipboard permission denied.`, {
- variant: "error",
- });
- return;
- }
- const cellDataType = getFieldProp("dataType", getFieldType(selectedCol));
- let parsed;
- switch (cellDataType) {
- case "number":
- parsed = Number(text);
- if (isNaN(parsed)) throw new Error(`${text} is not a number`);
- break;
- case "string":
- parsed = text;
- break;
- case "reference":
- try {
- parsed = doc(firebaseDb, text);
- } catch (e: any) {
- enqueueSnackbar(`Invalid reference.`, { variant: "error" });
+ if (!selectedCell || !selectedCol) return;
+ let text: string;
+ // Firefox doesn't allow for reading clipboard data, hence the workaround
+ if (navigator.userAgent.includes("Firefox")) {
+ if (!e || !e.clipboardData) {
+ enqueueSnackbar(
+ `If you're on Firefox, please use the hotkey instead (Ctrl + V / Cmd + V).`,
+ {
+ variant: "info",
+ autoHideDuration: 7000,
+ }
+ );
+ enqueueSnackbar(`Cannot read clipboard data.`, {
+ variant: "error",
+ });
+ return;
}
- break;
- default:
- parsed = JSON.parse(text);
- break;
- }
+ text = e.clipboardData.getData("text/plain") || "";
+ } else {
+ try {
+ text = await navigator.clipboard.readText();
+ } catch (e) {
+ enqueueSnackbar(`Read clipboard permission denied.`, {
+ variant: "error",
+ });
+ return;
+ }
+ }
+ const cellDataType = getFieldProp(
+ "dataType",
+ getFieldType(selectedCol)
+ );
+ let parsed;
+ switch (cellDataType) {
+ case "number":
+ parsed = Number(text);
+ if (isNaN(parsed)) throw new Error(`${text} is not a number`);
+ break;
+ case "string":
+ parsed = text;
+ break;
+ case "reference":
+ try {
+ parsed = doc(firebaseDb, text);
+ } catch (e: any) {
+ enqueueSnackbar(`Invalid reference.`, { variant: "error" });
+ }
+ break;
+ default:
+ parsed = JSON.parse(text);
+ break;
+ }
- if (selectedCol.type === FieldType.slider) {
- if (parsed < selectedCol.config?.min) parsed = selectedCol.config?.min;
- else if (parsed > selectedCol.config?.max)
- parsed = selectedCol.config?.max;
- }
+ if (selectedCol.type === FieldType.slider) {
+ if (parsed < selectedCol.config?.min)
+ parsed = selectedCol.config?.min;
+ else if (parsed > selectedCol.config?.max)
+ parsed = selectedCol.config?.max;
+ }
- if (selectedCol.type === FieldType.rating) {
- if (parsed < 0) parsed = 0;
- if (parsed > (selectedCol.config?.max || 5))
- parsed = selectedCol.config?.max || 5;
- }
+ if (selectedCol.type === FieldType.rating) {
+ if (parsed < 0) parsed = 0;
+ if (parsed > (selectedCol.config?.max || 5))
+ parsed = selectedCol.config?.max || 5;
+ }
- if (selectedCol.type === FieldType.percentage) {
- parsed = parsed / 100;
+ if (selectedCol.type === FieldType.percentage) {
+ parsed = parsed / 100;
+ }
+ updateField({
+ path: selectedCell.path,
+ fieldName: selectedCol.fieldName,
+ value: parsed,
+ arrayTableData: {
+ index: selectedCell.arrayIndex,
+ },
+ });
+ } catch (error) {
+ enqueueSnackbar(
+ `${selectedCol?.type} field does not support the data type being pasted`,
+ { variant: "error" }
+ );
}
- updateField({
- path: selectedCell.path,
- fieldName: selectedCol.fieldName,
- value: parsed,
- arrayTableData: {
- index: selectedCell.arrayIndex,
- },
- });
- } catch (error) {
- enqueueSnackbar(
- `${selectedCol?.type} field does not support the data type being pasted`,
- { variant: "error" }
- );
- }
- if (handleClose) handleClose();
- }, [selectedCell, selectedCol, updateField, enqueueSnackbar, handleClose]);
+ if (handleClose) handleClose();
+ },
+ [selectedCell, selectedCol, updateField, enqueueSnackbar, handleClose]
+ );
useEffect(() => {
if (!selectedCell) return setCellValue("");
@@ -276,9 +301,9 @@ export function useMenuAction(
};
}
const fieldType = getFieldType(selectedCol);
- return function () {
+ return function (e?: ClipboardEvent) {
if (SUPPORTED_TYPES_PASTE.has(fieldType)) {
- return func();
+ return func(e);
} else {
enqueueSnackbar(
`${fieldType} field does not support paste functionality`,
diff --git a/src/components/Table/useSaveColumnSizing.tsx b/src/components/Table/useSaveColumnSizing.tsx
index af1a6e37..b0c1d55c 100644
--- a/src/components/Table/useSaveColumnSizing.tsx
+++ b/src/components/Table/useSaveColumnSizing.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
-import { useSetAtom } from "jotai";
+import { useAtomValue, useSetAtom } from "jotai";
import { useSnackbar } from "notistack";
import { useDebounce } from "use-debounce";
import { isEqual, isEmpty } from "lodash-es";
@@ -13,6 +13,10 @@ import {
updateColumnAtom,
IUpdateColumnOptions,
} from "@src/atoms/tableScope";
+import {
+ defaultTableSettingsAtom,
+ projectScope,
+} from "@src/atoms/projectScope";
import { DEBOUNCE_DELAY } from "./Table";
import { ColumnSizingState } from "@tanstack/react-table";
@@ -26,14 +30,31 @@ export function useSaveColumnSizing(
) {
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
const updateColumn = useSetAtom(updateColumnAtom, tableScope);
+ const defaultTableSettings = useAtomValue(
+ defaultTableSettingsAtom,
+ projectScope
+ );
// Debounce for saving to schema
const [debouncedColumnSizing] = useDebounce(columnSizing, DEBOUNCE_DELAY, {
equalityFn: isEqual,
});
- // Offer to save when column sizing changes
+ // Offer to save when column sizing changes, depending on user settings
useEffect(() => {
if (!canEditColumns || isEmpty(debouncedColumnSizing)) return;
+ // If the user has disabled the popup, return early
+ if (defaultTableSettings?.saveColumnSizingPopupDisabled) {
+ // If the user has `automaticallyApplyColumnSizing` set to true, apply the column width before returning
+ if (defaultTableSettings?.automaticallyApplyColumnSizing) {
+ const updateTable = async () => {
+ for (const [key, value] of Object.entries(debouncedColumnSizing)) {
+ await updateColumn({ key, config: { width: value } });
+ }
+ };
+ updateTable();
+ }
+ return;
+ }
const snackbarId = enqueueSnackbar("Save column sizes for all users?", {
action: (
@@ -42,7 +63,7 @@ export function useSaveColumnSizing(
updateColumn={updateColumn}
/>
),
- anchorOrigin: { horizontal: "center", vertical: "top" },
+ anchorOrigin: { horizontal: "left", vertical: "bottom" },
});
return () => closeSnackbar(snackbarId);
@@ -52,6 +73,7 @@ export function useSaveColumnSizing(
enqueueSnackbar,
closeSnackbar,
updateColumn,
+ defaultTableSettings,
]);
return null;
diff --git a/src/components/TableInformationDrawer/Details.tsx b/src/components/TableInformationDrawer/Details.tsx
index 78dd0236..b097198a 100644
--- a/src/components/TableInformationDrawer/Details.tsx
+++ b/src/components/TableInformationDrawer/Details.tsx
@@ -8,6 +8,7 @@ import {
IconButton,
Stack,
TextField,
+ Tooltip,
Typography,
useTheme,
} from "@mui/material";
@@ -98,15 +99,17 @@ export default function Details() {
Description
{isAdmin && (
- {
- setEditDescription(!editDescription);
- }}
- sx={{ top: 4 }}
- >
- {editDescription ? : }
-
+
+ {
+ setEditDescription(!editDescription);
+ }}
+ sx={{ top: 4 }}
+ >
+ {editDescription ? : }
+
+
)}
{editDescription ? (
@@ -145,15 +148,17 @@ export default function Details() {
Details
{isAdmin && (
- {
- setEditDetails(!editDetails);
- }}
- sx={{ top: 4 }}
- >
- {editDetails ? : }
-
+
+ {
+ setEditDetails(!editDetails);
+ }}
+ sx={{ top: 4 }}
+ >
+ {editDetails ? : }
+
+
)}
- setSideDrawer(RESET)}
- aria-label="Close"
- >
-
-
+
+ setSideDrawer(RESET)}
+ aria-label="Close"
+ >
+
+
+
= {
+ buildshipAuthenticatedTrigger: "BuildShip Authenticated Trigger",
task: "Task",
docSync: "Doc Sync",
historySnapshot: "History Snapshot",
@@ -61,6 +63,30 @@ export interface IRuntimeOptions {
export const triggerTypes: ExtensionTrigger[] = ["create", "update", "delete"];
const extensionBodyTemplate = {
+ buildshipAuthenticatedTrigger: `const extensionBody: BuildshipAuthenticatedTriggerBody = async({row, db, change, ref, logging}) => {
+ logging.log("extensionBody started")
+
+ // Put your endpoint URL and request body below.
+ // It will trigger your endpoint with the request body.
+ return ({
+ buildshipConfig: {
+ projectId: "",
+ workflowId: ""
+ },
+ body: JSON.stringify({
+ row,
+ ref: {
+ id: ref.id,
+ path: ref.path
+ },
+ change: {
+ before: change.before.get(),
+ after: change.after.get(),
+ },
+ // Add your own payload here
+ })
+ })
+}`,
task: `const extensionBody: TaskBody = async({row, db, change, ref, logging}) => {
logging.log("extensionBody started")
diff --git a/src/components/TableModals/ImportCsvWizard/ImportCsvWizard.tsx b/src/components/TableModals/ImportCsvWizard/ImportCsvWizard.tsx
index 4f3f9a9e..f7989d26 100644
--- a/src/components/TableModals/ImportCsvWizard/ImportCsvWizard.tsx
+++ b/src/components/TableModals/ImportCsvWizard/ImportCsvWizard.tsx
@@ -323,7 +323,9 @@ export default function ImportCsvWizard({ onClose }: ITableModalProps) {
disableNext:
config.pairs.length === 0 ||
!validRows ||
- (config.documentId === "column" && !config.documentIdCsvKey),
+ (config.documentId === "column" && !config.documentIdCsvKey) ||
+ config.pairs.some((pair) => !pair.columnKey) ||
+ config.newColumns.some((col) => !col.key),
},
config.newColumns.length > 0 && {
title: "Set column types",
diff --git a/src/components/TableModals/ImportCsvWizard/Step1Columns.tsx b/src/components/TableModals/ImportCsvWizard/Step1Columns.tsx
index 888b87e5..5baa8616 100644
--- a/src/components/TableModals/ImportCsvWizard/Step1Columns.tsx
+++ b/src/components/TableModals/ImportCsvWizard/Step1Columns.tsx
@@ -257,8 +257,16 @@ export default function Step1Columns({
const isNewColumn = !!find(config.newColumns, { key: columnKey });
return (
-
-
+
+
-
+
{selected && (
- {
- if (!columnKey) return "Select or add column";
- else
- return (
-
-
- {!isNewColumn ? (
- getFieldProp("icon", matchingColumn?.type)
- ) : (
-
- )}
-
- {matchingColumn?.name}
- {isNewColumn && (
-
- )}
-
- );
- },
- sx: [
- {
- backgroundColor: "background.default",
- border: (theme) =>
- `1px solid ${theme.palette.divider}`,
- borderRadius: 0,
- boxShadow: "none",
- "& .MuiSelect-select": {
- boxSizing: "border-box",
- height: COLUMN_HEADER_HEIGHT - 2,
- typography: "caption",
- fontWeight: "medium",
- lineHeight: "28px",
+ <>
+
+ {
+ if (!columnKey) return "Select or add column";
+ else
+ return (
+
+
+ {!isNewColumn ? (
+ getFieldProp(
+ "icon",
+ matchingColumn?.type
+ )
+ ) : (
+
+ )}
+
+ {matchingColumn?.name}
+ {isNewColumn && (
+
+ )}
+
+ );
},
+ sx: [
+ {
+ backgroundColor: "background.default",
+ border: (theme) =>
+ `1px solid ${theme.palette.divider}`,
+ borderRadius: 0,
+ boxShadow: "none",
+ "& .MuiSelect-select": {
+ boxSizing: "border-box",
+ height: COLUMN_HEADER_HEIGHT - 2,
+ typography: "caption",
+ fontWeight: "medium",
+ lineHeight: "28px",
+ },
- color: "text.secondary",
- "&:hover": {
- backgroundColor: "background.default",
- color: "text.primary",
- boxShadow: "none",
- },
+ color: "text.secondary",
+ "&:hover": {
+ backgroundColor: "background.default",
+ color: "text.primary",
+ boxShadow: "none",
+ },
- "&::before": { content: "none" },
- "&::after": { pointerEvents: "none" },
+ "&::before": { content: "none" },
+ "&::after": { pointerEvents: "none" },
+ },
+ !columnKey && { color: "text.disabled" },
+ ],
},
- !columnKey && { color: "text.disabled" },
- ],
- },
- sx: { "& .MuiInputLabel-root": { display: "none" } },
- }}
- clearable={false}
- displayEmpty
- freeText
- AddButtonProps={{ children: "Create column…" }}
- AddDialogProps={{
- title: "Create column",
- textFieldLabel: "Column name",
- }}
- />
+ sx: { "& .MuiInputLabel-root": { display: "none" } },
+ }}
+ clearable={false}
+ displayEmpty
+ freeText
+ AddButtonProps={{ children: "Create column…" }}
+ AddDialogProps={{
+ title: "Create column",
+ textFieldLabel: "Column name",
+ }}
+ />
+
+
+ pair.columnKey === columnKey
+ )?.columnKey ??
+ config.newColumns.find(
+ (pair) => pair.key === columnKey
+ )?.key
+ }
+ onChange={(e) => {
+ const newKey = e.target.value;
+ const newPairs = config.pairs.map((pair) => {
+ if (pair.columnKey === columnKey) {
+ return { ...pair, columnKey: newKey };
+ } else {
+ return pair;
+ }
+ });
+
+ const newColumns = config.newColumns.map((column) => {
+ if (column.key === columnKey) {
+ return {
+ ...column,
+ key: newKey,
+ fieldName: newKey,
+ };
+ } else {
+ return column;
+ }
+ });
+
+ setConfig((config) => ({
+ ...config,
+ pairs: newPairs,
+ newColumns,
+ }));
+ }}
+ sx={{
+ "& .MuiInputLabel-root": {
+ position: "absolute",
+ transform: "translateY(-100%)",
+ },
+ "& .MuiInputBase-root": {
+ height: 40,
+ },
+ }}
+ />
+
+ >
)}
diff --git a/src/components/TableModals/WebhooksModal/Schemas/stripe.tsx b/src/components/TableModals/WebhooksModal/Schemas/stripe.tsx
index 21d0deb7..806efd3c 100644
--- a/src/components/TableModals/WebhooksModal/Schemas/stripe.tsx
+++ b/src/components/TableModals/WebhooksModal/Schemas/stripe.tsx
@@ -7,6 +7,7 @@ import {
projectScope,
secretNamesAtom,
updateSecretNamesAtom,
+ projectSettingsDialogAtom,
} from "@src/atoms/projectScope";
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
@@ -56,6 +57,10 @@ export const webhookStripe = {
Auth: (webhookObject: IWebhook, setWebhookObject: (w: IWebhook) => void) => {
const [secretNames] = useAtom(secretNamesAtom, projectScope);
const [updateSecretNames] = useAtom(updateSecretNamesAtom, projectScope);
+ const [{ open, tab }, setProjectSettingsDialog] = useAtom(
+ projectSettingsDialogAtom,
+ projectScope
+ );
return (
<>
@@ -118,8 +123,9 @@ export const webhookStripe = {
})}