@quillsql/admin 1.8.8 → 1.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -21567,6 +21567,7 @@ function EditEnvironmentModal({
21567
21567
  state,
21568
21568
  dispatch,
21569
21569
  refreshClientsList,
21570
+ setClient,
21570
21571
  getToken,
21571
21572
  quillFetchWithToken,
21572
21573
  eventTracking
@@ -21578,6 +21579,12 @@ function EditEnvironmentModal({
21578
21579
  const [isSubmitTenantLoading, setSubmitTenantLoading] = (0, import_react50.useState)(false);
21579
21580
  const [isSaveEnvironmentLoading, setIsSaveEnvironmentLoading] = (0, import_react50.useState)(false);
21580
21581
  const [isDeleteEnvironmentLoading, setIsDeleteEnvironmentLoading] = (0, import_react50.useState)(false);
21582
+ const [isCreateChildEnvironmentLoading, setIsCreateChildEnvironmentLoading] = (0, import_react50.useState)(false);
21583
+ const [
21584
+ isPromoteChildEnvironmentLoading,
21585
+ setIsPromoteChildEnvironmentLoading
21586
+ ] = (0, import_react50.useState)(false);
21587
+ const [developmentEnvironmentConfirm, setDevelopmentEnvironmentConfirm] = (0, import_react50.useState)(null);
21581
21588
  const [validationError, setValidationError] = (0, import_react50.useState)(
21582
21589
  void 0
21583
21590
  );
@@ -21585,6 +21592,26 @@ function EditEnvironmentModal({
21585
21592
  const connectionDetails = (0, import_react50.useMemo)(() => {
21586
21593
  return getDatabaseConnectionFormat(state.client?.databaseType || "");
21587
21594
  }, [state.client?.databaseType]);
21595
+ const sourceClientId = (0, import_react50.useMemo)(() => {
21596
+ if (state.sandboxParentClientId != null) {
21597
+ return String(state.sandboxParentClientId);
21598
+ }
21599
+ return clientRecordId(state.client);
21600
+ }, [state.sandboxParentClientId, state.client]);
21601
+ const relatedChildEnvironments = (0, import_react50.useMemo)(() => {
21602
+ const parentClientId = state.client?.sandboxParentClientId != null ? String(state.client.sandboxParentClientId) : sourceClientId;
21603
+ if (!parentClientId) return [];
21604
+ return state.clients.filter(
21605
+ (c2) => c2.sandboxParentClientId != null && String(c2.sandboxParentClientId) === parentClientId
21606
+ );
21607
+ }, [state.clients, state.client?.sandboxParentClientId, sourceClientId]);
21608
+ const sourceEnvironmentName = (0, import_react50.useMemo)(() => {
21609
+ if (!sourceClientId) return state.client?.name;
21610
+ const sourceClient = state.clients.find(
21611
+ (c2) => clientRecordId(c2) === sourceClientId
21612
+ );
21613
+ return sourceClient?.name ?? state.client?.name;
21614
+ }, [sourceClientId, state.clients, state.client?.name]);
21588
21615
  const [tenantToEdit, setTenantToEdit] = (0, import_react50.useState)(
21589
21616
  void 0
21590
21617
  );
@@ -21983,6 +22010,104 @@ function EditEnvironmentModal({
21983
22010
  setFetchSchemasLoading(false);
21984
22011
  }
21985
22012
  };
22013
+ const handleCreateChildEnvironment = async () => {
22014
+ if (!sourceClientId) return;
22015
+ setDevelopmentEnvironmentConfirm(null);
22016
+ setIsCreateChildEnvironmentLoading(true);
22017
+ try {
22018
+ const response = await quillFetchWithToken({
22019
+ client: {
22020
+ queryEndpoint: state.queryEndpoint,
22021
+ clientId: sourceClientId,
22022
+ withCredentials: !!state.withCredentials,
22023
+ queryHeaders: state.queryHeaders
22024
+ },
22025
+ task: "create-sandbox",
22026
+ metadata: {
22027
+ clientId: sourceClientId,
22028
+ permanentSandbox: true
22029
+ },
22030
+ adminMode: true
22031
+ });
22032
+ if (response.status === "error") {
22033
+ throw new Error(response.error);
22034
+ }
22035
+ await refreshClientsList(sourceClientId);
22036
+ } catch (e2) {
22037
+ eventTracking?.logError?.({
22038
+ type: "bug",
22039
+ // TODO: determine type
22040
+ severity: "high",
22041
+ message: "Error creating child environment",
22042
+ errorMessage: e2.message,
22043
+ errorStack: e2.stack,
22044
+ errorData: {
22045
+ caller: "EditEnvironmentModal",
22046
+ function: "handleCreateChildEnvironment"
22047
+ }
22048
+ });
22049
+ setValidationError(e2.message);
22050
+ } finally {
22051
+ setIsCreateChildEnvironmentLoading(false);
22052
+ }
22053
+ };
22054
+ const handlePromoteChildEnvironment = async () => {
22055
+ if (developmentEnvironmentConfirm?.action !== "promote") return;
22056
+ const childEnvironmentToPromote = developmentEnvironmentConfirm.environment;
22057
+ const promoteClientId = clientRecordId(childEnvironmentToPromote);
22058
+ if (!promoteClientId) {
22059
+ setValidationError("Cannot promote: missing target environment id.");
22060
+ return;
22061
+ }
22062
+ setDevelopmentEnvironmentConfirm(null);
22063
+ setIsPromoteChildEnvironmentLoading(true);
22064
+ try {
22065
+ const response = await quillFetchWithToken({
22066
+ client: {
22067
+ queryEndpoint: state.queryEndpoint,
22068
+ clientId: promoteClientId,
22069
+ withCredentials: !!state.withCredentials,
22070
+ queryHeaders: state.queryHeaders
22071
+ },
22072
+ task: "promote",
22073
+ metadata: {
22074
+ clientId: promoteClientId,
22075
+ forcePromote: true
22076
+ },
22077
+ adminMode: true
22078
+ });
22079
+ if (response.status === "error" || response.error) {
22080
+ throw new Error(response.error);
22081
+ }
22082
+ const hasActiveSandbox = state.sandboxSessionId != null || state.sandboxClientId != null;
22083
+ if (hasActiveSandbox && sourceClientId) {
22084
+ setClient(
22085
+ { clientId: sourceClientId },
22086
+ true,
22087
+ true,
22088
+ true
22089
+ );
22090
+ }
22091
+ await refreshClientsList(sourceClientId);
22092
+ setIsOpen(false);
22093
+ } catch (e2) {
22094
+ eventTracking?.logError?.({
22095
+ type: "bug",
22096
+ // TODO: determine type
22097
+ severity: "high",
22098
+ message: "Error promoting child environment",
22099
+ errorMessage: e2.message,
22100
+ errorStack: e2.stack,
22101
+ errorData: {
22102
+ caller: "EditEnvironmentModal",
22103
+ function: "handlePromoteChildEnvironment"
22104
+ }
22105
+ });
22106
+ setValidationError(e2.message);
22107
+ } finally {
22108
+ setIsPromoteChildEnvironmentLoading(false);
22109
+ }
22110
+ };
21986
22111
  const handleDeleteEnvironment = async () => {
21987
22112
  if (state.clients.length === 1) {
21988
22113
  alert("You cannot delete the only environment");
@@ -22029,8 +22154,9 @@ function EditEnvironmentModal({
22029
22154
  }
22030
22155
  }
22031
22156
  };
22032
- const isEnvironmentModalLoading = isDeleteEnvironmentLoading || isSaveEnvironmentLoading;
22033
- const environmentLoadingLabel = isDeleteEnvironmentLoading ? "Deleting environment..." : "Saving environment...";
22157
+ const isEnvironmentModalLoading = isDeleteEnvironmentLoading || isSaveEnvironmentLoading || isCreateChildEnvironmentLoading || isPromoteChildEnvironmentLoading;
22158
+ const environmentLoadingLabel = isDeleteEnvironmentLoading ? "Deleting environment..." : isCreateChildEnvironmentLoading ? "Creating child environment..." : isPromoteChildEnvironmentLoading ? "Promoting environment..." : "Saving environment...";
22159
+ const isDevelopmentEnvironmentConfirmLoading = developmentEnvironmentConfirm?.action === "promote" ? isPromoteChildEnvironmentLoading : isCreateChildEnvironmentLoading;
22034
22160
  return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_jsx_runtime52.Fragment, { children: [
22035
22161
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
22036
22162
  ModalComponent,
@@ -22043,6 +22169,9 @@ function EditEnvironmentModal({
22043
22169
  setSelectedEnvironmentName(state.client.name);
22044
22170
  setIsSaveEnvironmentLoading(false);
22045
22171
  setIsDeleteEnvironmentLoading(false);
22172
+ setIsCreateChildEnvironmentLoading(false);
22173
+ setIsPromoteChildEnvironmentLoading(false);
22174
+ setDevelopmentEnvironmentConfirm(null);
22046
22175
  },
22047
22176
  style: {
22048
22177
  minWidth: "600px"
@@ -22213,38 +22342,115 @@ function EditEnvironmentModal({
22213
22342
  disabled: isEnvironmentModalLoading
22214
22343
  }
22215
22344
  ) }),
22216
- /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_jsx_runtime52.Fragment, { children: [
22217
- /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(CardSection, { children: "Danger Zone" }),
22218
- /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
22219
- "div",
22220
- {
22221
- style: {
22222
- display: "flex",
22223
- flexDirection: "column",
22224
- width: "fit-content",
22225
- gap: 16
22226
- },
22227
- children: [
22228
- /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
22229
- SecondaryButtonComponent,
22230
- {
22231
- onClick: () => setIsConnectDatabaseModalOpen(true),
22232
- label: "Edit Database Connection",
22233
- disabled: isEnvironmentModalLoading
22234
- }
22235
- ),
22236
- /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
22237
- SecondaryButtonComponent,
22238
- {
22239
- onClick: handleDeleteEnvironment,
22240
- label: "Delete Environment",
22241
- disabled: isEnvironmentModalLoading
22242
- }
22243
- )
22244
- ]
22245
- }
22246
- )
22247
- ] }),
22345
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(CardSection, { children: "Danger Zone" }),
22346
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
22347
+ "div",
22348
+ {
22349
+ style: {
22350
+ display: "flex",
22351
+ flexDirection: "column",
22352
+ width: "fit-content",
22353
+ gap: 16
22354
+ },
22355
+ children: [
22356
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
22357
+ SecondaryButtonComponent,
22358
+ {
22359
+ onClick: () => setIsConnectDatabaseModalOpen(true),
22360
+ label: "Edit Database Connection",
22361
+ disabled: isEnvironmentModalLoading
22362
+ }
22363
+ ),
22364
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
22365
+ SecondaryButtonComponent,
22366
+ {
22367
+ onClick: handleDeleteEnvironment,
22368
+ label: "Delete Environment",
22369
+ disabled: isEnvironmentModalLoading
22370
+ }
22371
+ )
22372
+ ]
22373
+ }
22374
+ ),
22375
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(CardSection, { children: "Development Environments" }),
22376
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
22377
+ "div",
22378
+ {
22379
+ style: {
22380
+ display: "flex",
22381
+ flexDirection: "column",
22382
+ width: "fit-content",
22383
+ gap: 16
22384
+ },
22385
+ children: [
22386
+ relatedChildEnvironments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
22387
+ "table",
22388
+ {
22389
+ style: {
22390
+ borderCollapse: "collapse",
22391
+ width: "100%",
22392
+ minWidth: 280,
22393
+ fontFamily: state.theme.fontFamily,
22394
+ fontSize: 14,
22395
+ color: state.theme.primaryTextColor
22396
+ },
22397
+ children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("tbody", { children: relatedChildEnvironments.map((env) => {
22398
+ const envId = clientRecordId(env);
22399
+ const cellBorder = `1px solid ${state.theme.borderColor ?? "#e7e7e7"}`;
22400
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("tr", { children: [
22401
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
22402
+ "td",
22403
+ {
22404
+ style: {
22405
+ border: cellBorder,
22406
+ padding: "8px 12px",
22407
+ fontWeight: 500
22408
+ },
22409
+ children: env.name
22410
+ }
22411
+ ),
22412
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
22413
+ "td",
22414
+ {
22415
+ style: {
22416
+ border: cellBorder,
22417
+ padding: "8px 12px",
22418
+ width: 1,
22419
+ whiteSpace: "nowrap"
22420
+ },
22421
+ children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
22422
+ ButtonComponent,
22423
+ {
22424
+ label: "Promote",
22425
+ onClick: () => setDevelopmentEnvironmentConfirm({
22426
+ action: "promote",
22427
+ environment: env
22428
+ }),
22429
+ disabled: isEnvironmentModalLoading || developmentEnvironmentConfirm != null,
22430
+ style: {
22431
+ height: 32,
22432
+ paddingLeft: 12,
22433
+ paddingRight: 12
22434
+ }
22435
+ }
22436
+ )
22437
+ }
22438
+ )
22439
+ ] }, envId ?? env.name);
22440
+ }) })
22441
+ }
22442
+ ),
22443
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
22444
+ SecondaryButtonComponent,
22445
+ {
22446
+ onClick: () => setDevelopmentEnvironmentConfirm({ action: "create" }),
22447
+ label: "+ Create New",
22448
+ disabled: isEnvironmentModalLoading
22449
+ }
22450
+ ) })
22451
+ ]
22452
+ }
22453
+ ),
22248
22454
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("div", { style: { height: 18 } }),
22249
22455
  /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
