diff --git a/docs-mintlify/CLAUDE.md b/docs-mintlify/CLAUDE.md
index 96d0a28b5bd3f..f06f9ab5f9b18 100644
--- a/docs-mintlify/CLAUDE.md
+++ b/docs-mintlify/CLAUDE.md
@@ -124,6 +124,7 @@ Make sure to use correct terms. On billing, pricing, and support pages, use **on
- Controls
- Filter
- Time granularity
+ - Parent
- AI summary
- Layout
- Spacer
diff --git a/docs-mintlify/docs/explore-analyze/dashboards/widgets/charts.mdx b/docs-mintlify/docs/explore-analyze/dashboards/widgets/charts.mdx
index 64a5449d3b273..67a86aa6f28fe 100644
--- a/docs-mintlify/docs/explore-analyze/dashboards/widgets/charts.mdx
+++ b/docs-mintlify/docs/explore-analyze/dashboards/widgets/charts.mdx
@@ -13,7 +13,7 @@ If the picker is empty, create the report first in a workbook tab — only publi
## Interaction with controls
-Charts respect [controls][ref-controls] — filters and time granularity switchers — placed on the same dashboard. A single control can drive multiple charts at once: its value is applied to every chart whose query references the targeted dimension.
+Charts respect the [controls][ref-controls] placed on the same dashboard — filters and time granularity switchers. A single control can drive multiple charts at once: its value is applied to every chart whose query references the targeted dimension.
If some controls are incompatible with a chart's query, the chart skips them and shows a warning icon. See [Incompatible controls][ref-incompatible-controls] for details.
diff --git a/docs-mintlify/docs/explore-analyze/dashboards/widgets/controls.mdx b/docs-mintlify/docs/explore-analyze/dashboards/widgets/controls.mdx
index e3376efdba1bc..24c6e0f6eb3bf 100644
--- a/docs-mintlify/docs/explore-analyze/dashboards/widgets/controls.mdx
+++ b/docs-mintlify/docs/explore-analyze/dashboards/widgets/controls.mdx
@@ -1,16 +1,19 @@
---
title: Controls
-description: Filter and time granularity switcher widgets that let dashboard viewers change what's shown on the dashboard.
+description: Filter, time granularity switcher, and parent widgets that let dashboard viewers change what's shown on the dashboard.
---
-Controls are widgets that let dashboard viewers change what's shown without leaving the dashboard. The dashboard builder offers two control types — both target a dimension from your semantic model and apply the selected value to every [chart][ref-charts] on the dashboard whose query references that dimension.
+Controls are widgets that let dashboard viewers change what's shown without leaving the dashboard. The dashboard builder offers three control types:
- [Filter](#filter) — Narrow the data shown on the dashboard
- [Time granularity switcher](#time-granularity-switcher) — Change the granularity of time-based dimensions
+- [Parent](#parent) — Re-point several other controls at once from a single dropdown
+
+Filters and time granularity switchers each target a dimension from your semantic model, and apply the selected value to every [chart][ref-charts] on the dashboard whose query references that dimension. A parent control works one level up: it targets no dimension of its own and drives *other controls* instead.
## Filter
-Filter widgets let viewers narrow down the data shown on the dashboard. In the [dashboard builder][ref-workbooks], click **Filter** under **Add Controls** in the toolbar. The new filter is added in an unconfigured state — click **Configure Filter** (or open the widget's settings menu) to pick a semantic view and a dimension.
+Filter widgets let viewers narrow down the data shown on the dashboard. In the [dashboard builder][ref-workbooks], open the **Add Controls** menu in the toolbar and choose **Filter**. The new filter is added in an unconfigured state — click **Configure Filter** (or open the widget's settings menu) to pick a semantic view and a dimension.
### Operators by dimension type
@@ -78,7 +81,7 @@ For example, on a sales dashboard with a **Country** filter and a **City** filte
Time granularity switchers let viewers change the granularity of time-based dimensions on the dashboard — for example, switching a revenue chart from daily to weekly or monthly. The widget targets a single time dimension and applies the chosen [granularity][ref-granularities] to every chart that groups by that dimension.
-In the [dashboard builder][ref-workbooks], click **Time Granularity** under **Add Controls** in the toolbar. The new switcher is added in an unconfigured state — open its settings to pick a semantic view and a time dimension.
+In the [dashboard builder][ref-workbooks], open the **Add Controls** menu in the toolbar and choose **Time Granularity**. The new switcher is added in an unconfigured state — open its settings to pick a semantic view and a time dimension.
### Allowed granularities
@@ -92,25 +95,83 @@ Custom granularities defined in the [data model][ref-granularities] aren't offer
You can configure a default granularity that's applied when the dashboard loads. If no default is set, charts use the granularity that was saved on the underlying report — viewers can still switch granularities, but the dashboard opens with each chart at its original granularity.
+## Parent
+
+A parent control is a dropdown of options you define. Picking one re-points a whole row of other controls at once — so a viewer makes a single choice instead of adjusting three or four filters by hand.
+
+Unlike the other two control types, a parent control targets no dimension and never touches a chart query directly. It applies values to the controls it *drives* — its **children** — and those children then apply themselves to charts exactly as if the viewer had operated each one. Filters and time granularity switchers can both be children; a parent control cannot be a child of another parent control.
+
+For example, an **Analysis** parent with the options `Retail`, `Wholesale` and `Promo` can set a **Channel** filter, a **Minimum order value** filter, and a **Date range** filter to a different combination for each option. Viewers see one dropdown; you can [hide](#visibility) the children if the individual values aren't worth showing.
+
+In the [dashboard builder][ref-workbooks], open the **Add Controls** menu in the toolbar and choose **Parent**, then click **Configure Parent** to set it up. The editor has two tabs — **Options** and **Children**.
+
+
+Add the child controls to the dashboard *before* the parent control. The **Children** tab can only map controls that already exist, so a parent added to an empty dashboard has nothing to drive yet.
+
+
+### Options
+
+On the **Options** tab, type a label and click **Add** for each entry you want in the dropdown. Options appear as chips — remove one with its close button. A parent control can hold up to 50 options.
+
+Renaming an option later doesn't disturb the values you've mapped to it, so you can reword a label without redoing the mapping.
+
+### Children
+
+On the **Children** tab, pick a control from **Child control**, then give each of the parent's options a value for it. Each row renders *that child's own control* — a time granularity switcher's row shows its granularity picker, limited to the granularities that switcher allows; a filter's row shows that filter's operator and value inputs. So the values you can offer are exactly the ones a viewer could pick in the child itself.
+
+Repeat for each control you want the parent to drive. Every option/child pair can be in one of three states:
+
+| State | What happens when the viewer picks that option |
+|---|---|
+| **A value** | The child is set to that value. |
+| **Reset to default** | The child is cleared back to its own default. For a filter that means no filtering on that dimension. Turn on the row's **Reset to default** switch. |
+| **Left empty** | The child is left alone — it keeps whatever value the viewer already had. Use this deliberately when an option shouldn't have an opinion about a particular child. |
+
+While the parent's settings are open, the children it drives are highlighted on the canvas, so you can see the scope of the mapping at a glance.
+
+### One parent per child
+
+A control can be driven by only one parent control at a time. Mapping a child that another parent already drives **moves** it rather than sharing it — the editor warns you before you save, naming the parent that currently owns it.
+
+### Mapping status on child controls
+
+Once a dashboard has at least one parent control, every filter and time granularity switcher on it shows a small indicator reporting how it's driven:
+
+| Status | Meaning |
+|---|---|
+| **Fully driven** | Every option of the owning parent sets this control. |
+| **Partly driven** | Only some of the owning parent's options set this control; the rest leave it alone. |
+| **Not driven** | No parent control maps this one. |
+
+Click the indicator to jump straight to the **Children** tab of the parent that owns that control, with it already selected. For a control nothing drives yet, the click opens the first parent control on the dashboard — topmost, then leftmost — so you can map it.
+
+### Default option
+
+Set **Default value** to the option that should be selected when the dashboard loads. Choosing a default in the builder also applies that option's values to the children, so their saved defaults line up with the parent's and a published dashboard opens in a consistent state.
+
+If you leave **Default value** empty, the parent opens with nothing selected and the children use their own defaults.
+
## Visibility
-Each control has a **Visibility** setting that determines how it appears on the published dashboard. The setting applies to both filters and time granularity switchers.
+Each control has a **Visibility** setting that determines how it appears on the published dashboard. The setting applies to all three control types.
| Visibility | Behavior on the published dashboard |
|---|---|
| **Visible** (default) | Shown on the dashboard and viewers can change its value. |
-| **Hidden** | Not shown to viewers, but the control's value is still applied to the charts it targets. Use this to scope a dashboard with a fixed value — e.g., always filter to the current quarter — without exposing the control. |
+| **Hidden** | Not shown to viewers, but the control's value is still applied to the charts it targets. Use this to scope a dashboard with a fixed value — e.g., always filter to the current quarter — without exposing the control. Hiding the *children* of a [parent control](#parent) is the usual way to present one dropdown instead of the row of controls behind it. |
| **Disabled** | Shown on the dashboard so viewers can see the active value, but they cannot change it. |
Set the visibility from the **Visibility** dropdown when editing the control. **Hidden** controls remain visible in the dashboard builder so editors can reconfigure them, but disappear from the published view.
## Interaction with charts
+This section applies to filters and time granularity switchers. A [parent control](#parent) has no dimension and never applies to a chart itself, so it doesn't appear in any chart's [Controls mapping](#controls-mapping) — it acts only through the children it drives, and it's those children that show up here.
+
When a control is added to a dashboard, it's automatically wired up to every [chart][ref-charts] whose query already uses the same dimension. Charts that don't reference that dimension are left alone, so a dashboard can mix scoped and unscoped views by default. You can override this default per chart from its [Controls mapping](#controls-mapping) — disable the control for that chart, or remap it onto a different dimension.
### Incompatible controls
-If controls of a certain type are incompatible with a particular chart's query, the chart skips all controls of that type and renders the data without them. Controls of the other type still apply — for example, if filters fail but a time granularity switcher works, the chart shows the granularity-adjusted data without filtering, and vice versa.
+If controls of a certain type are incompatible with a particular chart's query, the chart skips all controls of that type and renders the data without them. Filters and time granularity switchers are skipped independently — if filters fail but a time granularity switcher works, the chart shows the granularity-adjusted data without filtering, and vice versa.
The chart displays a warning icon to indicate the problem:
diff --git a/docs-mintlify/docs/explore-analyze/dashboards/widgets/index.mdx b/docs-mintlify/docs/explore-analyze/dashboards/widgets/index.mdx
index c11b8126a0bb6..afeac4e2a1ebc 100644
--- a/docs-mintlify/docs/explore-analyze/dashboards/widgets/index.mdx
+++ b/docs-mintlify/docs/explore-analyze/dashboards/widgets/index.mdx
@@ -11,19 +11,21 @@ The dashboard builder supports the following widget types:
- [Charts](/docs/explore-analyze/dashboards/widgets/charts) — Visualize reports from your workbook
- [Text](/docs/explore-analyze/dashboards/widgets/text) — Add titles, descriptions, and rich formatting in Markdown
-- [Controls](/docs/explore-analyze/dashboards/widgets/controls) — Let viewers filter the data or switch the time granularity
+- [Controls](/docs/explore-analyze/dashboards/widgets/controls) — Let viewers filter the data, switch the time granularity, or drive several controls at once
- [AI summary](/docs/explore-analyze/dashboards/widgets/ai-summary) — Generate narrative summaries of dashboard data on demand
- [Spacer & Divider](/docs/explore-analyze/dashboards/widgets/layout) — Non-data layout elements for whitespace and section breaks (in preview)
## Adding widgets
-Add widgets from the toolbar at the top of the dashboard builder: pick reports from the **Charts** picker to add charts, use the **Add Widgets** menu for text, AI summaries, and layout elements, or add a **Filter** or **Time Granularity** control from the **Add Controls** group.
+Add widgets from the toolbar at the top of the dashboard builder: pick reports from the **Charts** picker to add charts, use the **Add Widgets** menu for text, AI summaries, and layout elements, or the **Add Controls** menu for **Filter**, **Time Granularity** and **Parent** controls.
-Each toolbar item can be added in two ways:
+Each item — a toolbar button, or an option inside the **Add Widgets** / **Add Controls** menus — can be added in two ways:
- **Click** it to drop the widget into the first open spot on the canvas.
- **Drag** it from the toolbar onto the canvas to place it exactly where you want. As you drag, a full-size placeholder previews the widget's footprint and the surrounding widgets reflow to open a slot; release to drop it there. Dragging is especially handy on dense dashboards, where clicking would otherwise place the new widget far down the page.
+Starting a drag from a menu option closes the menu, so it doesn't cover the canvas while you place the widget.
+
Dragging a toolbar item to place it exactly (drag-to-place) is currently in preview, and the behavior may still change. Reach out to the [Cube support team](/admin/account-billing/support) to activate it for your account. Clicking to add a widget is available to everyone.
diff --git a/docs-mintlify/docs/explore-analyze/workbooks/index.mdx b/docs-mintlify/docs/explore-analyze/workbooks/index.mdx
index 75421288a2d9a..390915ad0af6b 100644
--- a/docs-mintlify/docs/explore-analyze/workbooks/index.mdx
+++ b/docs-mintlify/docs/explore-analyze/workbooks/index.mdx
@@ -129,8 +129,21 @@ and paste it as a new tab in any workbook using the same workflow.
## Duplicating workbooks
You can duplicate a workbook by selecting **Duplicate** from the row
-actions menu on the workspace page. This creates a full copy of the
-workbook, including all its tabs, reports, and any published dashboard.
+actions menu on the workspace page, from the workbook menu while the
+workbook is open, or from a published dashboard's options menu. This
+creates a full copy of the workbook, including all its tabs, reports, and
+any published dashboard.
+
+A duplicate does not carry the original's sharing over by default. The
+copy is visible only to you, plus anyone with access to the folder it is
+created in.
+
+If the original is shared — with people, with groups, with your whole
+organization, or through signed embedding — the duplicate dialog offers
+**Copy sharing and embedding settings**. Selecting it gives the copy the
+same audience. The option appears only if you have **Full access** to the
+original, since copying its sharing means granting that access again. See
+[Duplicates][ref-sharing-duplicates] for what does and doesn't carry over.
## Workbook versions
@@ -151,4 +164,5 @@ has not been published will be lost.
[ref-dashboards]: /docs/explore-analyze/dashboards
+[ref-sharing-duplicates]: /docs/organize-content/sharing#duplicates
diff --git a/docs-mintlify/docs/organize-content/sharing.mdx b/docs-mintlify/docs/organize-content/sharing.mdx
index 192ba413f8c95..f8b14b3fe282e 100644
--- a/docs-mintlify/docs/organize-content/sharing.mdx
+++ b/docs-mintlify/docs/organize-content/sharing.mdx
@@ -141,6 +141,23 @@ This ensures users can always navigate to content they have permission to
view, even if they don't have explicit access to every folder along the
path.
+### Duplicates
+
+Duplicating a workbook or dashboard does not carry its sharing over by
+default — the copy starts visible only to you, plus anyone with access to
+the folder it is created in.
+
+When the original is shared, the duplicate dialog offers **Copy sharing
+and embedding settings**. Selecting it re-grants the original's users,
+groups, and organization-wide access on the copy, and carries over
+[signed embedding][ref-signed-embedding] if it was enabled. Access that
+the original inherits from a folder is not copied — the duplicate derives
+it from the folder it is created in, like any other new content there.
+
+The option requires **Full access** to the original, since copying its
+sharing means granting that access again. With **Can edit** or
+**Can view**, duplicating still works and the copy starts unshared.
+
## Sharing folders
Sharing a folder is the most efficient way to manage access for a
@@ -175,3 +192,4 @@ interface.
[ref-roles]: /admin/users-and-permissions/roles-and-permissions
[ref-google-sheets]: /docs/integrations/google-sheets
[ref-excel]: /docs/integrations/microsoft-excel
+[ref-signed-embedding]: /embedding/iframe/auth/signed
diff --git a/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregations.ts b/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregations.ts
index df5015a4a957f..8c2e824bfb77f 100644
--- a/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregations.ts
+++ b/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregations.ts
@@ -468,9 +468,10 @@ export class PreAggregations {
tables = tables.filter(row => `${schema}.${row.table_name}` === table);
// fetching query result
- const conn = await this.queue[dataSource].getQueueDriver().createConnection();
+ const queue = await this.getQueue(dataSource);
+ const conn = await queue.getQueueDriver().createConnection();
const result = await conn.getResult(key);
- this.queue[dataSource].getQueueDriver().release(conn);
+ queue.getQueueDriver().release(conn);
// calculating status
let status: string;
diff --git a/packages/cubejs-query-orchestrator/test/unit/PreAggregations.test.ts b/packages/cubejs-query-orchestrator/test/unit/PreAggregations.test.ts
index e63d6a54b8f31..17fd28ffa6e17 100644
--- a/packages/cubejs-query-orchestrator/test/unit/PreAggregations.test.ts
+++ b/packages/cubejs-query-orchestrator/test/unit/PreAggregations.test.ts
@@ -280,6 +280,38 @@ describe('PreAggregations', () => {
});
});
+ describe('isPartitionExist', () => {
+ test('initializes a missing data source queue before checking the job result', async () => {
+ const preAggregations = new PreAggregations(
+ 'TEST',
+ mockDriverFactory as any,
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
+ () => {},
+ queryCache!,
+ {
+ cacheAndQueueDriver: 'memory',
+ queueOptions: async () => ({
+ executionTimeout: 1,
+ concurrency: 2,
+ }),
+ },
+ );
+ mockDriver!.tables.push('stb_pre_aggregations.orders_main');
+
+ await expect(
+ preAggregations.isPartitionExist(
+ 'request-id',
+ false,
+ 'named_data_source',
+ 'stb_pre_aggregations',
+ 'stb_pre_aggregations.orders_main',
+ ['job-key'],
+ 'job-token',
+ )
+ ).resolves.toEqual([true, 'done']);
+ });
+ });
+
describe('loadAllPreAggregationsIfNeeded', () => {
let preAggregations: PreAggregations | null = null;