Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,41 @@ repository's webhooks. Ensure that the Git repository can push events which
should trigger a build on Cube Cloud. Back in Cube Cloud,
click **Connect** to test the webhook.

### Webhook format

Cube recognizes push webhooks in the formats sent by GitLab, Bitbucket, and
Azure DevOps out of the box, so you can usually point your repository's native
webhook integration at the webhook URL without any extra configuration.

If your Git host does not send one of these formats (for example, AWS
CodeCommit), or you want to trigger a sync from a CI/CD pipeline or manually,
send a `POST` request to the webhook URL with the `x-git-event: push` header:

```bash
curl -X POST "<CUBE-GIT-WEBHOOK-URL>" \
-H "x-git-event: push" \
-H "Content-Type: application/json" \
--data '{"ref": "refs/heads/main"}'
```

- Use the full webhook URL exactly as shown in the UI — it includes the
`token` query parameter that authenticates the request.
- The `x-git-event: push` header is required. Requests that do not carry a
recognized Git event are ignored: Cube responds with `204`, does not pull
the repository, and the request is not shown under recent webhook requests.
- `ref` is optional and names the branch to sync (in the `refs/heads/<branch>`
form). If omitted, the production branch configured for the deployment is
used.

Response status codes:

| Status | Meaning |
| ------ | ----------------------------------------------------------------------------------------- |
| `201` | The event was accepted and a build was triggered |
| `204` | The event was ignored — no recognized Git event in the request, or nothing new to build |
| `403` | The `token` query parameter is missing or invalid |
| `429` | Rate limited — there is a short per-branch cooldown between webhook calls; retry in ~30 s |

## Step 4: Connect your Database

