mirror of https://github.com/buster-so/buster.git
Edit metric title types
This commit is contained in:
parent
58c8c1f4ab
commit
0bd385799f
|
@ -8,14 +8,15 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue
|
SelectValue
|
||||||
} from './SelectBase';
|
} from './SelectBase';
|
||||||
|
import { useMemoizedFn } from 'ahooks';
|
||||||
|
|
||||||
interface SelectItemGroup {
|
interface SelectItemGroup<T = string> {
|
||||||
label: string;
|
label: string;
|
||||||
items: SelectItem[];
|
items: SelectItem<T>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectItem {
|
export interface SelectItem<T = string> {
|
||||||
value: string;
|
value: T;
|
||||||
label: string; //this will be used in the select item text
|
label: string; //this will be used in the select item text
|
||||||
secondaryLabel?: string;
|
secondaryLabel?: string;
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
|
@ -23,10 +24,10 @@ export interface SelectItem {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectProps {
|
export interface SelectProps<T = string> {
|
||||||
items: SelectItem[] | SelectItemGroup[];
|
items: SelectItem<T>[] | SelectItemGroup[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: T) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
onOpenChange?: (open: boolean) => void;
|
onOpenChange?: (open: boolean) => void;
|
||||||
|
@ -35,8 +36,7 @@ export interface SelectProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Select: React.FC<SelectProps> = React.memo(
|
const _Select = <T,>({
|
||||||
({
|
|
||||||
items,
|
items,
|
||||||
showIndex,
|
showIndex,
|
||||||
disabled,
|
disabled,
|
||||||
|
@ -46,13 +46,16 @@ export const Select: React.FC<SelectProps> = React.memo(
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
open,
|
open,
|
||||||
className = ''
|
className = ''
|
||||||
}) => {
|
}: SelectProps<T>) => {
|
||||||
|
const onValueChange = useMemoizedFn((value: string) => {
|
||||||
|
onChange(value as T);
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<SelectBase
|
<SelectBase
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onOpenChange={onOpenChange}
|
onOpenChange={onOpenChange}
|
||||||
open={open}
|
open={open}
|
||||||
onValueChange={onChange}>
|
onValueChange={onValueChange}>
|
||||||
<SelectTrigger className={className}>
|
<SelectTrigger className={className}>
|
||||||
<SelectValue placeholder={placeholder} defaultValue={value} />
|
<SelectValue placeholder={placeholder} defaultValue={value} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
@ -63,16 +66,19 @@ export const Select: React.FC<SelectProps> = React.memo(
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</SelectBase>
|
</SelectBase>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
);
|
_Select.displayName = 'Select';
|
||||||
|
export const Select = React.memo(_Select) as typeof _Select;
|
||||||
|
|
||||||
Select.displayName = 'Select';
|
const SelectItemSelector = <T,>({
|
||||||
|
item,
|
||||||
const SelectItemSelector: React.FC<{
|
index,
|
||||||
item: SelectItem | SelectItemGroup;
|
showIndex
|
||||||
|
}: {
|
||||||
|
item: SelectItem<T> | SelectItemGroup;
|
||||||
index: number;
|
index: number;
|
||||||
showIndex?: boolean;
|
showIndex?: boolean;
|
||||||
}> = React.memo(({ item, index, showIndex }) => {
|
}) => {
|
||||||
const isGroup = typeof item === 'object' && 'items' in item;
|
const isGroup = typeof item === 'object' && 'items' in item;
|
||||||
|
|
||||||
if (isGroup) {
|
if (isGroup) {
|
||||||
|
@ -101,7 +107,7 @@ const SelectItemSelector: React.FC<{
|
||||||
{label}
|
{label}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
SelectItemSelector.displayName = 'SelectItemSelector';
|
SelectItemSelector.displayName = 'SelectItemSelector';
|
||||||
const SelectItemSecondaryText: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
const SelectItemSecondaryText: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './AppSwitch';
|
|
@ -1,10 +1,11 @@
|
||||||
import { AppMaterialIcons, AppPopover } from '@/components/ui';
|
import { Button } from '@/components/ui/buttons';
|
||||||
import { Button } from 'antd';
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { SelectAxisContainerId } from '../config';
|
import { SelectAxisContainerId } from '../config';
|
||||||
import { SelectAxisSettingContent } from './SelectAxisSettingContent';
|
import { SelectAxisSettingContent } from './SelectAxisSettingContent';
|
||||||
import { useSelectAxisContextSelector } from '../useSelectAxisContext';
|
import { useSelectAxisContextSelector } from '../useSelectAxisContext';
|
||||||
import { zoneIdToAxisSettingContent } from './config';
|
import { zoneIdToAxisSettingContent } from './config';
|
||||||
|
import { Popover } from '@/components/ui/tooltip/Popover';
|
||||||
|
import { Sliders3 } from '@/components/ui/icons';
|
||||||
|
|
||||||
export const SelectAxisSettingsButton: React.FC<{
|
export const SelectAxisSettingsButton: React.FC<{
|
||||||
zoneId: SelectAxisContainerId;
|
zoneId: SelectAxisContainerId;
|
||||||
|
@ -20,14 +21,13 @@ export const SelectAxisSettingsButton: React.FC<{
|
||||||
if (!canUseAxisSetting) return null;
|
if (!canUseAxisSetting) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppPopover
|
<Popover
|
||||||
content={<SelectAxisSettingContent zoneId={zoneId} />}
|
content={<SelectAxisSettingContent zoneId={zoneId} />}
|
||||||
trigger="click"
|
trigger="click"
|
||||||
destroyTooltipOnHide
|
align="end"
|
||||||
performant
|
side="left">
|
||||||
placement="leftBottom">
|
<Button variant="ghost" prefix={<Sliders3 />} />
|
||||||
<Button type="text" icon={<AppMaterialIcons icon="tune" />} />
|
</Popover>
|
||||||
</AppPopover>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
SelectAxisSettingsButton.displayName = 'SelectAxisSettingsButton';
|
SelectAxisSettingsButton.displayName = 'SelectAxisSettingsButton';
|
||||||
|
|
|
@ -1,375 +0,0 @@
|
||||||
import type { IBusterMetricChartConfig, ColumnMetaData } from '@/api/asset_interfaces';
|
|
||||||
import { AppPopover, AppMaterialIcons } from '@/components/ui';
|
|
||||||
import type { IColumnLabelFormat, DerivedMetricTitle } from '@/components/ui/charts';
|
|
||||||
import { formatLabel, isNumericColumnType, isNumericColumnStyle } from '@/lib';
|
|
||||||
import { useMemoizedFn } from 'ahooks';
|
|
||||||
import { Input, Button, Divider, Switch, Select } from 'antd';
|
|
||||||
import { createStyles } from 'antd-style';
|
|
||||||
import last from 'lodash/last';
|
|
||||||
import React, { useMemo } from 'react';
|
|
||||||
import { LabelAndInput } from '../../Common';
|
|
||||||
import { AGGREGATE_OPTIONS } from './EditMetricType';
|
|
||||||
import { Text } from '@/components/ui';
|
|
||||||
import { createColumnFieldOptions } from './helpers';
|
|
||||||
|
|
||||||
const DEFAULT_METRIC_HEADER: Required<IBusterMetricChartConfig['metricHeader']> = {
|
|
||||||
columnId: '',
|
|
||||||
useValue: false,
|
|
||||||
aggregate: 'sum'
|
|
||||||
};
|
|
||||||
|
|
||||||
type NonNullableHeader =
|
|
||||||
| NonNullable<IBusterMetricChartConfig['metricHeader']>
|
|
||||||
| NonNullable<IBusterMetricChartConfig['metricSubHeader']>;
|
|
||||||
|
|
||||||
export const DerivedTitleInput: React.FC<{
|
|
||||||
type: 'header' | 'subHeader';
|
|
||||||
header: IBusterMetricChartConfig['metricHeader'] | IBusterMetricChartConfig['metricSubHeader'];
|
|
||||||
columnLabelFormat: IColumnLabelFormat;
|
|
||||||
columnLabelFormats: IBusterMetricChartConfig['columnLabelFormats'];
|
|
||||||
metricColumnId: IBusterMetricChartConfig['metricColumnId'];
|
|
||||||
columnMetadata: ColumnMetaData[];
|
|
||||||
onUpdateHeaderConfig: (newMetricHeader: IBusterMetricChartConfig['metricHeader']) => void;
|
|
||||||
}> = React.memo(
|
|
||||||
({
|
|
||||||
type,
|
|
||||||
header: headerProp,
|
|
||||||
columnLabelFormats,
|
|
||||||
columnLabelFormat,
|
|
||||||
metricColumnId,
|
|
||||||
columnMetadata,
|
|
||||||
onUpdateHeaderConfig
|
|
||||||
}) => {
|
|
||||||
const header = useMemo(() => {
|
|
||||||
const isStringHeader = typeof headerProp === 'string';
|
|
||||||
|
|
||||||
if (isStringHeader) return headerProp;
|
|
||||||
|
|
||||||
if (headerProp === null) {
|
|
||||||
return {
|
|
||||||
useValue: false,
|
|
||||||
columnId: metricColumnId,
|
|
||||||
aggregate: 'sum'
|
|
||||||
} as DerivedMetricTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
return headerProp;
|
|
||||||
}, [headerProp]);
|
|
||||||
|
|
||||||
const isStringHeader = typeof header === 'string';
|
|
||||||
|
|
||||||
const onUpdateHeader = useMemoizedFn(
|
|
||||||
(
|
|
||||||
newHeader:
|
|
||||||
| Partial<IBusterMetricChartConfig['metricHeader']>
|
|
||||||
| Partial<IBusterMetricChartConfig['metricSubHeader']>
|
|
||||||
) => {
|
|
||||||
if (typeof newHeader === 'string') {
|
|
||||||
return onUpdateHeaderConfig(newHeader);
|
|
||||||
} else {
|
|
||||||
if (typeof header === 'string') {
|
|
||||||
return onUpdateHeaderConfig({
|
|
||||||
...DEFAULT_METRIC_HEADER,
|
|
||||||
columnId: metricColumnId,
|
|
||||||
...newHeader
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return onUpdateHeaderConfig({ ...DEFAULT_METRIC_HEADER, ...header, ...newHeader });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const value = useMemo(() => {
|
|
||||||
if (isStringHeader) {
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
}, [isStringHeader, header]);
|
|
||||||
|
|
||||||
const placeholder = useMemo(() => {
|
|
||||||
if (isStringHeader) {
|
|
||||||
return 'Type or link a value';
|
|
||||||
}
|
|
||||||
const { useValue, columnId, aggregate } = header;
|
|
||||||
const columnLabelFormat = columnLabelFormats[columnId];
|
|
||||||
let label = formatLabel(columnId, columnLabelFormat, true);
|
|
||||||
if (useValue && aggregate) {
|
|
||||||
const aggregateLabel =
|
|
||||||
AGGREGATE_OPTIONS.find(({ value }) => value === aggregate)?.label || '';
|
|
||||||
label = `${label} (${aggregateLabel})`;
|
|
||||||
}
|
|
||||||
return label;
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
|
||||||
className="w-full"
|
|
||||||
value={value}
|
|
||||||
disabled={false}
|
|
||||||
placeholder={placeholder}
|
|
||||||
onChange={(e) => {
|
|
||||||
onUpdateHeader(e.target.value);
|
|
||||||
}}
|
|
||||||
suffix={
|
|
||||||
<DerivedTitleSuffix
|
|
||||||
columnLabelFormat={columnLabelFormat}
|
|
||||||
header={header}
|
|
||||||
columnMetadata={columnMetadata}
|
|
||||||
metricColumnId={metricColumnId}
|
|
||||||
columnLabelFormats={columnLabelFormats}
|
|
||||||
type={type}
|
|
||||||
onUpdateHeader={onUpdateHeader}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
DerivedTitleInput.displayName = 'DerivedTitleInput';
|
|
||||||
|
|
||||||
const DerivedTitleSuffix: React.FC<{
|
|
||||||
columnLabelFormat: IColumnLabelFormat;
|
|
||||||
columnLabelFormats: IBusterMetricChartConfig['columnLabelFormats'];
|
|
||||||
metricColumnId: IBusterMetricChartConfig['metricColumnId'];
|
|
||||||
columnMetadata: ColumnMetaData[];
|
|
||||||
header: NonNullableHeader;
|
|
||||||
onUpdateHeader: OnUpdateHeaderType;
|
|
||||||
type: 'header' | 'subHeader';
|
|
||||||
}> = ({
|
|
||||||
columnLabelFormat,
|
|
||||||
columnLabelFormats,
|
|
||||||
header,
|
|
||||||
onUpdateHeader,
|
|
||||||
metricColumnId,
|
|
||||||
columnMetadata,
|
|
||||||
type
|
|
||||||
}) => {
|
|
||||||
const isStringHeader = typeof header === 'string';
|
|
||||||
const buttonIcon = isStringHeader ? 'link_off' : 'link';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex" onClick={(e) => e.stopPropagation()}>
|
|
||||||
<AppPopover
|
|
||||||
placement="topLeft"
|
|
||||||
trigger="click"
|
|
||||||
destroyTooltipOnHide
|
|
||||||
content={
|
|
||||||
<DerivedTitleSuffixContent
|
|
||||||
columnLabelFormat={columnLabelFormat}
|
|
||||||
header={header}
|
|
||||||
metricColumnId={metricColumnId}
|
|
||||||
columnMetadata={columnMetadata}
|
|
||||||
columnLabelFormats={columnLabelFormats}
|
|
||||||
onUpdateHeader={onUpdateHeader}
|
|
||||||
type={type}
|
|
||||||
/>
|
|
||||||
}>
|
|
||||||
<Button className="h-[18px]!" type="text" icon={<AppMaterialIcons icon={buttonIcon} />} />
|
|
||||||
</AppPopover>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DerivedTitleSuffixContent: React.FC<{
|
|
||||||
type: 'header' | 'subHeader';
|
|
||||||
columnLabelFormat: IColumnLabelFormat;
|
|
||||||
metricColumnId: IBusterMetricChartConfig['metricColumnId'];
|
|
||||||
columnMetadata: ColumnMetaData[];
|
|
||||||
columnLabelFormats: IBusterMetricChartConfig['columnLabelFormats'];
|
|
||||||
header: NonNullableHeader;
|
|
||||||
onUpdateHeader: OnUpdateHeaderType;
|
|
||||||
}> = ({ type, columnLabelFormats, header, onUpdateHeader, columnMetadata }) => {
|
|
||||||
const isStringHeader = typeof header === 'string';
|
|
||||||
const headerColumnId = typeof header === 'object' ? header.columnId : '';
|
|
||||||
const headerUseValue = typeof header === 'object' && header.useValue;
|
|
||||||
|
|
||||||
const ComponentsLoop: {
|
|
||||||
key: string;
|
|
||||||
enabled: boolean;
|
|
||||||
Component: React.ReactNode;
|
|
||||||
}[] = [
|
|
||||||
{
|
|
||||||
key: 'link',
|
|
||||||
enabled: true,
|
|
||||||
Component: (
|
|
||||||
<ToggleHeaderLink isStringHeader={isStringHeader} onUpdateHeader={onUpdateHeader} />
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'columnId',
|
|
||||||
enabled: !isStringHeader,
|
|
||||||
Component: (
|
|
||||||
<DerivedTitleColumnId
|
|
||||||
headerColumnId={headerColumnId}
|
|
||||||
onUpdateHeader={onUpdateHeader}
|
|
||||||
columnMetadata={columnMetadata}
|
|
||||||
columnLabelFormats={columnLabelFormats}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'useValue',
|
|
||||||
enabled: !isStringHeader,
|
|
||||||
Component: <ToggleUseColumnValue onUpdateHeader={onUpdateHeader} useValue={headerUseValue} />
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
key: 'aggregate',
|
|
||||||
enabled: !isStringHeader && headerUseValue,
|
|
||||||
Component: (
|
|
||||||
<DerivedTitleAggregate
|
|
||||||
onUpdateHeader={onUpdateHeader}
|
|
||||||
aggregate={typeof header === 'object' && header.aggregate ? header.aggregate : 'sum'}
|
|
||||||
columnLabelFormat={columnLabelFormats[headerColumnId]}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex w-[285px] max-w-[285px] flex-col">
|
|
||||||
<DerivedTitleSuffixContentHeader type={type} />
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<div className="flex flex-col space-y-2 p-3">
|
|
||||||
{ComponentsLoop.map(({ enabled, key, Component }) => {
|
|
||||||
if (!enabled) return null;
|
|
||||||
return <React.Fragment key={key}>{Component}</React.Fragment>;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DerivedTitleSuffixContentHeader: React.FC<{
|
|
||||||
type: 'header' | 'subHeader';
|
|
||||||
}> = React.memo(
|
|
||||||
({ type }) => {
|
|
||||||
const title = type === 'header' ? 'Header settings' : 'Sub-header settings';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="p-3">
|
|
||||||
<Text>{title}</Text>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
() => true
|
|
||||||
);
|
|
||||||
DerivedTitleSuffixContentHeader.displayName = 'DerivedTitleSuffixContentHeader';
|
|
||||||
|
|
||||||
const ToggleUseColumnValue: React.FC<{
|
|
||||||
onUpdateHeader: OnUpdateHeaderType;
|
|
||||||
useValue: boolean;
|
|
||||||
}> = React.memo(({ onUpdateHeader, useValue }) => {
|
|
||||||
return (
|
|
||||||
<LabelAndInput label="Use column value">
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Switch checked={useValue} onChange={(v) => onUpdateHeader({ useValue: v })} />
|
|
||||||
</div>
|
|
||||||
</LabelAndInput>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
ToggleUseColumnValue.displayName = 'ToggleUseColumnValue';
|
|
||||||
|
|
||||||
const ToggleHeaderLink: React.FC<{
|
|
||||||
isStringHeader: boolean;
|
|
||||||
onUpdateHeader: OnUpdateHeaderType;
|
|
||||||
}> = React.memo(({ isStringHeader, onUpdateHeader }) => {
|
|
||||||
return (
|
|
||||||
<LabelAndInput label="Use column">
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Switch
|
|
||||||
checked={!isStringHeader}
|
|
||||||
onChange={(v) => {
|
|
||||||
onUpdateHeader(v ? {} : '');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</LabelAndInput>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
ToggleHeaderLink.displayName = 'ToggleHeaderLink';
|
|
||||||
|
|
||||||
const DerivedTitleColumnId: React.FC<{
|
|
||||||
headerColumnId: IBusterMetricChartConfig['metricColumnId'];
|
|
||||||
onUpdateHeader: OnUpdateHeaderType;
|
|
||||||
columnMetadata: ColumnMetaData[];
|
|
||||||
columnLabelFormats: IBusterMetricChartConfig['columnLabelFormats'];
|
|
||||||
}> = React.memo(({ headerColumnId, onUpdateHeader, columnMetadata, columnLabelFormats }) => {
|
|
||||||
const { styles } = useStyles();
|
|
||||||
|
|
||||||
const columnOptions = useMemo(() => {
|
|
||||||
return createColumnFieldOptions(columnMetadata, columnLabelFormats, styles.icon);
|
|
||||||
}, [columnMetadata, columnLabelFormats, styles.icon]);
|
|
||||||
|
|
||||||
const selectedColumn = useMemo(() => {
|
|
||||||
return columnOptions.find((option) => option.value === headerColumnId);
|
|
||||||
}, [headerColumnId, columnOptions]);
|
|
||||||
|
|
||||||
const onChangeSelect = useMemoizedFn((v: string) => {
|
|
||||||
const columnLabelFormat = columnLabelFormats[v];
|
|
||||||
const isNumberColumn = isNumericColumnType(columnLabelFormat?.columnType);
|
|
||||||
const isNumericStyle = isNumericColumnStyle(columnLabelFormat?.style);
|
|
||||||
const newHeader: Partial<IBusterMetricChartConfig['metricHeader']> = { columnId: v };
|
|
||||||
if (!isNumberColumn || !isNumericStyle) {
|
|
||||||
newHeader.aggregate = 'first';
|
|
||||||
}
|
|
||||||
onUpdateHeader(newHeader);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LabelAndInput label="Column ID">
|
|
||||||
<Select
|
|
||||||
className="w-full overflow-hidden"
|
|
||||||
options={columnOptions}
|
|
||||||
value={selectedColumn?.value}
|
|
||||||
onChange={onChangeSelect}
|
|
||||||
/>
|
|
||||||
</LabelAndInput>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
DerivedTitleColumnId.displayName = 'DerivedTitleColumnId';
|
|
||||||
|
|
||||||
const DerivedTitleAggregate: React.FC<{
|
|
||||||
onUpdateHeader: OnUpdateHeaderType;
|
|
||||||
aggregate: Required<DerivedMetricTitle>['aggregate'];
|
|
||||||
columnLabelFormat: IColumnLabelFormat;
|
|
||||||
}> = React.memo(({ onUpdateHeader, aggregate, columnLabelFormat }) => {
|
|
||||||
const isNumberColumn = isNumericColumnType(columnLabelFormat?.columnType);
|
|
||||||
const isNumericStyle = isNumericColumnStyle(columnLabelFormat?.style);
|
|
||||||
const disableOptions = !isNumberColumn || !isNumericStyle;
|
|
||||||
|
|
||||||
const selectedOption = useMemo(() => {
|
|
||||||
if (!disableOptions) {
|
|
||||||
return AGGREGATE_OPTIONS.find((option) => option.value === aggregate)?.value;
|
|
||||||
}
|
|
||||||
return last(AGGREGATE_OPTIONS)?.value;
|
|
||||||
}, [aggregate, disableOptions]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LabelAndInput label="Aggregate">
|
|
||||||
<Select
|
|
||||||
options={AGGREGATE_OPTIONS}
|
|
||||||
value={selectedOption}
|
|
||||||
disabled={disableOptions}
|
|
||||||
onChange={(v) =>
|
|
||||||
onUpdateHeader({ aggregate: v as Required<DerivedMetricTitle>['aggregate'] })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</LabelAndInput>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
DerivedTitleAggregate.displayName = 'DerivedTitleAggregate';
|
|
||||||
|
|
||||||
type OnUpdateHeaderType = (
|
|
||||||
header:
|
|
||||||
| Partial<IBusterMetricChartConfig['metricHeader']>
|
|
||||||
| Partial<IBusterMetricChartConfig['metricSubHeader']>
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
const useStyles = createStyles(({ token }) => ({
|
|
||||||
icon: token.colorIcon
|
|
||||||
}));
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { IBusterMetricChartConfig } from '@/api/asset_interfaces';
|
import { IBusterMetricChartConfig } from '@/api/asset_interfaces';
|
||||||
import { isNumericColumnStyle, isNumericColumnType } from '@/lib';
|
import { isNumericColumnStyle, isNumericColumnType } from '@/lib/messages';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { LabelAndInput } from '../../Common';
|
import { LabelAndInput } from '../../Common';
|
||||||
import { Button, Select } from 'antd';
|
import { Button, Select } from 'antd';
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { LabelAndInput } from '../../Common';
|
|
||||||
import type { ColumnMetaData, IBusterMetricChartConfig } from '@/api/asset_interfaces';
|
|
||||||
import { useMemoizedFn } from 'ahooks';
|
|
||||||
import { DerivedTitleInput } from './EditDerivedHeader';
|
|
||||||
|
|
||||||
export const EditMetricSubHeader: React.FC<{
|
|
||||||
metricSubHeader: IBusterMetricChartConfig['metricSubHeader'];
|
|
||||||
columnLabelFormats: IBusterMetricChartConfig['columnLabelFormats'];
|
|
||||||
metricColumnId: IBusterMetricChartConfig['metricColumnId'];
|
|
||||||
columnMetadata: ColumnMetaData[];
|
|
||||||
onUpdateChartConfig: (chartConfig: Partial<IBusterMetricChartConfig>) => void;
|
|
||||||
}> = React.memo(
|
|
||||||
({
|
|
||||||
metricSubHeader,
|
|
||||||
columnMetadata,
|
|
||||||
columnLabelFormats,
|
|
||||||
metricColumnId,
|
|
||||||
onUpdateChartConfig
|
|
||||||
}) => {
|
|
||||||
const columnLabelFormat = columnLabelFormats[metricColumnId];
|
|
||||||
|
|
||||||
const onUpdateMetricHeader = useMemoizedFn(
|
|
||||||
(newMetricSubHeader: IBusterMetricChartConfig['metricSubHeader']) => {
|
|
||||||
onUpdateChartConfig({ metricSubHeader: newMetricSubHeader });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LabelAndInput label={'Sub-header'}>
|
|
||||||
<DerivedTitleInput
|
|
||||||
type="subHeader"
|
|
||||||
header={metricSubHeader}
|
|
||||||
columnLabelFormat={columnLabelFormat}
|
|
||||||
metricColumnId={metricColumnId}
|
|
||||||
columnMetadata={columnMetadata}
|
|
||||||
columnLabelFormats={columnLabelFormats}
|
|
||||||
onUpdateHeaderConfig={onUpdateMetricHeader}
|
|
||||||
/>
|
|
||||||
</LabelAndInput>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
EditMetricSubHeader.displayName = 'EditMetricSubHeader';
|
|
|
@ -6,11 +6,9 @@ import last from 'lodash/last';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { isNumericColumnStyle, isNumericColumnType } from '@/lib';
|
import { isNumericColumnStyle, isNumericColumnType } from '@/lib';
|
||||||
import { ColumnLabelFormat } from '@/components/ui/charts';
|
import { ColumnLabelFormat } from '@/components/ui/charts';
|
||||||
|
import { SelectItem } from '@/components/ui/select';
|
||||||
|
|
||||||
export const AGGREGATE_OPTIONS: {
|
export const AGGREGATE_OPTIONS: SelectItem<IBusterMetricChartConfig['metricValueAggregate']>[] = [
|
||||||
label: string;
|
|
||||||
value: IBusterMetricChartConfig['metricValueAggregate'];
|
|
||||||
}[] = [
|
|
||||||
{ label: 'Sum', value: 'sum' },
|
{ label: 'Sum', value: 'sum' },
|
||||||
{ label: 'Average', value: 'average' },
|
{ label: 'Average', value: 'average' },
|
||||||
{ label: 'Median', value: 'median' },
|
{ label: 'Median', value: 'median' },
|
||||||
|
|
|
@ -1,24 +1,21 @@
|
||||||
import { ColumnMetaData, IBusterMetricChartConfig } from '@/api/asset_interfaces';
|
import { ColumnMetaData, IBusterMetricChartConfig } from '@/api/asset_interfaces';
|
||||||
import { formatLabel } from '@/lib';
|
import { formatLabel } from '@/lib';
|
||||||
import { ColumnTypeIcon } from '../SelectAxis/config';
|
import { ColumnTypeIcon } from '../SelectAxis/config';
|
||||||
|
import { type SelectItem } from '@/components/ui/select';
|
||||||
|
|
||||||
export const createColumnFieldOptions = (
|
export const createColumnFieldOptions = (
|
||||||
columnMetadata: ColumnMetaData[],
|
columnMetadata: ColumnMetaData[],
|
||||||
columnLabelFormats: IBusterMetricChartConfig['columnLabelFormats'],
|
columnLabelFormats: IBusterMetricChartConfig['columnLabelFormats'],
|
||||||
iconClass: string
|
iconClass: string
|
||||||
) => {
|
): SelectItem[] => {
|
||||||
return columnMetadata.map((column) => {
|
return columnMetadata.map<SelectItem>((column) => {
|
||||||
const labelFormat = columnLabelFormats[column.name];
|
const labelFormat = columnLabelFormats[column.name];
|
||||||
const formattedLabel = formatLabel(column.name, labelFormat, true);
|
const formattedLabel = formatLabel(column.name, labelFormat, true);
|
||||||
const Icon = ColumnTypeIcon[labelFormat.style];
|
const Icon = ColumnTypeIcon[labelFormat.style];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: (
|
icon: Icon.icon,
|
||||||
<div className="flex w-full items-center space-x-1.5 overflow-hidden">
|
label: formattedLabel,
|
||||||
<div className={`${iconClass} flex`}>{Icon.icon}</div>
|
|
||||||
<span className="truncate">{formattedLabel}</span>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
value: column.name
|
value: column.name
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue