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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions .env
Comment thread
gingerwizard marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ NEXT_ALL_IN_ONE_IMAGE_NAME_DOCKERHUB=clickhouse/clickstack-all-in-one
ALL_IN_ONE_IMAGE_NAME_DOCKERHUB=hyperdx/hyperdx-all-in-one
NEXT_OTEL_COLLECTOR_IMAGE_NAME_DOCKERHUB=clickhouse/clickstack-otel-collector
OTEL_COLLECTOR_IMAGE_NAME_DOCKERHUB=hyperdx/hyperdx-otel-collector
CODE_VERSION=2.24.1
IMAGE_VERSION_SUB_TAG=.24.1
CODE_VERSION=2.23.0
IMAGE_VERSION_SUB_TAG=.23.0
IMAGE_VERSION=2
IMAGE_NIGHTLY_TAG=2-nightly
IMAGE_LATEST_TAG=latest
Expand Down Expand Up @@ -38,11 +38,5 @@ HDX_DEV_OTEL_HTTP_PORT=4318
HDX_DEV_OTEL_METRICS_PORT=8888
HDX_DEV_OTEL_JSON_HTTP_PORT=14318

# Otel Collector version (used as Docker build arg for image tags and component versions)
# When bumping, look up the core version from the upstream manifest:
# https://github.com/open-telemetry/opentelemetry-collector-releases/blob/main/distributions/otelcol-contrib/manifest.yaml
OTEL_COLLECTOR_VERSION=0.149.0
OTEL_COLLECTOR_CORE_VERSION=1.55.0