Enter your credentials to connect to your database. Check the [connecting to
Expand Down
114 changes: 79 additions & 35 deletions docs-mintlify/docs/explore-analyze/scheduled-tasks.mdx
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
---
title: Scheduled Tasks
description: Save a natural-language agent prompt and have Cube run it automatically on a schedule, producing an Analytics Chat thread for each run.
description: Save a natural-language agent prompt and have Cube run it automatically on a schedule, producing a chat thread for each run.
---

<Warning>

Scheduled Tasks are currently in preview, and the user experience may still
change. Reach out to the [Cube support team](/admin/account-billing/support)
to activate this feature for your account.

</Warning>

Scheduled Tasks let you save a natural-language prompt for the
Scheduled Tasks let you run tasks on a schedule — or whenever you need them.
Save a natural-language prompt for the
[agent](/docs/explore-analyze/analytics-chat) and have Cube run it
automatically on a schedule or on demand. Each run produces an
[Analytics Chat](/docs/explore-analyze/analytics-chat) thread you can open
later to read the agent's answer.
automatically on a schedule or on demand. Each run produces a chat thread
you can open later to read the agent's answer. You can also ask the agent to create a
scheduled task for you from any of your chats.

For example, you might schedule a task to *"every weekday at 9am, summarize
yesterday's signups and flag anything anomalous"* and review the resulting
chat each morning.
yesterday's signups, flag anything anomalous, and email me the results"* and
review the summary each morning.

Scheduled Tasks are **scoped to a deployment** — each task belongs to the
deployment it was created in.
Expand All @@ -30,7 +23,7 @@ In the deployment sidebar, open **Scheduled** (the clock icon, below
**Explore**). The page lists the deployment's tasks with their name,
schedule, status, and description, and is searchable.

{/* TODO: screenshot — Scheduled Tasks list page in the deployment sidebar */}
{/* TODO: screenshot — Scheduled Tasks list page in the deployment sidebar, showing the New task dropdown */}

## Anatomy of a task

Expand Down Expand Up @@ -62,37 +55,88 @@ the schedule fires; it is stored per task.

From the list, you can:

- **Create** a new task.
- **Create** a new task. The **New task** button is a dropdown with two
options:
- **Create with agent** — opens a new Analytics Chat pre-seeded with a
message asking the agent to explain scheduled tasks and interview you
about what the task should do and when it should run. The agent then
creates the task for you (see
[Managing tasks from chat](#managing-tasks-from-chat)).
- **Set up manually** — opens the create dialog where you fill in the
task's details yourself.
- **Edit** an existing task's instructions, schedule, or details.
- **Enable / Pause** a scheduled task to control whether it runs on schedule.
- **Run now** — trigger a one-off run immediately. This works for both manual
and scheduled tasks.
and scheduled tasks. Triggering a run shows a notification with a **View**
link straight to the run's chat thread, and the thread appears in the
Recent Chats sidebar immediately.
- **Delete** a task. Deleting removes its schedule and stops all future runs.

{/* TODO: screenshot — New task dropdown with Create with agent and Set up manually options */}

## Task detail page

Clicking a task in the list opens its detail page. The header shows a
breadcrumb back to Scheduled Tasks, the task name, a status tag (**Manual**,
**Active**, or **Paused**), and the description, along with actions to
**Edit** (pencil), **Delete** (trash), and a primary **Run now** button.

The page shows:

- **History** — the task's runs, newest first. Each entry is a timestamped
link that opens that run's chat thread. Currently-executing runs
show a **Running** tag, and failed runs a **Failed** tag. The list shows
the latest 50 runs; a "Showing the latest 50 runs" note appears once the
cap is hit.
- **Instructions** — the task's prompt.
- **Repeats** — the schedule in plain language.

{/* TODO: screenshot — task detail page with History, Instructions, and Repeats */}

## Reading the output

Each run creates an [Analytics Chat](/docs/explore-analyze/analytics-chat)
thread containing the agent's response. Open the thread from the chat UI to
read the full answer, ask follow-up questions, or
Each run creates a chat thread containing the agent's response. Open the
thread in [Analytics Chat](/docs/explore-analyze/analytics-chat) to read the
full answer, ask follow-up questions, or
[save results to a Workbook](/docs/explore-analyze/workbooks).
Scheduled-run threads are marked in the Recent Chats sidebar with a clock
icon (hover over it to see the "Scheduled task" tooltip).

A run shows as **Running** while it executes, then completes or fails. A
failed run's thread shows a failure notice instead of an empty thread.

The task runs headlessly under the security context of the user who created
it, so it sees exactly the data that user can access.

### What the agent can do in a scheduled run

Beyond querying the semantic model, the agent in a scheduled run can:

- **Send email to workspace members** — for example, *"summarize yesterday's
signups and email the summary to me."* This requires the agent email tool
to be enabled for the workspace; recipients are restricted to workspace
members.
- **Use web search**.
- **Create and update reports, workbooks, and dashboards**, using the task
creator's permissions.

Scheduled runs do not yet have data-model access — editing the semantic
layer is currently available only in interactive chat.

## Managing tasks from chat

You can also create and manage Scheduled Tasks conversationally in
[Analytics Chat](/docs/explore-analyze/analytics-chat). Ask the agent to
schedule, update, list, or delete tasks in plain language — for example,
*"schedule a daily summary of yesterday's signups at 9am"* or *"list my
scheduled tasks."*

## Preview limitations

- Scheduled Tasks must be activated for your account by the
[Cube support team](/admin/account-billing/support).
- Results are delivered only as Analytics Chat threads — there is no email or
Slack delivery.
- Missed runs (for example, while a deployment is unavailable) are not caught
up automatically.
- There is no in-app run-history view yet.
[Analytics Chat](/docs/explore-analyze/analytics-chat) — from any chat, not
just ones started with **Create with agent** (that menu option simply opens
a chat pre-seeded for this flow). Ask the agent to schedule, update, list,
or delete tasks in plain language — for example, *"schedule a daily summary
of yesterday's signups at 9am"* or *"list my scheduled tasks."*

The agent's actions render in the chat as labeled steps with a clock icon —
*"Creating scheduled task…"* / *"Created scheduled task"*, *"Listed
scheduled tasks"*, *"Updated scheduled task"*, and *"Deleted scheduled
task"*.

The agent manages task definitions: when listing tasks, it can report each
task's id, name, description, schedule, timezone, and enabled state. A
task's run history lives on its [detail page](#task-detail-page).
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,41 @@ repository's webhooks. Ensure that the Git repository can push events which
should trigger a build on Cube Cloud. Back in Cube Cloud,
click **Connect** to test the webhook.

### Webhook format

Cube recognizes push webhooks in the formats sent by GitLab, Bitbucket, and
Azure DevOps out of the box, so you can usually point your repository's native
webhook integration at the webhook URL without any extra configuration.

If your Git host does not send one of these formats (for example, AWS
CodeCommit), or you want to trigger a sync from a CI/CD pipeline or manually,
send a `POST` request to the webhook URL with the `x-git-event: push` header:

```bash
curl -X POST "<CUBE-GIT-WEBHOOK-URL>" \
-H "x-git-event: push" \
-H "Content-Type: application/json" \
--data '{"ref": "refs/heads/main"}'
```

- Use the full webhook URL exactly as shown in the UI — it includes the
`token` query parameter that authenticates the request.
- The `x-git-event: push` header is required. Requests that do not carry a
recognized Git event are ignored: Cube responds with `204`, does not pull
the repository, and the request is not shown under recent webhook requests.
- `ref` is optional and names the branch to sync (in the `refs/heads/<branch>`
form). If omitted, the production branch configured for the deployment is
used.

Response status codes:

| Status | Meaning |
| ------ | ----------------------------------------------------------------------------------------- |
| `201` | The event was accepted and a build was triggered |
| `204` | The event was ignored — no recognized Git event in the request, or nothing new to build |
| `403` | The `token` query parameter is missing or invalid |
| `429` | Rate limited — there is a short per-branch cooldown between webhook calls; retry in ~30 s |

## Step 4: Connect your Database

Enter your credentials to connect to your database. Check the [connecting to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export class DatabricksQuery extends BaseQuery {
public sqlTemplates() {
const templates = super.sqlTemplates();
templates.functions.CURRENTDATE = 'CURRENT_DATE';
templates.functions.UTCTIMESTAMP = 'TO_UTC_TIMESTAMP(CURRENT_TIMESTAMP(), CURRENT_TIMEZONE())';
templates.functions.DATETRUNC = 'DATE_TRUNC({{ args_concat }})';
templates.functions.DATEPART = 'DATE_PART({{ args_concat }})';
templates.functions.BTRIM = 'TRIM({% if args[1] is defined %}{{ args[1] }} FROM {% endif %}{{ args[0] }})';
Expand Down
3 changes: 3 additions & 0 deletions packages/cubejs-druid-driver/src/DruidQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export class DruidQuery extends BaseQuery {
delete templates.expressions.like_escape;
templates.filters.like_pattern = 'CONCAT({% if start_wild %}\'%\'{% else %}\'\'{% endif %}, LOWER({{ value }}), {% if end_wild %}\'%\'{% else %}\'\'{% endif %})';
templates.tesseract.ilike = 'LOWER({{ expr }}) {% if negated %}NOT {% endif %}LIKE {{ pattern }}';
// Druid evaluates CURRENT_TIMESTAMP in the sqlTimeZone query context, which
// defaults to UTC — assumes the connection does not override sqlTimeZone
templates.functions.UTCTIMESTAMP = 'CURRENT_TIMESTAMP';

return templates;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/cubejs-duckdb-driver/src/DuckDBQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ export class DuckDBQuery extends BaseQuery {
public sqlTemplates() {
const templates = super.sqlTemplates();
templates.functions.DATETRUNC = 'DATE_TRUNC({{ args_concat }})';
// AT TIME ZONE on TIMESTAMPTZ yields a naive TIMESTAMP in UTC (requires the ICU
// extension, which is bundled and autoloaded in the DuckDB builds used by the driver)
templates.functions.UTCTIMESTAMP = '(NOW() AT TIME ZONE \'UTC\')';
templates.functions.LEAST = 'LEAST({{ args_concat }})';
templates.functions.GREATEST = 'GREATEST({{ args_concat }})';
templates.functions.STRING_AGG = 'STRING_AGG({% if distinct %}DISTINCT {% endif %}{{ args[0] }}, COALESCE({{ args[1] }}, \'\'))';
Expand Down
3 changes: 3 additions & 0 deletions packages/cubejs-pinot-driver/src/PinotQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ export class PinotQuery extends BaseQuery {
public sqlTemplates() {
const templates = super.sqlTemplates();
templates.functions.DATETRUNC = 'DATE_TRUNC({{ args_concat }})';
// NOW() returns the current epoch millis (inherently UTC), matching the
// epoch-millis representation produced by the timestamp_literal template
templates.functions.UTCTIMESTAMP = 'NOW()';
templates.functions.STRING_AGG = 'LISTAGG({% if distinct %}DISTINCT {% endif %}{{ args_concat }})';
templates.statements.select = 'SELECT {{ select_concat | map(attribute=\'aliased\') | join(\', \') }} \n' +
'FROM (\n {{ from }}\n) AS {{ from_alias }} \n' +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ export class BigqueryQuery extends BaseQuery {
// DATEADD is being rewritten to DATE_ADD
templates.functions.DATE_ADD = 'DATETIME_ADD(DATETIME({{ args[0] }}), INTERVAL {{ interval }} {{ date_part }})';
templates.functions.CURRENTDATE = 'CURRENT_DATE';
templates.functions.UTCTIMESTAMP = 'CURRENT_TIMESTAMP()';
delete templates.functions.TO_CHAR;
delete templates.functions.PERCENTILECONT;
templates.expressions.binary = '{% if op == \'%\' %}MOD({{ left }}, {{ right }}){% else %}({{ left }} {{ op }} {{ right }}){% endif %}';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ export class ClickHouseQuery extends BaseQuery {
public sqlTemplates() {
const templates = super.sqlTemplates();
templates.functions.DATETRUNC = 'DATE_TRUNC({{ args_concat }})';
templates.functions.UTCTIMESTAMP = 'now(\'UTC\')';
templates.functions.STRING_AGG = 'arrayStringConcat(group{% if distinct %}Uniq{% endif %}Array({{ args[0] }}), {{ args[1] }})';
// TODO: Introduce additional filter in jinja? or parseDateTimeBestEffort?
// https://github.com/ClickHouse/ClickHouse/issues/19351
Expand Down
1 change: 1 addition & 0 deletions packages/cubejs-schema-compiler/src/adapter/MssqlQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ export class MssqlQuery extends BaseQuery {
const templates = super.sqlTemplates();
templates.functions.LEAST = 'LEAST({{ args_concat }})';
templates.functions.GREATEST = 'GREATEST({{ args_concat }})';
templates.functions.UTCTIMESTAMP = 'GETUTCDATE()';
// MSSQL ROUND requires 2 arguments: ROUND(number, length)
templates.functions.ROUND = 'ROUND({{ args_concat }}{% if args | length < 2 %}, 0{% endif %})';
// NOTE: MSSQL does not support DISTINCT clause. No workaround is available
Expand Down
1 change: 1 addition & 0 deletions packages/cubejs-schema-compiler/src/adapter/MysqlQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ export class MysqlQuery extends BaseQuery {
public sqlTemplates() {
const templates = super.sqlTemplates();
templates.functions.STRING_AGG = 'GROUP_CONCAT({% if distinct %}DISTINCT {% endif %}{{ args[0] }} SEPARATOR {{ args[1] }})';
templates.functions.UTCTIMESTAMP = 'UTC_TIMESTAMP()';
// PERCENTILE_CONT works but requires PARTITION BY
delete templates.functions.PERCENTILECONT;
templates.quotes.identifiers = '`';
Expand Down
1 change: 1 addition & 0 deletions packages/cubejs-schema-compiler/src/adapter/OracleQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ export class OracleQuery extends BaseQuery {

public sqlTemplates() {
const templates = super.sqlTemplates();
templates.functions.UTCTIMESTAMP = 'SYS_EXTRACT_UTC(SYSTIMESTAMP)';
// Oracle forbids `AS` before a table/subquery alias.
templates.expressions.query_aliased = '{{ query }} {{ quoted_alias }}';
// Oracle does not support positional GROUP BY — group by expressions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export class PostgresQuery extends BaseQuery {
templates.functions.LEAST = 'LEAST({{ args_concat }})';
templates.functions.GREATEST = 'GREATEST({{ args_concat }})';
templates.functions.NOW = 'NOW({{ args_concat }})';
templates.functions.UTCTIMESTAMP = '(NOW() AT TIME ZONE \'UTC\')';
// DATEADD is being rewritten to DATE_ADD
// templates.functions.DATEADD = '({{ args[2] }} + \'{{ interval }} {{ date_part }}\'::interval)';
// TODO: is DATEDIFF expr worth documenting?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export class PrestodbQuery extends BaseQuery {
templates.functions.DATEPART = 'DATE_PART({{ args_concat }})';
templates.functions.DATEDIFF = 'DATE_DIFF(\'{{ date_part }}\', {{ args[1] }}, {{ args[2] }})';
templates.functions.CURRENTDATE = 'CURRENT_DATE';
templates.functions.UTCTIMESTAMP = 'CAST(NOW() AT TIME ZONE \'UTC\' AS TIMESTAMP)';
templates.functions.TRUNC = 'TRUNCATE({{ args_concat }})';
templates.functions.STRING_AGG = 'ARRAY_JOIN(ARRAY_AGG({% if distinct %}DISTINCT {% endif %}{{ args[0] }}), COALESCE({{ args[1] }}, \'\'))';
delete templates.functions.PERCENTILECONT;
Expand Down
3 changes: 3 additions & 0 deletions packages/cubejs-schema-compiler/src/adapter/RedshiftQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ export class RedshiftQuery extends PostgresQuery {
public sqlTemplates() {
const templates = super.sqlTemplates();
templates.functions.DLOG10 = 'LOG(10, {{ args_concat }})';
// Redshift clusters always run in UTC and GETDATE() is supported on compute
// nodes, unlike NOW(), which is a leader node–only function.
templates.functions.UTCTIMESTAMP = 'GETDATE()';
templates.functions.DATEDIFF = 'DATEDIFF({{ date_part }}, {{ args[1] }}, {{ args[2] }})';
templates.functions.STRING_AGG = 'LISTAGG({% if distinct %}DISTINCT {% endif %}{{ args_concat }})';
templates.statements.time_series_select = 'SELECT dates.f::timestamp date_from, dates.t::timestamp date_to \n' +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export class SnowflakeQuery extends BaseQuery {
templates.functions.DATEPART = 'DATE_PART({{ args_concat }})';
templates.functions.CURRENTDATE = 'CURRENT_DATE';
templates.functions.NOW = 'CURRENT_TIMESTAMP';
templates.functions.UTCTIMESTAMP = 'SYSDATE()';
templates.functions.LOG = 'LOG({% if args[1] is undefined %}10, {% endif %}{{ args_concat }})';
templates.functions.DLOG10 = 'LOG(10, {{ args_concat }})';
templates.functions.CHARACTERLENGTH = 'LENGTH({{ args[0] }})';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,26 @@ views:
},

[{ count: 1, city: 'New York', cubejoinfield: 'NULL' }, { count: 1, city: 'New York', cubejoinfield: 'NULL' }]));

it('dimension-only measure expression over multiple dimensions of the same cube', async () => runQueryTest({
measures: [
{
// eslint-disable-next-line no-new-func
expression: new Function(
'customers',
// eslint-disable-next-line no-template-curly-in-string
'return `SUM(CASE WHEN ${customers.state} = ${customers.city} THEN 1 ELSE 0 END)`'
),
// eslint-disable-next-line no-template-curly-in-string
definition: 'SUM(CASE WHEN ${customers.state} = ${customers.city} THEN 1 ELSE 0 END)',
expressionName: 'same_state_city_count',
cubeName: 'customers',
},
],
},

[{ same_state_city_count: '0' }]));

if (getEnv('nativeSqlPlanner')) {
it('member expression multi stage', async () => runQueryTest({
measures: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,14 @@ Array [
]
`;

exports[`SQL API Postgres (Data) current_timestamp subquery filter push down: current_timestamp_push_down 1`] = `
Array [
Object {
"cn": "5",
},
]
`;

exports[`SQL API Postgres (Data) date/string measures in view: date case 1`] = `
Array [
Object {
Expand Down
Loading
Loading