22250
22456
  "div",
@@ -22483,6 +22689,117 @@ function EditEnvironmentModal({
22483
22689
  )
22484
22690
  }
22485
22691
  ),
22692
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
22693
+ ModalComponent,
22694
+ {
22695
+ isOpen: developmentEnvironmentConfirm != null,
22696
+ onClose: () => {
22697
+ if (isDevelopmentEnvironmentConfirmLoading) return;
22698
+ setDevelopmentEnvironmentConfirm(null);
22699
+ },
22700
+ style: {
22701
+ width: 480
22702
+ },
22703
+ children: /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
22704
+ "div",
22705
+ {
22706
+ style: {
22707
+ display: "flex",
22708
+ flexDirection: "column",
22709
+ gap: 20,
22710
+ width: "100%",
22711
+ padding: 24
22712
+ },
22713
+ children: [
22714
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(CardSection, { children: developmentEnvironmentConfirm?.action === "promote" ? "Promote Environment Changes" : "Create child environment" }),
22715
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
22716
+ "div",
22717
+ {
22718
+ style: {
22719
+ display: "flex",
22720
+ flexDirection: "column",
22721
+ gap: 12,
22722
+ fontFamily: state.theme.fontFamily,
22723
+ fontSize: 14,
22724
+ lineHeight: 1.6,
22725
+ color: state.theme.primaryTextColor
22726
+ },
22727
+ children: [
22728
+ developmentEnvironmentConfirm?.action === "promote" ? /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { style: { margin: 0 }, children: [
22729
+ "Promoting",
22730
+ " ",
22731
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("strong", { children: [
22732
+ developmentEnvironmentConfirm.environment?.name ?? "this environment",
22733
+ "\xA0into\xA0",
22734
+ sourceEnvironmentName ?? "the current environment"
22735
+ ] }),
22736
+ " ",
22737
+ "will replicate the development environment's state into the current environment."
22738
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { style: { margin: 0 }, children: [
22739
+ "Creating a child development environment for",
22740
+ " ",
22741
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("strong", { children: sourceEnvironmentName ?? "the current environment" }),
22742
+ " ",
22743
+ "will create an environment you can use to preview changes before promoting them into the currently active environment."
22744
+ ] }),
22745
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
22746
+ "p",
22747
+ {
22748
+ style: {
22749
+ margin: 0,
22750
+ padding: "12px 14px",
22751
+ borderRadius: 6,
22752
+ border: `1px solid ${state.theme.borderColor ?? "#e7e7e7"}`,
22753
+ backgroundColor: state.theme.hoverBackgroundColor ?? "#f7f7f7",
22754
+ color: "#DC2626"
22755
+ },
22756
+ children: "This is not a sandbox operation and will immediately apply to the live environment. Your current active session will be discarded."
22757
+ }
22758
+ )
22759
+ ]
22760
+ }
22761
+ ),
22762
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
22763
+ "div",
22764
+ {
22765
+ style: {
22766
+ display: "flex",
22767
+ flexDirection: "row",
22768
+ justifyContent: "flex-end",
22769
+ gap: 16,
22770
+ width: "100%"
22771
+ },
22772
+ children: [
22773
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
22774
+ SecondaryButtonComponent,
22775
+ {
22776
+ label: "Cancel",
22777
+ onClick: () => setDevelopmentEnvironmentConfirm(null),
22778
+ disabled: isDevelopmentEnvironmentConfirmLoading
22779
+ }
22780
+ ),
22781
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
22782
+ ButtonComponent,
22783
+ {
22784
+ label: "Confirm",
22785
+ onClick: () => {
22786
+ if (developmentEnvironmentConfirm?.action === "promote") {
22787
+ handlePromoteChildEnvironment();
22788
+ } else if (developmentEnvironmentConfirm?.action === "create") {
22789
+ handleCreateChildEnvironment();
22790
+ }
22791
+ },
22792
+ disabled: isDevelopmentEnvironmentConfirmLoading
22793
+ }
22794
+ )
22795
+ ]
22796
+ }
22797
+ )
22798
+ ]
22799
+ }
22800
+ )
22801
+ }
22802
+ ),
22486
22803
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
22487
22804
  ModalComponent,
22488
22805
  {
@@ -23153,7 +23470,8 @@ function SavedQueriesModal({
23153
23470
  ) });
23154
23471
  }
23155
23472
  function SavedQueryTile({ report }) {
23156
- const { state, dispatch, getToken } = useAdmin();
23473
+ const { state, dispatch, getToken, eventTracking } = useAdmin();
23474
+ const { reload } = (0, import_react56.useDashboardInternal)(SAVED_QUERIES_DASHBOARD);
23157
23475
  (0, import_react55.useEffect)(() => {
23158
23476
  async function getIsOpenableByReportBuilder() {
23159
23477
  const hello = await parseQuillReportToQuillReportInternal(
@@ -23178,6 +23496,14 @@ function SavedQueryTile({ report }) {
23178
23496
  payload: report?.queryString
23179
23497
  });
23180
23498
  };
23499
+ const handleDelete = async () => {
23500
+ if (await deleteReport(report.id, state, getToken, eventTracking)) {
23501
+ reload(SAVED_QUERIES_DASHBOARD, true, {
23502
+ report: { id: report.id },
23503
+ action: "delete"
23504
+ });
23505
+ }
23506
+ };
23181
23507
  if (!report.queryString) {
23182
23508
  return null;
23183
23509
  }
@@ -23211,7 +23537,8 @@ function SavedQueryTile({ report }) {
23211
23537
  label: "Open in SQL Editor \u2197",
23212
23538
  onClick: handleOpenReportInSQLEditor
23213
23539
  }
23214
- )
23540
+ ),
23541
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(SecondaryButtonPrimitive_default, { label: "Delete", onClick: handleDelete })
23215
23542
  ]