# Otel/Clickhouse config
HYPERDX_OTEL_EXPORTER_CLICKHOUSE_DATABASE=default
34 changes: 33 additions & 1 deletion packages/api/src/routers/external-api/v2/utils/dashboards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,15 @@ const convertToExternalTileChartConfig = (
sourceId: config.source,
numberFormat: config.numberFormat,
};
case DisplayType.Bar:
return {
configType: 'sql',
displayType: DisplayType.Bar,
connectionId: config.connection,
sqlTemplate: config.sqlTemplate,
sourceId: config.source,
numberFormat: config.numberFormat,
};
case DisplayType.Search:
case DisplayType.Markdown:
case DisplayType.Heatmap:
Expand Down Expand Up @@ -239,6 +248,16 @@ const convertToExternalTileChartConfig = (
groupBy: stringValueOrDefault(config.groupBy, undefined),
numberFormat: config.numberFormat,
};
case DisplayType.Bar:
return {
displayType: config.displayType,
sourceId,
select: Array.isArray(config.select)
? [convertToExternalSelectItem(config.select[0])]
: [DEFAULT_SELECT_ITEM],
groupBy: stringValueOrDefault(config.groupBy, undefined),
numberFormat: config.numberFormat,
};
case DisplayType.Table:
return {
...pick(config, ['having', 'numberFormat', 'groupByColumnsOnLeft']),
Expand Down Expand Up @@ -370,14 +389,17 @@ export function convertToInternalTileConfig(
case 'table':
case 'number':
case 'pie':
case 'bar':
internalConfig = {
configType: 'sql',
displayType:
externalConfig.displayType === 'table'
? DisplayType.Table
: externalConfig.displayType === 'number'
? DisplayType.Number
: DisplayType.Pie,
: externalConfig.displayType === 'pie'
? DisplayType.Pie
: DisplayType.Bar,
name,
connection: externalConfig.connectionId,
sqlTemplate: externalConfig.sqlTemplate,
Expand Down Expand Up @@ -453,6 +475,16 @@ export function convertToInternalTileConfig(
name,
} satisfies BuilderSavedChartConfig;
break;
case 'bar':
internalConfig = {
...pick(externalConfig, ['groupBy', 'numberFormat']),
displayType: DisplayType.Bar,
select: [convertToInternalSelectItem(externalConfig.select[0])],
source: externalConfig.sourceId,
where: '',
name,
} satisfies BuilderSavedChartConfig;
break;
case 'search':
internalConfig = {
...pick(externalConfig, ['select', 'where']),
Expand Down
15 changes: 15 additions & 0 deletions packages/api/src/utils/zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,11 @@ const externalDashboardPieRawSqlChartConfigSchema =
displayType: z.literal('pie'),
});

const externalDashboardCategoricalBarRawSqlChartConfigSchema =
externalDashboardRawSqlChartConfigBaseSchema.extend({
displayType: z.literal('bar'),
});

const externalDashboardNumberChartConfigSchema = z.object({
displayType: z.literal('number'),
sourceId: objectIdSchema,
Expand All @@ -296,6 +301,14 @@ const externalDashboardPieChartConfigSchema = z.object({
numberFormat: NumberFormatSchema.optional(),
});

const externalDashboardCategoricalBarChartConfigSchema = z.object({
displayType: z.literal('bar'),
sourceId: objectIdSchema,
select: z.array(externalDashboardSelectItemSchema).length(1),
groupBy: z.string().max(10000).optional(),
numberFormat: NumberFormatSchema.optional(),
});

const externalDashboardSearchChartConfigSchema = z.object({
displayType: z.literal('search'),
sourceId: objectIdSchema,
Expand All @@ -317,6 +330,7 @@ const externalDashboardBuilderTileConfigSchema = z.discriminatedUnion(
externalDashboardTableChartConfigSchema,
externalDashboardNumberChartConfigSchema,
externalDashboardPieChartConfigSchema,
externalDashboardCategoricalBarChartConfigSchema,
externalDashboardMarkdownChartConfigSchema,
externalDashboardSearchChartConfigSchema,
],
Expand All @@ -334,6 +348,7 @@ const externalDashboardRawSqlTileConfigSchema = z.discriminatedUnion(
externalDashboardTableRawSqlChartConfigSchema,
externalDashboardNumberRawSqlChartConfigSchema,
externalDashboardPieRawSqlChartConfigSchema,
externalDashboardCategoricalBarRawSqlChartConfigSchema,
],
);

Expand Down
13 changes: 13 additions & 0 deletions packages/app/src/ChartUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,19 @@ export function convertToPieChartConfig(
return omit(config, ['granularity']);
}

export function convertToBarChartConfig(
config: BuilderChartConfigWithOptTimestamp,
): BuilderChartConfigWithOptTimestamp {
const convertedConfig = structuredClone(omit(config, ['granularity']));

// Apply a default limit if not already configured
if (!convertedConfig.limit) {
convertedConfig.limit = { limit: 10 };
}

return convertedConfig;
}

export function convertToTableChartConfig(
config: BuilderChartConfigWithOptTimestamp,
): BuilderChartConfigWithOptTimestamp {
Expand Down
9 changes: 9 additions & 0 deletions packages/app/src/DBDashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ import useDashboardContainers, {
import { calculateNextTilePosition, makeId } from '@/utils/tilePositioning';

import ChartContainer from './components/charts/ChartContainer';
import { DBBarChart } from './components/DBBarChart';
import { DBPieChart } from './components/DBPieChart';
import DBSqlRowTableWithSideBar from './components/DBSqlRowTableWithSidebar';
import OnboardingModal from './components/OnboardingModal';
Expand Down Expand Up @@ -658,6 +659,14 @@ const Tile = forwardRef(
config={queriedConfig}
/>
)}
{queriedConfig?.displayType === DisplayType.Bar && (
<DBBarChart
key={`${keyPrefix}-${chart.id}`}
title={title}
toolbarPrefix={toolbar}
config={queriedConfig}
/>
)}
{effectiveMarkdownConfig?.displayType ===
DisplayType.Markdown &&
'markdown' in effectiveMarkdownConfig && (
Expand Down
34 changes: 33 additions & 1 deletion packages/app/src/components/ChartDisplaySettingsDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { Controller, useForm, useWatch } from 'react-hook-form';
import {
ChartConfigWithDateRange,
DisplayType,
Expand All @@ -12,6 +12,7 @@ import {
Divider,
Drawer,
Group,
NumberInput,
Stack,
} from '@mantine/core';

Expand All @@ -21,13 +22,16 @@ import { FormatTime } from '@/useFormatTime';
import { CheckBoxControlled } from './InputControlled';
import { DEFAULT_NUMBER_FORMAT, NumberFormatForm } from './NumberFormat';

export const DEFAULT_MAX_GROUPS = 10;

export type ChartConfigDisplaySettings = Pick<
ChartConfigWithDateRange,
| 'numberFormat'
| 'alignDateRangeToGranularity'
| 'fillNulls'
| 'compareToPreviousPeriod'
> & {
limit?: { limit?: number };
groupByColumnsOnLeft?: boolean;
};
Comment thread
gingerwizard marked this conversation as resolved.

Expand Down Expand Up @@ -58,6 +62,7 @@ function applyDefaultSettings(
: settings.alignDateRangeToGranularity,
fillNulls: settings.fillNulls ?? 0,
compareToPreviousPeriod: settings.compareToPreviousPeriod ?? false,
limit: settings.limit ?? { limit: DEFAULT_MAX_GROUPS },
groupByColumnsOnLeft: settings.groupByColumnsOnLeft ?? false,
};
}
Expand Down Expand Up @@ -105,6 +110,7 @@ export default function ChartDisplaySettingsDrawer({

const isTimeChart =
displayType === DisplayType.Line || displayType === DisplayType.StackedBar;
const isBarChart = displayType === DisplayType.Bar && configType !== 'sql';

// Group By column ordering only applies to builder table charts; raw SQL
// configs let the user author whatever column order they want directly.
Expand Down Expand Up @@ -157,6 +163,32 @@ export default function ChartDisplaySettingsDrawer({
</>
)}

{isBarChart && (
Comment thread
gingerwizard marked this conversation as resolved.
<>
<Box>
<Controller
control={control}
name="limit"
render={({ field }) => (
<NumberInput
size="xs"
label="Max Number of Groups"
min={1}
max={1000}
value={field.value?.limit ?? DEFAULT_MAX_GROUPS}
onChange={v =>
field.onChange({
limit: typeof v === 'number' ? v : DEFAULT_MAX_GROUPS,
})
}
/>
)}
/>
</Box>
<Divider />
</>
)}

{showGroupByColumnsOnLeft && (
<>
<CheckBoxControlled
Expand Down
71 changes: 71 additions & 0 deletions packages/app/src/components/ChartEditor/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,77 @@ describe('validateChartForm', () => {
expect(errors.filter(e => e.path === 'series')).toHaveLength(0);
});

it('errors when Bar chart has no groupBy', () => {
const setError = jest.fn();
const errors = validateChartForm(
makeForm({
displayType: DisplayType.Bar,
source: 'source-log',
series: [seriesItem],
groupBy: undefined,
}),
logSource,
setError,
);
expect(errors).toContainEqual(
expect.objectContaining({
path: 'groupBy',
message: 'Group By is required for bar charts',
}),
);
});

it('errors when Bar chart has an empty groupBy array', () => {
const setError = jest.fn();
const errors = validateChartForm(
makeForm({
displayType: DisplayType.Bar,
source: 'source-log',
series: [seriesItem],
groupBy: [] as any,
}),
logSource,
setError,
);
expect(errors).toContainEqual(
expect.objectContaining({
path: 'groupBy',
message: 'Group By is required for bar charts',
}),
);
});

it('returns no errors for Bar chart with a valid groupBy', () => {
const setError = jest.fn();
const errors = validateChartForm(
makeForm({
displayType: DisplayType.Bar,
source: 'source-log',
series: [seriesItem],
groupBy: 'ServiceName',
}),
logSource,
setError,
);
expect(errors.filter(e => e.path === 'groupBy')).toHaveLength(0);
});

it('does not require groupBy for raw SQL Bar charts', () => {
const setError = jest.fn();
const errors = validateChartForm(
makeForm({
configType: 'sql',
displayType: DisplayType.Bar,
sqlTemplate: 'SELECT 1',
connection: 'conn-1',
groupBy: undefined,
}),
undefined,
setError,
);
expect(errors.filter(e => e.path === 'groupBy')).toHaveLength(0);
});

it('does not apply single-series limit for raw SQL Number charts', () => {
const setError = jest.fn();
const errors = validateChartForm(
Expand Down
38 changes: 38 additions & 0 deletions packages/app/src/components/ChartEditor/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ ORDER BY ts ASC;`;
export const SQL_PLACEHOLDERS: Record<DisplayType, string> = {
[DisplayType.Line]: TIMESERIES_PLACEHOLDER_SQL,
[DisplayType.StackedBar]: TIMESERIES_PLACEHOLDER_SQL,
[DisplayType.Bar]: `SELECT
SeverityText,
count()
FROM
default.otel_logs
WHERE TimestampTime >= fromUnixTimestamp64Milli({startDateMilliseconds:Int64})
AND TimestampTime < fromUnixTimestamp64Milli({endDateMilliseconds:Int64})
GROUP BY SeverityText
ORDER BY count() DESC
LIMIT 10;`,
[DisplayType.Table]: `SELECT
count()
FROM
Expand Down Expand Up @@ -94,6 +104,34 @@ export const DISPLAY_TYPE_INSTRUCTIONS: Partial<
> = {
[DisplayType.Line]: TIMESERIES_INSTRUCTIONS,
[DisplayType.StackedBar]: TIMESERIES_INSTRUCTIONS,
[DisplayType.Bar]: (
<>
<Text size="xs" fw="bold">
Result columns are plotted as follows:
</Text>
<List size="xs" withPadding spacing={3} mb="xs">
<List.Item>
<Text span size="xs" fw={600}>
Bar Value
</Text>
<Text span size="xs">
{' '}
— The first numeric column determines each bar&apos;s height.
</Text>
</List.Item>
<List.Item>
<Text span size="xs" fw={600}>
X Axis Label
</Text>
<Text span size="xs">
{' '}
— Each unique value of each string, map, and array type column will
be used as an X axis label. Group By is required.
</Text>
</List.Item>
</List>
</>
),
[DisplayType.Pie]: (
<>
<Text size="xs" fw="bold">
Expand Down
Loading
Loading