hyperdx/packages/app/src/components/Sources/SourceForm.tsx

2125 lines
61 KiB
TypeScript
Raw Normal View History

import React, {
Fragment,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
2024-11-12 12:53:15 +00:00
import {
Control,
Controller,
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
useFieldArray,
2024-11-12 12:53:15 +00:00
useForm,
UseFormSetValue,
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
useWatch,
2024-11-12 12:53:15 +00:00
} from 'react-hook-form';
import { z } from 'zod';
import { ClickHouseQueryError } from '@hyperdx/common-utils/dist/clickhouse';
import {
MetricsDataType,
SourceKind,
sourceSchemaWithout,
TSource,
TSourceUnion,
} from '@hyperdx/common-utils/dist/types';
2024-11-12 12:53:15 +00:00
import {
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
ActionIcon,
2024-11-12 12:53:15 +00:00
Anchor,
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
Badge,
2024-11-12 12:53:15 +00:00
Box,
Button,
Center,
2024-11-12 12:53:15 +00:00
Divider,
Flex,
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
Grid,
2024-11-12 12:53:15 +00:00
Group,
Radio,
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
Select,
2024-11-12 12:53:15 +00:00
Slider,
Stack,
Text,
Tooltip,
} from '@mantine/core';
import { DateInput } from '@mantine/dates';
2024-11-12 12:53:15 +00:00
import { notifications } from '@mantine/notifications';
import {
IconCheck,
IconCirclePlus,
IconHelpCircle,
IconSettings,
IconTrash,
} from '@tabler/icons-react';
2024-11-12 12:53:15 +00:00
import { SourceSelectControlled } from '@/components/SourceSelect';
import { IS_METRICS_ENABLED, IS_SESSIONS_ENABLED } from '@/config';
2024-11-12 12:53:15 +00:00
import { useConnections } from '@/connection';
import { useExplainQuery } from '@/hooks/useExplainQuery';
import { useMetadataWithSettings } from '@/hooks/useMetadata';
2024-11-12 12:53:15 +00:00
import {
inferTableSourceConfig,
isValidMetricTable,
isValidSessionsTable,
2024-11-12 12:53:15 +00:00
useCreateSource,
useDeleteSource,
useSource,
useSources,
2024-11-12 12:53:15 +00:00
useUpdateSource,
} from '@/source';
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
import {
inferMaterializedViewConfig,
MV_AGGREGATE_FUNCTIONS,
feat: Add MV granularities and infer config from SummingMergeTree (#1550) Closes HDX-3064 # Summary This PR makes two improvements to MV configuration 1. Additional granularities are now available 2. Configuration can now be inferred from SummingMergeTree MVs (in addition to AggregatingMergeTree, which were already supported) 3. Source column inference has been improved to support cases where the target table column name does not follow the `aggFn__sourceColumn` convention. Also, tests have been updated and additional tests have been backported from the EE repo. ## Demo https://github.com/user-attachments/assets/7ba6ef0c-2187-4e07-bfda-7b4b690b7a73 ## Testing To test the Summing Merge Tree inference, create a materialized view like so: ```sql CREATE TABLE default.traces_summed_2 ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sumDuration` UInt64, `quantileDuration` AggregateFunction(quantile(0.5), UInt64) ) ENGINE = SummingMergeTree((count, sumDuration)) ORDER BY (ServiceName, StatusCode, Timestamp) SETTINGS index_granularity = 8192; CREATE MATERIALIZED VIEW default.traces_summed_mv_2 TO default.traces_summed_2 ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sumDuration` UInt64, `quantileDuration` AggregateFunction(quantile(0.5), UInt64) ) AS SELECT toStartOfInterval(Timestamp, toIntervalMinute(30)) AS Timestamp, ServiceName, StatusCode, count() AS count, sum(Duration) AS sumDuration, quantileState(0.5)(Duration) AS quantileDuration FROM default.otel_traces GROUP BY Timestamp, ServiceName, StatusCode; ```
2026-01-06 19:45:52 +00:00
MV_GRANULARITY_OPTIONS,
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
} from '@/utils/materializedViews';
2024-11-12 12:53:15 +00:00
import ConfirmDeleteMenu from '../ConfirmDeleteMenu';
import { ConnectionSelectControlled } from '../ConnectionSelect';
import { DatabaseSelectControlled } from '../DatabaseSelect';
import { DBTableSelectControlled } from '../DBTableSelect';
import { ErrorCollapse } from '../Error/ErrorCollapse';
import { InputControlled } from '../InputControlled';
import SelectControlled from '../SelectControlled';
import { SQLInlineEditorControlled } from '../SQLInlineEditor';
2024-11-12 12:53:15 +00:00
const DEFAULT_DATABASE = 'default';
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
const MV_AGGREGATE_FUNCTION_OPTIONS = MV_AGGREGATE_FUNCTIONS.map(fn => ({
value: fn,
label: fn,
}));
// TODO: maybe otel clickhouse export migrate the schema?
const OTEL_CLICKHOUSE_EXPRESSIONS = {
timestampValueExpression: 'TimeUnix',
resourceAttributesExpression: 'ResourceAttributes',
};
const CORRELATION_FIELD_MAP: Record<
SourceKind,
Record<string, { targetKind: SourceKind; targetField: keyof TSource }[]>
> = {
[SourceKind.Log]: {
metricSourceId: [
{ targetKind: SourceKind.Metric, targetField: 'logSourceId' },
],
traceSourceId: [
{ targetKind: SourceKind.Trace, targetField: 'logSourceId' },
],
},
[SourceKind.Trace]: {
logSourceId: [{ targetKind: SourceKind.Log, targetField: 'traceSourceId' }],
sessionSourceId: [
{ targetKind: SourceKind.Session, targetField: 'traceSourceId' },
],
metricSourceId: [
{ targetKind: SourceKind.Metric, targetField: 'logSourceId' },
],
},
[SourceKind.Session]: {
traceSourceId: [
{ targetKind: SourceKind.Trace, targetField: 'sessionSourceId' },
],
},
[SourceKind.Metric]: {
logSourceId: [
{ targetKind: SourceKind.Log, targetField: 'metricSourceId' },
],
},
};
2024-11-12 12:53:15 +00:00
function FormRow({
label,
children,
helpText,
}: {
label: React.ReactNode;
children: React.ReactNode;
helpText?: string;
}) {
return (
// <Group grow preventGrowOverflow={false}>
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
<Flex align="flex-start">
<Flex align="center">
<Stack
justify="center"
style={{
maxWidth: 220,
minWidth: 220,
height: '36px',
}}
>
{typeof label === 'string' ? (
<Text tt="capitalize" size="sm">
{label}
</Text>
) : (
label
)}
</Stack>
<Center
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
me="sm"
ms="sm"
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
style={{
...(!helpText ? { opacity: 0, pointerEvents: 'none' } : {}),
}}
>
<Tooltip label={helpText} color="dark" c="white" multiline maw={600}>
<IconHelpCircle size={20} className="cursor-pointer" />
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
</Tooltip>
</Center>
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
</Flex>
2024-11-12 12:53:15 +00:00
<Box
w="100%"
style={{
minWidth: 0,
}}
>
{children}
</Box>
</Flex>
);
}
type HighlightedAttributeRowProps = Omit<TableModelProps, 'setValue'> & {
id: string;
index: number;
databaseName: string;
name:
| 'highlightedTraceAttributeExpressions'
| 'highlightedRowAttributeExpressions';
tableName: string;
connectionId: string;
removeHighlightedAttribute: (index: number) => void;
};
function HighlightedAttributeRow({
id,
index,
control,
databaseName,
name,
tableName,
connectionId,
removeHighlightedAttribute,
}: HighlightedAttributeRowProps) {
const expression = useWatch({
control,
name: `${name}.${index}.sqlExpression`,
});
const alias = useWatch({
control,
name: `${name}.${index}.alias`,
});
const {
data: explainData,
error: explainError,
isLoading: explainLoading,
refetch: explainExpression,
} = useExplainQuery(
{
from: { databaseName, tableName },
connection: connectionId,
select: [{ alias, valueExpression: expression }],
where: '',
},
{ enabled: false },
);
const runExpression = () => {
if (expression) {
explainExpression();
}
};
const isExpressionValid = !!explainData?.length;
const isExpressionInvalid = explainError instanceof ClickHouseQueryError;
return (
<React.Fragment key={id}>
<Grid.Col span={3} pe={0}>
<div
style={{ display: 'contents' }}
data-name={`${name}.${index}.sqlExpression`}
>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
control={control}
name={`${name}.${index}.sqlExpression`}
disableKeywordAutocomplete
placeholder="ResourceAttributes['http.host']"
/>
</div>
</Grid.Col>
<Grid.Col span={2} ps="xs">
<Flex align="center" gap="sm">
<Text c="gray">AS</Text>
<SQLInlineEditorControlled
control={control}
name={`${name}.${index}.alias`}
placeholder="Optional Alias"
disableKeywordAutocomplete
/>
<Tooltip label="Validate expression">
<ActionIcon
size="xs"
variant="subtle"
color="gray"
loading={explainLoading}
disabled={!expression || explainLoading}
onClick={runExpression}
>
<IconCheck size={16} />
</ActionIcon>
</Tooltip>
<ActionIcon
size="xs"
variant="subtle"
color="gray"
onClick={() => removeHighlightedAttribute(index)}
>
<IconTrash size={16} />
</ActionIcon>
</Flex>
</Grid.Col>
{(isExpressionValid || isExpressionInvalid) && (
<Grid.Col span={5} pe={0} pt={0}>
{isExpressionValid && (
<Text c="green" size="xs">
Expression is valid.
</Text>
)}
{isExpressionInvalid && (
<ErrorCollapse
summary="Expression is invalid"
details={explainError?.message}
/>
)}
</Grid.Col>
)}
<Grid.Col span={3} pe={0}>
<InputControlled
control={control}
name={`${name}.${index}.luceneExpression`}
placeholder="ResourceAttributes.http.host (Optional) "
/>
</Grid.Col>
<Grid.Col span={1} pe={0}>
<Text me="sm" mt={6}>
<Tooltip
label={
'An optional, Lucene version of the above expression. If provided, it is used when searching for this attribute value.'
}
color="dark"
c="white"
multiline
maw={600}
>
<IconHelpCircle size={14} className="cursor-pointer" />
</Tooltip>
</Text>
</Grid.Col>
</React.Fragment>
);
}
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
function HighlightedAttributeExpressionsFormRow({
control,
name,
label,
helpText,
}: Omit<TableModelProps, 'setValue'> & {
name:
| 'highlightedTraceAttributeExpressions'
| 'highlightedRowAttributeExpressions';
label: string;
helpText?: string;
}) {
const databaseName = useWatch({
control,
name: 'from.databaseName',
defaultValue: DEFAULT_DATABASE,
});
const tableName = useWatch({ control, name: 'from.tableName' });
const connectionId = useWatch({ control, name: 'connection' });
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
const {
fields: highlightedAttributes,
append: appendHighlightedAttribute,
remove: removeHighlightedAttribute,
} = useFieldArray({
control,
name,
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
});
return (
<FormRow label={label} helpText={helpText}>
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
<Grid columns={5}>
{highlightedAttributes.map(({ id }, index) => (
<HighlightedAttributeRow
key={id}
{...{
id,
index,
name,
control,
databaseName,
tableName,
connectionId,
removeHighlightedAttribute,
}}
/>
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
))}
</Grid>
<Button
variant="secondary"
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
size="sm"
className="align-self-start"
mt={highlightedAttributes.length ? 'sm' : 'md'}
onClick={() => {
appendHighlightedAttribute(
{
sqlExpression: '',
luceneExpression: '',
alias: '',
},
{ shouldFocus: false },
);
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
}}
>
<IconCirclePlus size={14} className="me-2" />
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
Add expression
</Button>
</FormRow>
);
}
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
/** Component for configuring one or more materialized views */
function MaterializedViewsFormSection({ control, setValue }: TableModelProps) {
const databaseName = useWatch({
control,
name: `from.databaseName`,
defaultValue: DEFAULT_DATABASE,
});
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
const {
fields: materializedViews,
append: appendMaterializedView,
remove: removeMaterializedView,
} = useFieldArray({
control,
name: 'materializedViews',
});
return (
<Stack gap="md">
<FormRow
label="Materialized Views"
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
helpText="Configure materialized views for query optimization. These pre-aggregated views can significantly improve query performance on aggregation queries."
>
<Stack gap="md">
{materializedViews.map((field, index) => (
<MaterializedViewFormSection
key={field.id}
control={control}
mvIndex={index}
setValue={setValue}
onRemove={() => removeMaterializedView(index)}
/>
))}
<Button
variant="secondary"
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
onClick={() => {
appendMaterializedView({
databaseName: databaseName,
tableName: '',
dimensionColumns: '',
minGranularity: '',
timestampColumn: '',
aggregatedColumns: [],
});
}}
>
<Group>
<IconCirclePlus size={16} />
Add Materialized View
</Group>
</Button>
</Stack>
</FormRow>
</Stack>
);
}
/** Component for configuring a single materialized view */
function MaterializedViewFormSection({
control,
mvIndex,
onRemove,
setValue,
}: { mvIndex: number; onRemove: () => void } & TableModelProps) {
const connection = useWatch({ control, name: `connection` });
const sourceDatabaseName = useWatch({
control,
name: `from.databaseName`,
defaultValue: DEFAULT_DATABASE,
});
const mvDatabaseName = useWatch({
control,
name: `materializedViews.${mvIndex}.databaseName`,
defaultValue: sourceDatabaseName,
});
const mvTableName = useWatch({
control,
name: `materializedViews.${mvIndex}.tableName`,
defaultValue: '',
});
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
return (
<Stack gap="sm">
<Grid columns={2} flex={1}>
<Grid.Col span={1}>
<DatabaseSelectControlled
control={control}
name={`materializedViews.${mvIndex}.databaseName`}
connectionId={connection}
/>
</Grid.Col>
<Grid.Col span={1}>
<Group>
<Box flex={1}>
<DBTableSelectControlled
database={mvDatabaseName}
control={control}
name={`materializedViews.${mvIndex}.tableName`}
connectionId={connection}
/>
</Box>
<ActionIcon size="sm" onClick={onRemove}>
<IconTrash size={16} />
</ActionIcon>
</Group>
</Grid.Col>
<Grid.Col span={2}>
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
<Text size="xs" fw={500} mb={4}>
Timestamp Column
</Text>
<SQLInlineEditorControlled
tableConnection={{
databaseName: mvDatabaseName,
tableName: mvTableName,
connectionId: connection,
}}
control={control}
placeholder="Timestamp"
name={`materializedViews.${mvIndex}.timestampColumn`}
disableKeywordAutocomplete
/>
</Grid.Col>
<Grid.Col span={1}>
<Text size="xs" fw={500} mb={4}>
Granularity
<Tooltip
label={'The granularity of the timestamp column'}
color="dark"
c="white"
multiline
maw={600}
>
<IconHelpCircle size={14} className="cursor-pointer ms-1" />
</Tooltip>
</Text>
<Controller
control={control}
name={`materializedViews.${mvIndex}.minGranularity`}
render={({ field }) => (
<Select
{...field}
data={MV_GRANULARITY_OPTIONS}
placeholder="Granularity"
size="sm"
/>
)}
/>
</Grid.Col>
<Grid.Col span={1}>
<Text size="xs" fw={500} mb={4}>
Minimum Date
<Tooltip
label="(Optional) The earliest date and time (in the local timezone) for which the materialized view contains data. If not provided, then HyperDX will assume that the materialized view contains data for all dates for which the source table contains data."
color="dark"
c="white"
multiline
maw={600}
>
<IconHelpCircle size={14} className="cursor-pointer ms-1" />
</Tooltip>
</Text>
<Controller
control={control}
name={`materializedViews.${mvIndex}.minDate`}
render={({ field }) => (
<DateInput
{...field}
value={field.value ? new Date(field.value) : undefined}
onChange={dateStr =>
field.onChange(dateStr ? dateStr.toISOString() : null)
}
clearable
highlightToday
placeholder="YYYY-MM-DD HH:mm:ss"
valueFormat="YYYY-MM-DD HH:mm:ss"
/>
)}
/>
</Grid.Col>
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
</Grid>
<Box>
<Text size="xs" fw={500} mb={4}>
Dimension Columns (comma-separated)
<Tooltip
label={
'Columns which are not pre-aggregated in the materialized view and can be used for filtering and grouping.'
}
color="dark"
c="white"
multiline
maw={600}
>
<IconHelpCircle size={14} className="cursor-pointer ms-1" />
</Tooltip>
</Text>
<SQLInlineEditorControlled
tableConnection={{
databaseName: mvDatabaseName,
tableName: mvTableName,
connectionId: connection,
}}
control={control}
name={`materializedViews.${mvIndex}.dimensionColumns`}
placeholder="ServiceName, StatusCode"
disableKeywordAutocomplete
/>
</Box>
<AggregatedColumnsFormSection
control={control}
mvIndex={mvIndex}
setValue={setValue}
/>
<Divider />
</Stack>
);
}
/** Component for configuring the Aggregated Columns list for a single materialized view */
function AggregatedColumnsFormSection({
control,
setValue,
mvIndex,
}: TableModelProps & { mvIndex: number }) {
const {
fields: aggregates,
append: appendAggregate,
remove: removeAggregate,
replace: replaceAggregates,
} = useFieldArray({
control,
name: `materializedViews.${mvIndex}.aggregatedColumns`,
});
const addAggregate = useCallback(() => {
appendAggregate({ sourceColumn: '', aggFn: 'avg', mvColumn: '' });
}, [appendAggregate]);
const kind = useWatch({ control, name: 'kind' });
const connection = useWatch({ control, name: 'connection' });
const mvTableName = useWatch({
control,
name: `materializedViews.${mvIndex}.tableName`,
});
const mvDatabaseName = useWatch({
control,
name: `materializedViews.${mvIndex}.databaseName`,
});
const fromDatabaseName = useWatch({ control, name: 'from.databaseName' });
const fromTableName = useWatch({ control, name: 'from.tableName' });
const prevMvTableNameRef = useRef(mvTableName);
const metadata = useMetadataWithSettings();
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
useEffect(() => {
(async () => {
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
try {
if (mvTableName !== prevMvTableNameRef.current) {
prevMvTableNameRef.current = mvTableName;
if (
(kind === SourceKind.Log || kind === SourceKind.Trace) &&
connection &&
mvDatabaseName &&
mvTableName &&
fromDatabaseName &&
fromTableName
) {
const config = await inferMaterializedViewConfig(
{
databaseName: mvDatabaseName,
tableName: mvTableName,
connectionId: connection,
},
{
databaseName: fromDatabaseName,
tableName: fromTableName,
connectionId: connection,
},
metadata,
);
if (config) {
setValue(`materializedViews.${mvIndex}`, config);
replaceAggregates(config.aggregatedColumns ?? []);
notifications.show({
color: 'green',
feat: Add MV granularities and infer config from SummingMergeTree (#1550) Closes HDX-3064 # Summary This PR makes two improvements to MV configuration 1. Additional granularities are now available 2. Configuration can now be inferred from SummingMergeTree MVs (in addition to AggregatingMergeTree, which were already supported) 3. Source column inference has been improved to support cases where the target table column name does not follow the `aggFn__sourceColumn` convention. Also, tests have been updated and additional tests have been backported from the EE repo. ## Demo https://github.com/user-attachments/assets/7ba6ef0c-2187-4e07-bfda-7b4b690b7a73 ## Testing To test the Summing Merge Tree inference, create a materialized view like so: ```sql CREATE TABLE default.traces_summed_2 ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sumDuration` UInt64, `quantileDuration` AggregateFunction(quantile(0.5), UInt64) ) ENGINE = SummingMergeTree((count, sumDuration)) ORDER BY (ServiceName, StatusCode, Timestamp) SETTINGS index_granularity = 8192; CREATE MATERIALIZED VIEW default.traces_summed_mv_2 TO default.traces_summed_2 ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sumDuration` UInt64, `quantileDuration` AggregateFunction(quantile(0.5), UInt64) ) AS SELECT toStartOfInterval(Timestamp, toIntervalMinute(30)) AS Timestamp, ServiceName, StatusCode, count() AS count, sum(Duration) AS sumDuration, quantileState(0.5)(Duration) AS quantileDuration FROM default.otel_traces GROUP BY Timestamp, ServiceName, StatusCode; ```
2026-01-06 19:45:52 +00:00
id: 'mv-infer-success',
message:
'Partially inferred materialized view configuration from view schema.',
});
} else {
notifications.show({
color: 'yellow',
feat: Add MV granularities and infer config from SummingMergeTree (#1550) Closes HDX-3064 # Summary This PR makes two improvements to MV configuration 1. Additional granularities are now available 2. Configuration can now be inferred from SummingMergeTree MVs (in addition to AggregatingMergeTree, which were already supported) 3. Source column inference has been improved to support cases where the target table column name does not follow the `aggFn__sourceColumn` convention. Also, tests have been updated and additional tests have been backported from the EE repo. ## Demo https://github.com/user-attachments/assets/7ba6ef0c-2187-4e07-bfda-7b4b690b7a73 ## Testing To test the Summing Merge Tree inference, create a materialized view like so: ```sql CREATE TABLE default.traces_summed_2 ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sumDuration` UInt64, `quantileDuration` AggregateFunction(quantile(0.5), UInt64) ) ENGINE = SummingMergeTree((count, sumDuration)) ORDER BY (ServiceName, StatusCode, Timestamp) SETTINGS index_granularity = 8192; CREATE MATERIALIZED VIEW default.traces_summed_mv_2 TO default.traces_summed_2 ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sumDuration` UInt64, `quantileDuration` AggregateFunction(quantile(0.5), UInt64) ) AS SELECT toStartOfInterval(Timestamp, toIntervalMinute(30)) AS Timestamp, ServiceName, StatusCode, count() AS count, sum(Duration) AS sumDuration, quantileState(0.5)(Duration) AS quantileDuration FROM default.otel_traces GROUP BY Timestamp, ServiceName, StatusCode; ```
2026-01-06 19:45:52 +00:00
id: 'mv-infer-failure',
message: 'Unable to infer materialized view configuration.',
});
}
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
}
}
} catch (e) {
console.error(e);
}
})();
}, [
mvTableName,
kind,
connection,
mvDatabaseName,
fromDatabaseName,
fromTableName,
mvIndex,
replaceAggregates,
setValue,
metadata,
]);
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
return (
<Box>
<Text size="xs" mb={4}>
Pre-aggregated Columns
<Tooltip
label={'Columns which are pre-aggregated by the materialized view'}
color="dark"
c="white"
multiline
maw={600}
>
<IconHelpCircle size={14} className="cursor-pointer ms-1" />
</Tooltip>
</Text>
<Grid columns={10}>
{aggregates.map((field, colIndex) => (
<AggregatedColumnRow
key={field.id}
setValue={setValue}
control={control}
mvIndex={mvIndex}
colIndex={colIndex}
onRemove={() => removeAggregate(colIndex)}
/>
))}
</Grid>
<Button size="sm" variant="secondary" onClick={addAggregate} mt="lg">
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
<Group>
<IconCirclePlus size={16} />
Add Column
</Group>
</Button>
</Box>
);
}
/** Component to render one row in the MV Aggregated Columns section */
function AggregatedColumnRow({
control,
mvIndex,
colIndex,
onRemove,
}: TableModelProps & {
mvIndex: number;
colIndex: number;
onRemove: () => void;
}) {
const connectionId = useWatch({ control, name: `connection` });
const sourceDatabaseName = useWatch({
control,
name: `from.databaseName`,
defaultValue: DEFAULT_DATABASE,
});
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
const sourceTableName = useWatch({ control, name: `from.tableName` });
const mvDatabaseName = useWatch({
control,
name: `materializedViews.${mvIndex}.databaseName`,
defaultValue: sourceDatabaseName,
});
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
const mvTableName = useWatch({
control,
name: `materializedViews.${mvIndex}.tableName`,
});
const isCount =
useWatch({
control,
name: `materializedViews.${mvIndex}.aggregatedColumns.${colIndex}.aggFn`,
}) === 'count';
return (
<>
<Grid.Col span={2}>
<SelectControlled
control={control}
name={`materializedViews.${mvIndex}.aggregatedColumns.${colIndex}.aggFn`}
data={MV_AGGREGATE_FUNCTION_OPTIONS}
size="sm"
/>
</Grid.Col>
{!isCount && (
<Grid.Col span={4}>
<SQLInlineEditorControlled
tableConnection={{
databaseName: sourceDatabaseName,
tableName: sourceTableName,
connectionId,
}}
control={control}
name={`materializedViews.${mvIndex}.aggregatedColumns.${colIndex}.sourceColumn`}
placeholder="Source Column"
disableKeywordAutocomplete
/>
</Grid.Col>
)}
<Grid.Col span={!isCount ? 4 : 8}>
<Group wrap="nowrap">
<Box flex={1}>
<SQLInlineEditorControlled
tableConnection={{
databaseName: mvDatabaseName,
tableName: mvTableName,
connectionId,
}}
control={control}
name={`materializedViews.${mvIndex}.aggregatedColumns.${colIndex}.mvColumn`}
placeholder="View Column"
disableKeywordAutocomplete
/>
</Box>
<ActionIcon size="sm" onClick={onRemove}>
<IconTrash size={16} />
</ActionIcon>
</Group>
</Grid.Col>
</>
);
}
2024-11-12 12:53:15 +00:00
// traceModel= ...
// logModel=....
// traceModel.logModel = 'custom'
// will pop open the custom trace model form as well
// need to make sure we don't recursively render them :joy:
// OR traceModel.logModel = 'log_id_blah'
// custom always points towards the url param
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
export function LogTableModelForm(props: TableModelProps) {
const { control } = props;
const databaseName = useWatch({
control,
name: 'from.databaseName',
defaultValue: DEFAULT_DATABASE,
});
const tableName = useWatch({ control, name: 'from.tableName' });
const connectionId = useWatch({ control, name: 'connection' });
2024-11-12 12:53:15 +00:00
const [showOptionalFields, setShowOptionalFields] = useState(false);
return (
<>
<Stack gap="sm">
<FormRow
label={'Timestamp Column'}
helpText="DateTime column or expression that is part of your table's primary key."
>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="timestampValueExpression"
disableKeywordAutocomplete
/>
</FormRow>
<FormRow
label={'Default Select'}
helpText="Default columns selected in search results (this can be customized per search later)"
>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="defaultTableSelectExpression"
placeholder="Timestamp, Body"
/>
</FormRow>
<Box>
{!showOptionalFields && (
<Anchor
underline="always"
onClick={() => setShowOptionalFields(true)}
size="xs"
>
<Group gap="xs">
<IconSettings size={14} />
Configure Optional Fields
</Group>
2024-11-12 12:53:15 +00:00
</Anchor>
)}
{showOptionalFields && (
<Button
onClick={() => setShowOptionalFields(false)}
size="xs"
variant="subtle"
>
Hide Optional Fields
</Button>
)}
</Box>
</Stack>
<Stack
gap="sm"
style={{
display: showOptionalFields ? 'flex' : 'none',
}}
>
<Divider />
<FormRow label={'Service Name Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="serviceNameExpression"
placeholder="ServiceName"
/>
</FormRow>
<FormRow label={'Log Level Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="severityTextExpression"
placeholder="SeverityText"
/>
</FormRow>
<FormRow label={'Body Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
control={control}
name="bodyExpression"
placeholder="Body"
/>
</FormRow>
2024-11-12 12:53:15 +00:00
<FormRow label={'Log Attributes Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="eventAttributesExpression"
placeholder="LogAttributes"
/>
</FormRow>
<FormRow label={'Resource Attributes Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="resourceAttributesExpression"
placeholder="ResourceAttributes"
/>
</FormRow>
<FormRow
label={'Displayed Timestamp Column'}
feat: Include displayed timestamp in default order by (#1279) Closes HDX-2593 # Summary This PR adds a source's `displayedTimestampValueExpression` (if one exists) to the default order by on the search page. ## Motivation In schemas like our default otel_logs table, there are two timestamp columns: `TimestampTime DateTime` (1-second precision) and a `Timestamp DateTime64(9)` (nanosecond precision). `TimestampTime` is preferred for filtering because it is more granular and in the primary key. However, ordering by `TimestampTime` alone results in an arbitrary order of events within each second: <img width="646" height="158" alt="Screenshot 2025-10-17 at 2 28 50 PM" src="https://github.com/user-attachments/assets/298a340f-387d-4fdf-9298-622388bb6962" /> ## Details The HyperDX source configuration form already supports configuring a 'Displayed Timestamp Column" for a log source. This PR adds the same option for Trace sources. This field is inferred from the otel logs and traces schemas as `Timestamp`. <img width="999" height="383" alt="Screenshot 2025-10-17 at 2 30 13 PM" src="https://github.com/user-attachments/assets/db1ed1eb-7ab1-4d6a-a702-b45b4d2274af" /> If the source has a displayed timestamp column configured, and if this column is different than the source's timestamp value expression, then this field will be added to the default order by which is generated for the search page. This results in a more precise ordering of the events in the logs table within each second: <img width="950" height="233" alt="Screenshot 2025-10-17 at 2 33 16 PM" src="https://github.com/user-attachments/assets/1d8447c5-ce4c-40e5-bce6-f681fe881436" />
2025-10-27 13:44:48 +00:00
helpText="This DateTime column is used to display and order search results."
>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
control={control}
name="displayedTimestampValueExpression"
disableKeywordAutocomplete
/>
</FormRow>
2024-11-12 12:53:15 +00:00
<Divider />
<FormRow
label={'Correlated Metric Source'}
helpText="HyperDX Source for metrics associated with logs. Optional"
>
<SourceSelectControlled control={control} name="metricSourceId" />
</FormRow>
2024-11-12 12:53:15 +00:00
<FormRow
label={'Correlated Trace Source'}
helpText="HyperDX Source for traces associated with logs. Optional"
>
<SourceSelectControlled control={control} name="traceSourceId" />
</FormRow>
<FormRow label={'Trace Id Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="traceIdExpression"
placeholder="TraceId"
/>
</FormRow>
<FormRow label={'Span Id Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="spanIdExpression"
placeholder="SpanId"
/>
</FormRow>
2024-11-12 12:53:15 +00:00
<Divider />
{/* <FormRow
label={'Unique Row ID Expression'}
helpText="Unique identifier for a given row, will be primary key if not specified. Used for showing full row details in search results."
>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="uniqueRowIdExpression"
placeholder="Timestamp, ServiceName, Body"
/>
</FormRow> */}
{/* <FormRow label={'Table Filter Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="tableFilterExpression"
placeholder="ServiceName = 'only_this_service'"
/>
</FormRow> */}
<FormRow
label={'Implicit Column Expression'}
helpText="Column used for full text search if no property is specified in a Lucene-based search. Typically the message body of a log."
>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="implicitColumnExpression"
placeholder="Body"
/>
</FormRow>
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
<Divider />
<HighlightedAttributeExpressionsFormRow
{...props}
name="highlightedRowAttributeExpressions"
label="Highlighted Attributes"
helpText="Expressions defining row-level attributes which are displayed in the row side panel for the selected row."
/>
<HighlightedAttributeExpressionsFormRow
{...props}
name="highlightedTraceAttributeExpressions"
label="Highlighted Trace Attributes"
helpText="Expressions defining trace-level attributes which are displayed in the trace view for the selected trace."
/>
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
<Divider />
<MaterializedViewsFormSection {...props} />
2024-11-12 12:53:15 +00:00
</Stack>
</>
);
}
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
export function TraceTableModelForm(props: TableModelProps) {
const { control } = props;
const databaseName = useWatch({
control,
name: 'from.databaseName',
defaultValue: DEFAULT_DATABASE,
});
const tableName = useWatch({ control, name: 'from.tableName' });
const connectionId = useWatch({ control, name: 'connection' });
2024-11-12 12:53:15 +00:00
return (
<Stack gap="sm">
<FormRow
label={'Timestamp Column'}
helpText="DateTime column or expression defines the start of the span"
>
2024-11-12 12:53:15 +00:00
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="timestampValueExpression"
placeholder="Timestamp"
disableKeywordAutocomplete
/>
</FormRow>
2024-11-22 05:44:33 +00:00
<FormRow
label={'Default Select'}
helpText="Default columns selected in search results (this can be customized per search later)"
>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-22 05:44:33 +00:00
control={control}
name="defaultTableSelectExpression"
placeholder="Timestamp, ServiceName, StatusCode, Duration, SpanName"
/>
</FormRow>
2024-11-12 12:53:15 +00:00
<Divider />
<FormRow label={'Duration Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="durationExpression"
placeholder="Duration Column"
/>
</FormRow>
<FormRow label={'Duration Precision'}>
<Box mx="xl">
<Controller
control={control}
name="durationPrecision"
render={({ field: { onChange, value } }) => (
2024-11-12 12:53:15 +00:00
<div style={{ width: '90%', marginBottom: 8 }}>
<Slider
color="green"
defaultValue={0}
min={0}
max={9}
marks={[
{ value: 0, label: 'Seconds' },
{ value: 3, label: 'Millisecond' },
{ value: 6, label: 'Microsecond' },
{ value: 9, label: 'Nanosecond' },
]}
value={value}
onChange={onChange}
/>
</div>
)}
/>
</Box>
</FormRow>
<FormRow label={'Trace Id Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="traceIdExpression"
placeholder="TraceId"
/>
</FormRow>
<FormRow label={'Span Id Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="spanIdExpression"
placeholder="SpanId"
/>
</FormRow>
<FormRow label={'Parent Span Id Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="parentSpanIdExpression"
placeholder="ParentSpanId"
/>
</FormRow>
<FormRow label={'Span Name Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="spanNameExpression"
placeholder="SpanName"
/>
</FormRow>
2024-11-22 05:44:33 +00:00
<FormRow label={'Span Kind Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-22 05:44:33 +00:00
control={control}
name="spanKindExpression"
placeholder="SpanKind"
/>
</FormRow>
2024-11-12 12:53:15 +00:00
<Divider />
<FormRow
label={'Correlated Log Source'}
helpText="HyperDX Source for logs associated with traces. Optional"
>
<SourceSelectControlled control={control} name="logSourceId" />
</FormRow>
<FormRow
label={'Correlated Session Source'}
helpText="HyperDX Source for sessions associated with traces. Optional"
>
<SourceSelectControlled control={control} name="sessionSourceId" />
</FormRow>
<FormRow
label={'Correlated Metric Source'}
helpText="HyperDX Source for metrics associated with traces. Optional"
>
<SourceSelectControlled control={control} name="metricSourceId" />
</FormRow>
2024-11-12 12:53:15 +00:00
<FormRow label={'Status Code Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="statusCodeExpression"
placeholder="StatusCode"
/>
</FormRow>
<FormRow label={'Status Message Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="statusMessageExpression"
placeholder="StatusMessage"
/>
</FormRow>
<FormRow label={'Service Name Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-12 12:53:15 +00:00
control={control}
name="serviceNameExpression"
placeholder="ServiceName"
/>
</FormRow>
2024-11-22 05:44:33 +00:00
<FormRow label={'Resource Attributes Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-22 05:44:33 +00:00
control={control}
name="resourceAttributesExpression"
placeholder="ResourceAttributes"
/>
</FormRow>
<FormRow label={'Event Attributes Expression'}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
2024-11-22 05:44:33 +00:00
control={control}
name="eventAttributesExpression"
placeholder="SpanAttributes"
/>
</FormRow>
<FormRow
label={'Span Events Expression'}
helpText="Expression to extract span events. Used to capture events associated with spans. Expected to be Nested ( Timestamp DateTime64(9), Name LowCardinality(String), Attributes Map(LowCardinality(String), String)"
>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
control={control}
name="spanEventsValueExpression"
placeholder="Events"
/>
</FormRow>
<FormRow
label={'Implicit Column Expression'}
helpText="Column used for full text search if no property is specified in a Lucene-based search. Typically the message body of a log."
>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
control={control}
name="implicitColumnExpression"
placeholder="SpanName"
/>
</FormRow>
feat: Include displayed timestamp in default order by (#1279) Closes HDX-2593 # Summary This PR adds a source's `displayedTimestampValueExpression` (if one exists) to the default order by on the search page. ## Motivation In schemas like our default otel_logs table, there are two timestamp columns: `TimestampTime DateTime` (1-second precision) and a `Timestamp DateTime64(9)` (nanosecond precision). `TimestampTime` is preferred for filtering because it is more granular and in the primary key. However, ordering by `TimestampTime` alone results in an arbitrary order of events within each second: <img width="646" height="158" alt="Screenshot 2025-10-17 at 2 28 50 PM" src="https://github.com/user-attachments/assets/298a340f-387d-4fdf-9298-622388bb6962" /> ## Details The HyperDX source configuration form already supports configuring a 'Displayed Timestamp Column" for a log source. This PR adds the same option for Trace sources. This field is inferred from the otel logs and traces schemas as `Timestamp`. <img width="999" height="383" alt="Screenshot 2025-10-17 at 2 30 13 PM" src="https://github.com/user-attachments/assets/db1ed1eb-7ab1-4d6a-a702-b45b4d2274af" /> If the source has a displayed timestamp column configured, and if this column is different than the source's timestamp value expression, then this field will be added to the default order by which is generated for the search page. This results in a more precise ordering of the events in the logs table within each second: <img width="950" height="233" alt="Screenshot 2025-10-17 at 2 33 16 PM" src="https://github.com/user-attachments/assets/1d8447c5-ce4c-40e5-bce6-f681fe881436" />
2025-10-27 13:44:48 +00:00
<FormRow
label={'Displayed Timestamp Column'}
helpText="This DateTime column is used to display and order search results."
>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
control={control}
name="displayedTimestampValueExpression"
disableKeywordAutocomplete
/>
</FormRow>
feat: Add custom trace-level attributes above trace waterfall (#1356) Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
2025-11-18 19:34:07 +00:00
<Divider />
<HighlightedAttributeExpressionsFormRow
{...props}
name="highlightedRowAttributeExpressions"
label="Highlighted Attributes"
helpText="Expressions defining row-level attributes which are displayed in the row side panel for the selected row"
/>
<HighlightedAttributeExpressionsFormRow
{...props}
name="highlightedTraceAttributeExpressions"
label="Highlighted Trace Attributes"
helpText="Expressions defining trace-level attributes which are displayed in the trace view for the selected trace."
/>
feat: Add materialized view support (Beta) (#1507) Closes HDX-3082 # Summary This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes. This feature is intended to support: 1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source 2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram. 3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization. ## Note to Reviewer(s) This is a large PR, but the code has largely already been reviewed. - For net-new files, types, components, and utility functions, the code does not differ from the EE repo - Changes to the various services dashboard pages do not differ from the EE repo - Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.** ## Demo <details> <summary>Demo: MV Configuration</summary> https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3 </details> <details> <summary>Demo: Chart Explorer</summary> https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8 </details> <details> <summary>Demo: Dashboards</summary> https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065 </details> ## Known Limitations This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs: 1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range. 2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization. ## Differences in OSS vs EE Support - In OSS, there is a beta label on the MV configurations section - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured. ## Testing To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse: <details> <summary>Example MVs DDL</summary> ```sql CREATE TABLE default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `StatusCode` LowCardinality(String), `count` SimpleAggregateFunction(sum, UInt64), `sum__Duration` SimpleAggregateFunction(sum, UInt64), `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName); CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `version` LowCardinality(String), `StatusCode` LowCardinality(String), `count` UInt64, `sum__Duration` Int64, `avg__Duration` AggregateFunction(avg, UInt64), `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64), `min__Duration` SimpleAggregateFunction(min, UInt64), `max__Duration` SimpleAggregateFunction(max, UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, StatusCode, count() AS count, sum(Duration) AS sum__Duration, avgState(Duration) AS avg__Duration, quantileTDigestState(0.5)(Duration) AS quantile__Duration, minSimpleState(Duration) AS min__Duration, maxSimpleState(Duration) AS max__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind, StatusCode; ``` ```sql CREATE TABLE default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Timestamp) ORDER BY (Timestamp, ServiceName, SpanKind); CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m ( `Timestamp` DateTime, `ServiceName` LowCardinality(String), `SpanKind` LowCardinality(String), `histogram__Duration` AggregateFunction(histogram(20), UInt64) ) AS SELECT toStartOfMinute(Timestamp) AS Timestamp, ServiceName, SpanKind, histogramState(20)(Duration) AS histogram__Duration FROM default.otel_traces GROUP BY Timestamp, ServiceName, SpanKind; ``` </details> Then you'll need to configure the materialized views in your source settings: <details> <summary>Source Configuration (should auto-infer when MVs are selected)</summary> <img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" /> </details>
2025-12-19 16:17:23 +00:00
<Divider />
<MaterializedViewsFormSection {...props} />
2024-11-12 12:53:15 +00:00
</Stack>
);
}
export function SessionTableModelForm({ control }: TableModelProps) {
const databaseName = useWatch({
control,
name: 'from.databaseName',
defaultValue: DEFAULT_DATABASE,
});
const connectionId = useWatch({ control, name: 'connection' });
const tableName = useWatch({ control, name: 'from.tableName' });
const prevTableNameRef = useRef(tableName);
const metadata = useMetadataWithSettings();
useEffect(() => {
(async () => {
try {
if (tableName && tableName !== prevTableNameRef.current) {
prevTableNameRef.current = tableName;
const isValid = await isValidSessionsTable({
databaseName,
tableName,
connectionId,
metadata,
});
if (!isValid) {
notifications.show({
color: 'red',
message: `${tableName} is not a valid Sessions schema.`,
});
}
}
} catch (e) {
console.error(e);
notifications.show({
color: 'red',
message: e.message,
});
}
})();
}, [tableName, databaseName, connectionId, metadata]);
return (
<>
<Stack gap="sm">
<FormRow
label={'Correlated Trace Source'}
helpText="HyperDX Source for traces associated with sessions. Required"
>
<SourceSelectControlled control={control} name="traceSourceId" />
</FormRow>
2026-01-22 00:25:39 +00:00
<FormRow
label={'Timestamp Column'}
helpText="DateTime column or expression that is part of your table's primary key."
>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
control={control}
name="timestampValueExpression"
disableKeywordAutocomplete
/>
</FormRow>
</Stack>
</>
);
}
interface TableModelProps {
control: Control<TSourceUnion>;
setValue: UseFormSetValue<TSourceUnion>;
}
export function MetricTableModelForm({ control, setValue }: TableModelProps) {
const databaseName = useWatch({
control,
name: 'from.databaseName',
defaultValue: DEFAULT_DATABASE,
});
const connectionId = useWatch({ control, name: 'connection' });
const metricTables = useWatch({ control, name: 'metricTables' });
const prevMetricTablesRef = useRef(metricTables);
const metadata = useMetadataWithSettings();
useEffect(() => {
for (const [_key, _value] of Object.entries(OTEL_CLICKHOUSE_EXPRESSIONS)) {
setValue(_key as any, _value);
}
}, [setValue]);
useEffect(() => {
(async () => {
try {
if (metricTables && prevMetricTablesRef.current) {
// Check which metric table changed
for (const metricType of Object.values(MetricsDataType)) {
const newValue =
metricTables[metricType as keyof typeof metricTables];
const prevValue =
prevMetricTablesRef.current[
metricType as keyof typeof prevMetricTablesRef.current
];
if (newValue !== prevValue) {
const isValid = await isValidMetricTable({
databaseName,
tableName: newValue as string,
connectionId,
metricType: metricType as MetricsDataType,
metadata,
});
if (!isValid) {
notifications.show({
color: 'red',
message: `${newValue} is not a valid OTEL ${metricType} schema.`,
});
}
}
}
}
prevMetricTablesRef.current = metricTables;
} catch (e) {
console.error(e);
notifications.show({
color: 'red',
message: e.message,
});
}
})();
}, [metricTables, databaseName, connectionId, metadata]);
return (
<>
<Stack gap="sm">
{Object.values(MetricsDataType).map(metricType => (
<FormRow
key={metricType.toLowerCase()}
label={`${metricType} Table`}
helpText={
metricType === MetricsDataType.ExponentialHistogram ||
metricType === MetricsDataType.Summary
? `Table containing ${metricType.toLowerCase()} metrics data. Note: not yet fully supported by HyperDX`
: `Table containing ${metricType.toLowerCase()} metrics data`
}
>
<DBTableSelectControlled
connectionId={connectionId}
database={databaseName}
control={control}
name={`metricTables.${metricType.toLowerCase()}`}
/>
</FormRow>
))}
<FormRow
label={'Correlated Log Source'}
helpText="HyperDX Source for logs associated with metrics. Optional"
>
<SourceSelectControlled control={control} name="logSourceId" />
</FormRow>
</Stack>
</>
);
}
function TableModelForm({
control,
setValue,
kind,
}: {
control: Control<TSourceUnion>;
setValue: UseFormSetValue<TSourceUnion>;
kind: SourceKind;
}) {
switch (kind) {
case SourceKind.Log:
return <LogTableModelForm control={control} setValue={setValue} />;
case SourceKind.Trace:
return <TraceTableModelForm control={control} setValue={setValue} />;
case SourceKind.Session:
return <SessionTableModelForm control={control} setValue={setValue} />;
case SourceKind.Metric:
return <MetricTableModelForm control={control} setValue={setValue} />;
}
}
2024-11-12 12:53:15 +00:00
export function TableSourceForm({
sourceId,
onSave,
onCreate,
isNew = false,
defaultName,
onCancel,
}: {
sourceId?: string;
onSave?: () => void;
onCreate?: (source: TSource) => void;
onCancel?: () => void;
isNew?: boolean;
defaultName?: string;
}) {
const { data: source } = useSource({ id: sourceId });
const { data: connections } = useConnections();
const { control, setValue, handleSubmit, resetField, setError, clearErrors } =
useForm<TSourceUnion>({
defaultValues: {
kind: SourceKind.Log,
name: defaultName,
connection: connections?.[0]?.id,
from: {
databaseName: 'default',
tableName: '',
},
querySettings: source?.querySettings,
2024-11-12 12:53:15 +00:00
},
// TODO: HDX-1768 remove type assertion
values: source as TSourceUnion,
resetOptions: {
keepDirtyValues: true,
keepErrors: true,
},
});
2024-11-12 12:53:15 +00:00
const watchedConnection = useWatch({
control,
name: 'connection',
defaultValue: source?.connection,
});
const watchedDatabaseName = useWatch({
control,
name: 'from.databaseName',
defaultValue: source?.from?.databaseName || DEFAULT_DATABASE,
});
const watchedTableName = useWatch({
control,
name: 'from.tableName',
defaultValue: source?.from?.tableName,
});
const watchedKind = useWatch({
control,
name: 'kind',
defaultValue: source?.kind || SourceKind.Log,
});
const prevTableNameRef = useRef(watchedTableName);
const metadata = useMetadataWithSettings();
2024-11-12 12:53:15 +00:00
useEffect(() => {
(async () => {
2024-11-12 12:53:15 +00:00
try {
if (watchedTableName !== prevTableNameRef.current) {
prevTableNameRef.current = watchedTableName;
if (
watchedConnection != null &&
watchedDatabaseName != null &&
(watchedKind === SourceKind.Metric || watchedTableName != null)
) {
const config = await inferTableSourceConfig({
databaseName: watchedDatabaseName,
tableName:
watchedKind !== SourceKind.Metric ? watchedTableName : '',
connectionId: watchedConnection,
metadata,
2024-11-12 12:53:15 +00:00
});
if (Object.keys(config).length > 0) {
notifications.show({
color: 'green',
message:
'Automatically inferred source configuration from table schema.',
});
}
Object.entries(config).forEach(([key, value]) => {
resetField(key as any, {
keepDirty: true,
defaultValue: value,
});
2024-11-12 12:53:15 +00:00
});
}
2024-11-12 12:53:15 +00:00
}
} catch (e) {
console.error(e);
}
})();
}, [
watchedTableName,
watchedConnection,
watchedDatabaseName,
watchedKind,
resetField,
metadata,
]);
2024-11-12 12:53:15 +00:00
// Sets the default connection field to the first connection after the
// connections have been loaded
useEffect(() => {
resetField('connection', { defaultValue: connections?.[0]?.id });
}, [connections, resetField]);
const kind = useWatch({
control,
name: 'kind',
defaultValue: source?.kind || SourceKind.Log,
});
2024-11-12 12:53:15 +00:00
const createSource = useCreateSource();
const updateSource = useUpdateSource();
const deleteSource = useDeleteSource();
// Bidirectional source linking
const { data: sources } = useSources();
const currentSourceId = useWatch({ control, name: 'id' });
// Watch all potential correlation fields
const logSourceId = useWatch({ control, name: 'logSourceId' });
const traceSourceId = useWatch({ control, name: 'traceSourceId' });
const metricSourceId = useWatch({ control, name: 'metricSourceId' });
const sessionTraceSourceId = useWatch({ control, name: 'traceSourceId' }); // For sessions
const prevLogSourceIdRef = useRef(logSourceId);
const prevTraceSourceIdRef = useRef(traceSourceId);
const prevMetricSourceIdRef = useRef(metricSourceId);
const prevSessionTraceSourceIdRef = useRef(sessionTraceSourceId);
useEffect(() => {
(async () => {
if (!currentSourceId || !sources || !kind) return;
const correlationFields = CORRELATION_FIELD_MAP[kind];
if (!correlationFields) return;
// Check each field for changes
const changedFields: Array<{
name: keyof TSourceUnion;
value: string | undefined;
}> = [];
if (logSourceId !== prevLogSourceIdRef.current) {
prevLogSourceIdRef.current = logSourceId;
changedFields.push({
name: 'logSourceId' as keyof TSourceUnion,
value: logSourceId ?? undefined,
});
}
if (traceSourceId !== prevTraceSourceIdRef.current) {
prevTraceSourceIdRef.current = traceSourceId;
changedFields.push({
name: 'traceSourceId' as keyof TSourceUnion,
value: traceSourceId ?? undefined,
});
}
if (metricSourceId !== prevMetricSourceIdRef.current) {
prevMetricSourceIdRef.current = metricSourceId;
changedFields.push({
name: 'metricSourceId' as keyof TSourceUnion,
value: metricSourceId ?? undefined,
});
}
if (
sessionTraceSourceId !== prevSessionTraceSourceIdRef.current &&
kind === SourceKind.Session
) {
prevSessionTraceSourceIdRef.current = sessionTraceSourceId;
changedFields.push({
name: 'traceSourceId' as keyof TSourceUnion,
value: sessionTraceSourceId ?? undefined,
});
}
for (const {
name: fieldName,
value: newTargetSourceId,
} of changedFields) {
if (!(fieldName in correlationFields)) continue;
const targetConfigs = correlationFields[fieldName];
for (const { targetKind, targetField } of targetConfigs) {
// Find the previously linked source if any
const previouslyLinkedSource = sources.find(
s => s.kind === targetKind && s[targetField] === currentSourceId,
);
// If there was a previously linked source and it's different from the new one, unlink it
if (
previouslyLinkedSource &&
previouslyLinkedSource.id !== newTargetSourceId
) {
await updateSource.mutateAsync({
source: {
...previouslyLinkedSource,
[targetField]: undefined,
} as TSource,
});
}
// If a new source is selected, link it back
if (newTargetSourceId) {
const targetSource = sources.find(s => s.id === newTargetSourceId);
if (targetSource && targetSource.kind === targetKind) {
// Only update if the target field is empty to avoid overwriting existing correlations
if (!targetSource[targetField]) {
await updateSource.mutateAsync({
source: {
...targetSource,
[targetField]: currentSourceId,
} as TSource,
});
}
}
}
}
}
})();
}, [
logSourceId,
traceSourceId,
metricSourceId,
sessionTraceSourceId,
kind,
currentSourceId,
sources,
updateSource,
]);
const sourceFormSchema = sourceSchemaWithout({ id: true });
const handleError = useCallback(
(error: z.ZodError<TSourceUnion>) => {
const errors = error.errors;
for (const err of errors) {
const errorPath: string = err.path.join('.');
// TODO: HDX-1768 get rid of this type assertion if possible
setError(errorPath as any, { ...err });
}
notifications.show({
color: 'red',
message: (
<Stack>
<Text size="sm">
<b>Failed to create source</b>
</Text>
{errors.map((err, i) => (
<Text key={i} size="sm">
{err.message}
</Text>
))}
</Stack>
),
});
},
[setError],
);
2024-11-12 12:53:15 +00:00
const _onCreate = useCallback(() => {
clearErrors();
handleSubmit(async data => {
const parseResult = sourceFormSchema.safeParse(data);
if (parseResult.error) {
handleError(parseResult.error);
return;
}
2024-11-12 12:53:15 +00:00
createSource.mutate(
// TODO: HDX-1768 get rid of this type assertion
{ source: data as TSource },
2024-11-12 12:53:15 +00:00
{
onSuccess: async newSource => {
// Handle bidirectional linking for new sources
const correlationFields = CORRELATION_FIELD_MAP[newSource.kind];
if (correlationFields && sources) {
for (const [fieldName, targetConfigs] of Object.entries(
correlationFields,
)) {
const targetSourceId = (newSource as any)[fieldName];
if (targetSourceId) {
for (const { targetKind, targetField } of targetConfigs) {
const targetSource = sources.find(
s => s.id === targetSourceId,
);
if (targetSource && targetSource.kind === targetKind) {
// Only update if the target field is empty to avoid overwriting existing correlations
if (!targetSource[targetField]) {
await updateSource.mutateAsync({
source: {
...targetSource,
[targetField]: newSource.id,
} as TSource,
});
}
}
}
}
}
}
onCreate?.(newSource);
2024-11-12 12:53:15 +00:00
notifications.show({
color: 'green',
message: 'Source created',
});
},
onError: error => {
2024-11-12 12:53:15 +00:00
notifications.show({
color: 'red',
message: `Failed to create source - ${error.message}`,
2024-11-12 12:53:15 +00:00
});
},
},
);
})();
}, [
clearErrors,
handleError,
sourceFormSchema,
handleSubmit,
createSource,
onCreate,
sources,
updateSource,
]);
2024-11-12 12:53:15 +00:00
const _onSave = useCallback(() => {
clearErrors();
2024-11-12 12:53:15 +00:00
handleSubmit(data => {
const parseResult = sourceFormSchema.safeParse(data);
if (parseResult.error) {
handleError(parseResult.error);
return;
}
2024-11-12 12:53:15 +00:00
updateSource.mutate(
// TODO: HDX-1768 get rid of this type assertion
{ source: data as TSource },
2024-11-12 12:53:15 +00:00
{
onSuccess: () => {
onSave?.();
notifications.show({
color: 'green',
message: 'Source updated',
});
},
onError: () => {
notifications.show({
color: 'red',
message: 'Failed to update source',
});
},
},
);
})();
}, [
handleSubmit,
updateSource,
onSave,
clearErrors,
handleError,
sourceFormSchema,
]);
2024-11-12 12:53:15 +00:00
const databaseName = useWatch({
control,
name: 'from.databaseName',
defaultValue: source?.from?.databaseName || DEFAULT_DATABASE,
});
const connectionId = useWatch({
control,
name: 'connection',
defaultValue: source?.connection,
});
const {
fields: querySettingFields,
append: appendSetting,
remove: removeSetting,
} = useFieldArray({ control, name: 'querySettings' });
2024-11-12 12:53:15 +00:00
return (
<div
style={
{
// maxWidth: 700
}
}
>
<Stack gap="md" mb="md">
<Text mb="lg">Source Settings</Text>
2024-11-12 12:53:15 +00:00
<FormRow label={'Name'}>
<InputControlled
control={control}
name="name"
rules={{ required: 'Name is required' }}
/>
</FormRow>
<FormRow label={'Source Data Type'}>
<Controller
control={control}
name="kind"
render={({ field: { onChange, value } }) => (
<Radio.Group
value={value}
onChange={v => onChange(v)}
2024-11-12 12:53:15 +00:00
withAsterisk
>
<Group>
<Radio value={SourceKind.Log} label="Log" />
<Radio value={SourceKind.Trace} label="Trace" />
{IS_METRICS_ENABLED && (
<Radio value={SourceKind.Metric} label="OTEL Metrics" />
)}
{IS_SESSIONS_ENABLED && (
<Radio value={SourceKind.Session} label="Session" />
)}
2024-11-12 12:53:15 +00:00
</Group>
</Radio.Group>
)}
/>
</FormRow>
<FormRow label={'Server Connection'}>
<ConnectionSelectControlled control={control} name={`connection`} />
</FormRow>
<FormRow label={'Database'}>
<DatabaseSelectControlled
control={control}
name={`from.databaseName`}
connectionId={connectionId}
/>
</FormRow>
{kind !== SourceKind.Metric && (
<FormRow label={'Table'}>
<DBTableSelectControlled
database={databaseName}
control={control}
name={`from.tableName`}
connectionId={connectionId}
rules={{ required: 'Table is required' }}
/>
</FormRow>
)}
<FormRow
label={
<Anchor
href="https://clickhouse.com/docs/operations/settings/settings"
size="sm"
target="_blank"
>
Query Settings
</Anchor>
}
helpText="Query-level Session Settings that will be added to each query for this source."
>
<Grid columns={11}>
{querySettingFields.map((field, index) => (
<Fragment key={field.id}>
<Grid.Col span={5} pe={0}>
<InputControlled
placeholder="Setting"
control={control}
name={`querySettings.${index}.setting`}
/>
</Grid.Col>
<Grid.Col span={5} pe={0}>
<InputControlled
placeholder="Value"
control={control}
name={`querySettings.${index}.value`}
/>
</Grid.Col>
<Grid.Col span={1} ps={0}>
<Flex align="center" justify="center" gap="sm" h="100%">
<ActionIcon
variant="subtle"
color="gray"
title="Remove setting"
onClick={() => removeSetting(index)}
>
<IconTrash size={16} />
</ActionIcon>
</Flex>
</Grid.Col>
</Fragment>
))}
</Grid>
<Button
variant="secondary"
size="sm"
color="gray"
mt="md"
disabled={querySettingFields.length >= 10}
onClick={() => {
if (querySettingFields.length < 10) {
appendSetting({ setting: '', value: '' });
}
}}
>
<IconCirclePlus size={14} className="me-2" />
Add Setting
</Button>
</FormRow>
2024-11-12 12:53:15 +00:00
</Stack>
<TableModelForm control={control} setValue={setValue} kind={kind} />
<Group justify="flex-end" mt="lg">
{onCancel && (
<Button variant="secondary" onClick={onCancel} size="xs">
Cancel
</Button>
)}
{isNew ? (
<Button
variant="primary"
onClick={_onCreate}
size="xs"
loading={createSource.isPending}
>
Save New Source
</Button>
) : (
<>
<ConfirmDeleteMenu
onDelete={() => deleteSource.mutate({ id: sourceId ?? '' })}
/>
<Button
variant="primary"
onClick={_onSave}
size="xs"
loading={createSource.isPending}
>
Save Source
</Button>
</>
)}
</Group>
2024-11-12 12:53:15 +00:00
</div>
);
}