23216
23543
  }
23217
23544
  );
package/dist/index.d.cts CHANGED
@@ -180,6 +180,7 @@ interface BasePivot {
180
180
  triggerButtonText?: string;
181
181
  rowLimit?: number;
182
182
  columnValues?: string[];
183
+ dateBucket?: 'day' | 'week' | 'month' | 'year';
183
184
  }
184
185
  interface SingleAggregationPivot extends BasePivot {
185
186
  aggregationType: AggregationType;
package/dist/index.d.ts CHANGED
@@ -180,6 +180,7 @@ interface BasePivot {
180
180
  triggerButtonText?: string;
181
181
  rowLimit?: number;
182
182
  columnValues?: string[];
183
+ dateBucket?: 'day' | 'week' | 'month' | 'year';
183
184
  }
184
185
  interface SingleAggregationPivot extends BasePivot {
185
186
  aggregationType: AggregationType;
package/dist/index.js CHANGED
@@ -9505,7 +9505,7 @@ async function getClientTenantIds({
9505
9505
  // src/public_components/DashboardManager.tsx
9506
9506
  import { useCallback as useCallback10, useEffect as useEffect33, useRef as useRef25, useState as useState37 } from "react";
9507
9507
  import {
9508
- useDashboardInternal as useDashboardInternal5,
9508
+ useDashboardInternal as useDashboardInternal6,
9509
9509
  useDashboards as useDashboards5
9510
9510
  } from "@quillsql/react";
9511
9511
 
@@ -21599,6 +21599,7 @@ function EditEnvironmentModal({
21599
21599
  state,
21600
21600
  dispatch,
21601
21601
  refreshClientsList,
21602
+ setClient,
21602
21603
  getToken,
21603
21604
  quillFetchWithToken,
21604
21605
  eventTracking
@@ -21610,6 +21611,12 @@ function EditEnvironmentModal({
21610
21611
  const [isSubmitTenantLoading, setSubmitTenantLoading] = useState28(false);
21611
21612
  const [isSaveEnvironmentLoading, setIsSaveEnvironmentLoading] = useState28(false);
21612
21613
  const [isDeleteEnvironmentLoading, setIsDeleteEnvironmentLoading] = useState28(false);
21614
+ const [isCreateChildEnvironmentLoading, setIsCreateChildEnvironmentLoading] = useState28(false);
21615
+ const [
21616
+ isPromoteChildEnvironmentLoading,
21617
+ setIsPromoteChildEnvironmentLoading
21618
+ ] = useState28(false);
21619
+ const [developmentEnvironmentConfirm, setDevelopmentEnvironmentConfirm] = useState28(null);
21613
21620
  const [validationError, setValidationError] = useState28(
21614
21621
  void 0
21615
21622
  );
@@ -21617,6 +21624,26 @@ function EditEnvironmentModal({
21617
21624
  const connectionDetails = useMemo14(() => {
21618
21625
  return getDatabaseConnectionFormat(state.client?.databaseType || "");
21619
21626
  }, [state.client?.databaseType]);
21627
+ const sourceClientId = useMemo14(() => {
21628
+ if (state.sandboxParentClientId != null) {
21629
+ return String(state.sandboxParentClientId);
21630
+ }
21631
+ return clientRecordId(state.client);
21632
+ }, [state.sandboxParentClientId, state.client]);
21633
+ const relatedChildEnvironments = useMemo14(() => {
21634
+ const parentClientId = state.client?.sandboxParentClientId != null ? String(state.client.sandboxParentClientId) : sourceClientId;
21635
+ if (!parentClientId) return [];
21636
+ return state.clients.filter(
21637
+ (c2) => c2.sandboxParentClientId != null && String(c2.sandboxParentClientId) === parentClientId
21638
+ );
21639
+ }, [state.clients, state.client?.sandboxParentClientId, sourceClientId]);
21640
+ const sourceEnvironmentName = useMemo14(() => {
21641
+ if (!sourceClientId) return state.client?.name;
21642
+ const sourceClient = state.clients.find(
21643
+ (c2) => clientRecordId(c2) === sourceClientId
21644
+ );
21645
+ return sourceClient?.name ?? state.client?.name;
21646
+ }, [sourceClientId, state.clients, state.client?.name]);
21620
21647
  const [tenantToEdit, setTenantToEdit] = useState28(
21621
21648
  void 0
21622
21649
  );
@@ -22015,6 +22042,104 @@ function EditEnvironmentModal({
22015
22042
  setFetchSchemasLoading(false);
22016
22043
  }
22017
22044
  };
22045
+ const handleCreateChildEnvironment = async () => {
22046
+ if (!sourceClientId) return;
22047
+ setDevelopmentEnvironmentConfirm(null);
22048
+ setIsCreateChildEnvironmentLoading(true);
22049
+ try {
22050
+ const response = await quillFetchWithToken({
22051
+ client: {
22052
+ queryEndpoint: state.queryEndpoint,
22053
+ clientId: sourceClientId,
22054
+ withCredentials: !!state.withCredentials,
22055
+ queryHeaders: state.queryHeaders
22056
+ },
22057
+ task: "create-sandbox",
22058
+ metadata: {
22059
+ clientId: sourceClientId,
22060
+ permanentSandbox: true
22061
+ },
22062
+ adminMode: true
22063
+ });
22064
+ if (response.status === "error") {
22065
+ throw new Error(response.error);
22066
+ }
22067
+ await refreshClientsList(sourceClientId);
22068
+ } catch (e2) {
22069
+ eventTracking?.logError?.({
22070
+ type: "bug",
22071
+ // TODO: determine type
22072
+ severity: "high",
22073
+ message: "Error creating child environment",
22074
+ errorMessage: e2.message,
22075
+ errorStack: e2.stack,
22076
+ errorData: {
22077
+ caller: "EditEnvironmentModal",
22078
+ function: "handleCreateChildEnvironment"
22079
+ }
22080
+ });
22081
+ setValidationError(e2.message);
22082
+ } finally {
22083
+ setIsCreateChildEnvironmentLoading(false);
22084
+ }
22085
+ };
22086
+ const handlePromoteChildEnvironment = async () => {
22087
+ if (developmentEnvironmentConfirm?.action !== "promote") return;
22088
+ const childEnvironmentToPromote = developmentEnvironmentConfirm.environment;
22089
+ const promoteClientId = clientRecordId(childEnvironmentToPromote);
22090
+ if (!promoteClientId) {
22091
+ setValidationError("Cannot promote: missing target environment id.");
22092
+ return;
22093
+ }
22094
+ setDevelopmentEnvironmentConfirm(null);
22095
+ setIsPromoteChildEnvironmentLoading(true);
22096
+ try {
22097
+ const response = await quillFetchWithToken({
22098
+ client: {
22099
+ queryEndpoint: state.queryEndpoint,
22100
+ clientId: promoteClientId,
22101
+ withCredentials: !!state.withCredentials,
22102
+ queryHeaders: state.queryHeaders
22103
+ },
22104
+ task: "promote",
22105
+ metadata: {
22106
+ clientId: promoteClientId,
22107
+ forcePromote: true
22108
+ },
22109
+ adminMode: true
22110
+ });
22111
+ if (response.status === "error" || response.error) {
22112
+ throw new Error(response.error);
22113
+ }
22114
+ const hasActiveSandbox = state.sandboxSessionId != null || state.sandboxClientId != null;
22115
+ if (hasActiveSandbox && sourceClientId) {
22116
+ setClient(
22117
+ { clientId: sourceClientId },
22118
+ true,
22119
+ true,
22120
+ true
22121
+ );
22122
+ }
22123
+ await refreshClientsList(sourceClientId);
22124
+ setIsOpen(false);
22125
+ } catch (e2) {
22126
+ eventTracking?.logError?.({
22127
+ type: "bug",
22128
+ // TODO: determine type
22129
+ severity: "high",
22130
+ message: "Error promoting child environment",
22131
+ errorMessage: e2.message,
22132
+ errorStack: e2.stack,
22133
+ errorData: {
22134
+ caller: "EditEnvironmentModal",
22135
+ function: "handlePromoteChildEnvironment"
22136
+ }
22137
+ });
22138
+ setValidationError(e2.message);
22139
+ } finally {
22140
+ setIsPromoteChildEnvironmentLoading(false);
22141
+ }
22142
+ };
22018
22143
  const handleDeleteEnvironment = async () => {
22019
22144
  if (state.clients.length === 1) {
22020
22145
  alert("You cannot delete the only environment");
@@ -22061,8 +22186,9 @@ function EditEnvironmentModal({
22061
22186
  }
22062
22187
  }
22063
22188
  };
22064
- const isEnvironmentModalLoading = isDeleteEnvironmentLoading || isSaveEnvironmentLoading;
22065
- const environmentLoadingLabel = isDeleteEnvironmentLoading ? "Deleting environment..." : "Saving environment...";
22189
+ const isEnvironmentModalLoading = isDeleteEnvironmentLoading || isSaveEnvironmentLoading || isCreateChildEnvironmentLoading || isPromoteChildEnvironmentLoading;
22190
+ const environmentLoadingLabel = isDeleteEnvironmentLoading ? "Deleting environment..." : isCreateChildEnvironmentLoading ? "Creating child environment..." : isPromoteChildEnvironmentLoading ? "Promoting environment..." : "Saving environment...";
22191
+ const isDevelopmentEnvironmentConfirmLoading = developmentEnvironmentConfirm?.action === "promote" ? isPromoteChildEnvironmentLoading : isCreateChildEnvironmentLoading;
22066
22192
  return /* @__PURE__ */ jsxs37(Fragment13, { children: [
22067
22193
  /* @__PURE__ */ jsx52(
22068
22194
  ModalComponent,
@@ -22075,6 +22201,9 @@ function EditEnvironmentModal({
22075
22201
  setSelectedEnvironmentName(state.client.name);
22076
22202
  setIsSaveEnvironmentLoading(false);
22077
22203
  setIsDeleteEnvironmentLoading(false);
22204
+ setIsCreateChildEnvironmentLoading(false);
22205
+ setIsPromoteChildEnvironmentLoading(false);
22206
+ setDevelopmentEnvironmentConfirm(null);
22078
22207
  },
22079
22208
  style: {
22080
22209
  minWidth: "600px"
@@ -22245,38 +22374,115 @@ function EditEnvironmentModal({
22245
22374
  disabled: isEnvironmentModalLoading
22246
22375
  }
22247
22376
  ) }),
22248
- /* @__PURE__ */ jsxs37(Fragment13, { children: [
22249
- /* @__PURE__ */ jsx52(CardSection, { children: "Danger Zone" }),
22250
- /* @__PURE__ */ jsxs37(
22251
- "div",
22252
- {
22253
- style: {
22254
- display: "flex",
22255
- flexDirection: "column",
22256
- width: "fit-content",
22257
- gap: 16
22258
- },
22259
- children: [
22260
- /* @__PURE__ */ jsx52(
22261
- SecondaryButtonComponent,
22262
- {
22263
- onClick: () => setIsConnectDatabaseModalOpen(true),
22264
- label: "Edit Database Connection",
22265
- disabled: isEnvironmentModalLoading
22266
- }
22267
- ),
22268
- /* @__PURE__ */ jsx52(
22269
- SecondaryButtonComponent,
22270
- {
22271
- onClick: handleDeleteEnvironment,
22272
- label: "Delete Environment",
22273
- disabled: isEnvironmentModalLoading
22274
- }
22275
- )
22276
- ]
22277
- }
22278
- )
22279
- ] }),
22377
+ /* @__PURE__ */ jsx52(CardSection, { children: "Danger Zone" }),
22378
+ /* @__PURE__ */ jsxs37(
22379
+ "div",
22380
+ {
22381
+ style: {
22382
+ display: "flex",
22383
+ flexDirection: "column",
22384
+ width: "fit-content",
22385
+ gap: 16
22386
+ },
22387
+ children: [
22388
+ /* @__PURE__ */ jsx52(
22389
+ SecondaryButtonComponent,
22390
+ {
22391
+ onClick: () => setIsConnectDatabaseModalOpen(true),
22392
+ label: "Edit Database Connection",
22393
+ disabled: isEnvironmentModalLoading
22394
+ }
22395
+ ),
22396
+ /* @__PURE__ */ jsx52(
22397
+ SecondaryButtonComponent,
22398
+ {
22399
+ onClick: handleDeleteEnvironment,
22400
+ label: "Delete Environment",
22401
+ disabled: isEnvironmentModalLoading
22402
+ }
22403
+ )
22404
+ ]
22405
+ }
22406
+ ),
22407
+ /* @__PURE__ */ jsx52(CardSection, { children: "Development Environments" }),
22408
+ /* @__PURE__ */ jsxs37(
22409
+ "div",
22410
+ {
22411
+ style: {
22412
+ display: "flex",
22413
+ flexDirection: "column",
22414
+ width: "fit-content",
22415
+ gap: 16
22416
+ },
22417
+ children: [
22418
+ relatedChildEnvironments.length > 0 && /* @__PURE__ */ jsx52(
22419
+ "table",
22420
+ {
22421
+ style: {
22422
+ borderCollapse: "collapse",
22423
+ width: "100%",
22424
+ minWidth: 280,
22425
+ fontFamily: state.theme.fontFamily,
22426
+ fontSize: 14,
22427
+ color: state.theme.primaryTextColor
22428
+ },
22429
+ children: /* @__PURE__ */ jsx52("tbody", { children: relatedChildEnvironments.map((env) => {
22430
+ const envId = clientRecordId(env);
22431
+ const cellBorder = `1px solid ${state.theme.borderColor ?? "#e7e7e7"}`;
22432
+ return /* @__PURE__ */ jsxs37("tr", { children: [
22433
+ /* @__PURE__ */ jsx52(
22434
+ "td",
22435
+ {
22436
+ style: {
22437
+ border: cellBorder,
22438
+ padding: "8px 12px",
22439
+ fontWeight: 500
22440
+ },
22441
+ children: env.name
22442
+ }
22443
+ ),
22444
+ /* @__PURE__ */ jsx52(
22445
+ "td",
22446
+ {
22447
+ style: {
22448
+ border: cellBorder,
22449
+ padding: "8px 12px",
22450
+ width: 1,
22451
+ whiteSpace: "nowrap"
22452
+ },
22453
+ children: /* @__PURE__ */ jsx52(
22454
+ ButtonComponent,
22455
+ {
22456
+ label: "Promote",
22457
+ onClick: () => setDevelopmentEnvironmentConfirm({
22458
+ action: "promote",
22459
+ environment: env
22460
+ }),
22461
+ disabled: isEnvironmentModalLoading || developmentEnvironmentConfirm != null,
22462
+ style: {
22463
+ height: 32,
22464
+ paddingLeft: 12,
22465
+ paddingRight: 12
22466
+ }
22467
+ }
22468
+ )
22469
+ }
22470
+ )
22471
+ ] }, envId ?? env.name);
22472
+ }) })
22473
+ }
22474
+ ),
22475
+ /* @__PURE__ */ jsx52("div", { children: /* @__PURE__ */ jsx52(
22476
+ SecondaryButtonComponent,
22477
+ {
22478
+ onClick: () => setDevelopmentEnvironmentConfirm({ action: "create" }),
22479
+ label: "+ Create New",
22480
+ disabled: isEnvironmentModalLoading
22481
+ }
22482
+ ) })
22483
+ ]
22484
+ }
22485
+ ),
22280
22486
  /* @__PURE__ */ jsx52("div", { style: { height: 18 } }),
22281
22487
  /* @__PURE__ */ jsxs37(
22282
22488
  "div",
@@ -22515,6 +22721,117 @@ function EditEnvironmentModal({
22515
22721
  )
22516
22722
  }
22517
22723
  ),
22724
+ /* @__PURE__ */ jsx52(
22725
+ ModalComponent,
22726
+ {
22727
+ isOpen: developmentEnvironmentConfirm != null,
22728
+ onClose: () => {
22729
+ if (isDevelopmentEnvironmentConfirmLoading) return;
22730
+ setDevelopmentEnvironmentConfirm(null);
22731
+ },
22732
+ style: {
22733
+ width: 480
22734
+ },
22735
+ children: /* @__PURE__ */ jsxs37(
22736
+ "div",
22737
+ {
22738
+ style: {
22739
+ display: "flex",
22740
+ flexDirection: "column",
22741
+ gap: 20,
22742
+ width: "100%",
22743
+ padding: 24
22744
+ },
22745
+ children: [
22746
+ /* @__PURE__ */ jsx52(CardSection, { children: developmentEnvironmentConfirm?.action === "promote" ? "Promote Environment Changes" : "Create child environment" }),
22747
+ /* @__PURE__ */ jsxs37(
22748
+ "div",
22749
+ {
22750
+ style: {
22751
+ display: "flex",
22752
+ flexDirection: "column",
22753
+ gap: 12,
22754
+ fontFamily: state.theme.fontFamily,
22755
+ fontSize: 14,
22756
+ lineHeight: 1.6,
22757
+ color: state.theme.primaryTextColor
22758
+ },
22759
+ children: [
22760
+ developmentEnvironmentConfirm?.action === "promote" ? /* @__PURE__ */ jsxs37("p", { style: { margin: 0 }, children: [
22761
+ "Promoting",
22762
+ " ",
22763
+ /* @__PURE__ */ jsxs37("strong", { children: [
22764
+ developmentEnvironmentConfirm.environment?.name ?? "this environment",
22765
+ "\xA0into\xA0",
22766
+ sourceEnvironmentName ?? "the current environment"
22767
+ ] }),
22768
+ " ",
22769
+ "will replicate the development environment's state into the current environment."
22770
+ ] }) : /* @__PURE__ */ jsxs37("p", { style: { margin: 0 }, children: [
22771
+ "Creating a child development environment for",
22772
+ " ",
22773
+ /* @__PURE__ */ jsx52("strong", { children: sourceEnvironmentName ?? "the current environment" }),
22774
+ " ",
22775
+ "will create an environment you can use to preview changes before promoting them into the currently active environment."
22776
+ ] }),
22777
+ /* @__PURE__ */ jsx52(
22778
+ "p",
22779
+ {
22780
+ style: {
22781
+ margin: 0,
22782
+ padding: "12px 14px",
22783
+ borderRadius: 6,
22784
+ border: `1px solid ${state.theme.borderColor ?? "#e7e7e7"}`,
22785
+ backgroundColor: state.theme.hoverBackgroundColor ?? "#f7f7f7",
22786
+ color: "#DC2626"
22787
+ },
22788
+ children: "This is not a sandbox operation and will immediately apply to the live environment. Your current active session will be discarded."
22789
+ }
22790
+ )
22791
+ ]
22792
+ }
22793
+ ),
22794
+ /* @__PURE__ */ jsxs37(
22795
+ "div",
22796
+ {
22797
+ style: {
22798
+ display: "flex",
22799
+ flexDirection: "row",
22800
+ justifyContent: "flex-end",
22801
+ gap: 16,
22802
+ width: "100%"
22803
+ },
22804
+ children: [
22805
+ /* @__PURE__ */ jsx52(
22806
+ SecondaryButtonComponent,
22807
+ {
22808
+ label: "Cancel",
22809
+ onClick: () => setDevelopmentEnvironmentConfirm(null),
22810
+ disabled: isDevelopmentEnvironmentConfirmLoading
22811
+ }
22812
+ ),
22813
+ /* @__PURE__ */ jsx52(
22814
+ ButtonComponent,
22815
+ {
22816
+ label: "Confirm",
22817
+ onClick: () => {
22818
+ if (developmentEnvironmentConfirm?.action === "promote") {
22819
+ handlePromoteChildEnvironment();
22820
+ } else if (developmentEnvironmentConfirm?.action === "create") {
22821
+ handleCreateChildEnvironment();
22822
+ }
22823
+ },
22824
+ disabled: isDevelopmentEnvironmentConfirmLoading
22825
+ }
22826
+ )
22827
+ ]
22828
+ }
22829
+ )
22830
+ ]
22831
+ }
22832
+ )
22833
+ }
22834
+ ),
22518
22835
  /* @__PURE__ */ jsx52(
22519
22836
  ModalComponent,
22520
22837
  {
@@ -23086,7 +23403,7 @@ function ReportCodeDetail({
23086
23403
 
23087
23404
  // src/modals/SavedQueriesModal.tsx
23088
23405
  import { useCallback as useCallback8, useEffect as useEffect29, useMemo as useMemo16, useRef as useRef20, useState as useState31 } from "react";
23089
- import { DashboardLegacy as DashboardLegacy2 } from "@quillsql/react";
23406
+ import { DashboardLegacy as DashboardLegacy2, useDashboardInternal as useDashboardInternal4 } from "@quillsql/react";
23090
23407
  import { Highlight as Highlight4, themes as themes4 } from "prism-react-renderer";
23091
23408
  import { jsx as jsx55, jsxs as jsxs40 } from "react/jsx-runtime";
23092
23409
  import { createElement as createElement4 } from "react";
@@ -23192,7 +23509,8 @@ function SavedQueriesModal({
23192
23509
  ) });
23193
23510
  }
23194
23511
  function SavedQueryTile({ report }) {
23195
- const { state, dispatch, getToken } = useAdmin();
23512
+ const { state, dispatch, getToken, eventTracking } = useAdmin();
23513
+ const { reload } = useDashboardInternal4(SAVED_QUERIES_DASHBOARD);
23196
23514
  useEffect29(() => {
23197
23515
  async function getIsOpenableByReportBuilder() {
23198
23516
  const hello = await parseQuillReportToQuillReportInternal(
@@ -23217,6 +23535,14 @@ function SavedQueryTile({ report }) {
23217
23535
  payload: report?.queryString
23218
23536
  });
23219
23537
  };
23538
+ const handleDelete = async () => {
23539
+ if (await deleteReport(report.id, state, getToken, eventTracking)) {
23540
+ reload(SAVED_QUERIES_DASHBOARD, true, {
23541
+ report: { id: report.id },
23542
+ action: "delete"
23543
+ });
23544
+ }
23545
+ };
23220
23546
  if (!report.queryString) {
23221
23547
  return null;
23222
23548
  }
@@ -23250,7 +23576,8 @@ function SavedQueryTile({ report }) {
23250
23576
  label: "Open in SQL Editor \u2197",
23251
23577
  onClick: handleOpenReportInSQLEditor
23252
23578
  }
23253
- )
23579
+ ),
23580
+ /* @__PURE__ */ jsx55(SecondaryButtonPrimitive_default, { label: "Delete", onClick: handleDelete })
23254
23581
  ]
23255
23582
  }
23256
23583
  );
@@ -23322,7 +23649,7 @@ import {
23322
23649
  } from "react";
23323
23650
  import {
23324
23651
  Chart as Chart2,
23325
- useDashboardInternal as useDashboardInternal4,
23652
+ useDashboardInternal as useDashboardInternal5,
23326
23653
  useAllReports
23327
23654
  } from "@quillsql/react";
23328
23655
  import equal2 from "fast-deep-equal";
@@ -24074,7 +24401,7 @@ var InternalDashboard = ({
24074
24401
  dashboardFilters: populatedDashboardFilters,
24075
24402
  reload,
24076
24403
  setSectionOrder
24077
- } = useDashboardInternal4(
24404
+ } = useDashboardInternal5(
24078
24405
  name,
24079
24406
  filters?.map((f) => convertCustomFilter(f)) ?? []
24080
24407
  // .concat(
@@ -29149,7 +29476,7 @@ function DashboardManager({
29149
29476
  },
29150
29477
  [dispatch]
29151
29478
  );
29152
- const { data: dashboardConfig, reload: reloadDashboard } = useDashboardInternal5(state.selectedDashboard, userFilters);
29479
+ const { data: dashboardConfig, reload: reloadDashboard } = useDashboardInternal6(state.selectedDashboard, userFilters);
29153
29480
  const { dashboards, isLoading: dashboardsLoading } = useDashboards5();
29154
29481
  useLongLoading(dashboardsLoading, {
29155
29482
  origin: "DashboardManager",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quillsql/admin",
3
- "version": "1.8.8",
3
+ "version": "1.8.9",
4
4
  "description": "Admin tools for Quill",
5
5
  "type": "module",
6
6
  "exports": {