feat: Allow users to define custom column aliases for charts (#996)

<img width="1337" height="988" alt="image" src="https://github.com/user-attachments/assets/80d83541-3fa9-4ebb-b54c-3caccbd86e90" />

Resolves HDX-1719
This commit is contained in:
Mike Shi 2025-07-15 07:08:29 -07:00 committed by GitHub
parent 2741646c80
commit 33fc071dfa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 55 additions and 4 deletions

View file

@ -0,0 +1,7 @@
---
"@hyperdx/common-utils": patch
"@hyperdx/api": patch
"@hyperdx/app": patch
---
feat: Allow users to define custom column aliases for charts

View file

@ -483,6 +483,9 @@ export function formatResponseForTimeChart({
};
} = {};
const isSingleValueColumn = valueColumns.length === 1;
const hasGroupColumns = groupColumns.length > 0;
for (const row of data) {
const date = new Date(row[timestampColumn.name]);
const ts = date.getTime() / 1000;
@ -491,7 +494,8 @@ export function formatResponseForTimeChart({
const tsBucket = tsBucketMap.get(ts) ?? {};
const keyName = [
valueColumn.name,
// Simplify the display name if there's only one series and a group by
...(isSingleValueColumn && hasGroupColumns ? [] : [valueColumn.name]),
...groupColumns.map(g => row[g.name]),
].join(' · ');

View file

@ -67,7 +67,7 @@ import HDXMarkdownChart from '../HDXMarkdownChart';
import { AggFnSelectControlled } from './AggFnSelect';
import DBNumberChart from './DBNumberChart';
import { InputControlled } from './InputControlled';
import { InputControlled, TextInputControlled } from './InputControlled';
import { MetricNameSelect } from './MetricNameSelect';
import { NumberFormatInput } from './NumberFormat';
import { SourceSelectControlled } from './SourceSelect';
@ -163,6 +163,17 @@ function ChartSeriesEditorComponent({
<Divider
label={
<Group gap="xs">
<Text size="xxs">Alias</Text>
<div style={{ width: 150 }}>
<TextInputControlled
name={`${namePrefix}alias`}
control={control}
placeholder="Series alias"
onChange={() => onSubmit()}
size="xs"
/>
</div>
{(index ?? -1) > 0 && (
<Button
variant="subtle"
@ -179,6 +190,7 @@ function ChartSeriesEditorComponent({
c="dark.2"
labelPosition="right"
mb={8}
mt="sm"
/>
<Flex gap="sm" mt="xs" align="center">
<div

View file

@ -5,6 +5,8 @@ import {
InputProps,
PasswordInput,
PasswordInputProps,
TextInput,
TextInputProps,
} from '@mantine/core';
interface InputControlledProps<T extends FieldValues>
@ -23,6 +25,32 @@ interface PasswordInputControlledProps<T extends FieldValues>
rules?: Parameters<Control<T>['register']>[1];
}
interface TextInputControlledProps<T extends FieldValues>
extends Omit<TextInputProps, 'name' | 'style'>,
Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name' | 'size'> {
name: Path<T>;
control: Control<T>;
rules?: Parameters<Control<T>['register']>[1];
}
export function TextInputControlled<T extends FieldValues>({
name,
control,
rules,
...props
}: TextInputControlledProps<T>) {
return (
<Controller
name={name}
control={control}
rules={rules}
render={({ field, fieldState: { error } }) => (
<TextInput {...props} {...field} error={error?.message} />
)}
/>
);
}
export function InputControlled<T extends FieldValues>({
name,
control,

View file

@ -85,7 +85,7 @@ export const setChartSelectsAlias = (config: ChartConfigWithOptDateRange) => {
...config,
select: config.select.map(s => ({
...s,
alias: s.alias ?? `${s.aggFn}(${s.metricName})`, // use an alias if one isn't already set
alias: s.alias || `${s.aggFn}(${s.metricName})`, // use an alias if one isn't already set
})),
};
}
@ -396,7 +396,7 @@ async function renderSelectList(
}
return chSql`${expr}${
select.alias != null
select.alias != null && select.alias.trim() !== ''
? chSql` AS "${{ UNSAFE_RAW_SQL: select.alias }}"`
: []
}`;