mirror of https://github.com/buster-so/buster.git
make a chart card
This commit is contained in:
parent
679295cff8
commit
af5e599dab
|
@ -1,6 +1,6 @@
|
|||
import type { IBusterMetricChartConfig } from './requireInterfaces';
|
||||
import type { ColumnSettings } from '../../../components/charts/interfaces/columnInterfaces';
|
||||
import { ChartType, ViewType } from '../../../components/charts/interfaces/enum';
|
||||
import { ChartType } from '../../../components/charts/interfaces/enum';
|
||||
import { DEFAULT_CHART_THEME } from '../../../components/charts/configColors';
|
||||
import type { ColumnLabelFormat } from '../../../components/charts/interfaces/columnLabelInterfaces';
|
||||
import type { ColumnMetaData } from './interfaces';
|
||||
|
@ -8,7 +8,6 @@ import type { ColumnMetaData } from './interfaces';
|
|||
export const DEFAULT_CHART_CONFIG: IBusterMetricChartConfig = {
|
||||
colors: DEFAULT_CHART_THEME,
|
||||
selectedChartType: ChartType.Table,
|
||||
selectedView: ViewType.Table,
|
||||
yAxisShowAxisLabel: true,
|
||||
yAxisShowAxisTitle: true,
|
||||
yAxisAxisTitle: null,
|
||||
|
|
|
@ -40,7 +40,7 @@ export const MetricChartEvaluation: React.FC<{
|
|||
className={cx(
|
||||
styles.container,
|
||||
colorClass,
|
||||
'flex items-center gap-1 rounded-lg px-2 py-1'
|
||||
'flex items-center gap-1 rounded-lg px-2 py-1 hover:shadow-sm'
|
||||
)}>
|
||||
{icon}
|
||||
{text}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { createStyles } from 'antd-style';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { MetricViewChartContent } from './MetricViewChartContent';
|
||||
import { MetricViewChartHeader } from './MetricViewChartHeader';
|
||||
import { useBusterMetricIndividual, useBusterMetricsContextSelector } from '@/context/Metrics';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { inputHasText } from '@/utils/text';
|
||||
import { MetricChartEvaluation } from './MetricChartEvaluation';
|
||||
import { ChartType } from '@/components/charts/interfaces/enum';
|
||||
|
||||
export const MetricViewChart: React.FC<{
|
||||
metricId: string;
|
||||
|
@ -14,6 +15,16 @@ export const MetricViewChart: React.FC<{
|
|||
const onUpdateMetric = useBusterMetricsContextSelector((x) => x.onUpdateMetric);
|
||||
const { metric, metricData } = useBusterMetricIndividual({ metricId });
|
||||
const { title, description, time_frame, evaluation_score, evaluation_summary } = metric;
|
||||
const isTable = metric.chart_config.selectedChartType === ChartType.Table;
|
||||
|
||||
const loadingData = !metricData.fetched;
|
||||
const errorData = !!metricData.error;
|
||||
|
||||
const cardClass = useMemo(() => {
|
||||
if (loadingData || errorData) return 'h-full max-h-[600px]';
|
||||
if (isTable) return '';
|
||||
return 'h-full max-h-[600px]';
|
||||
}, [isTable, loadingData, errorData]);
|
||||
|
||||
const onSetTitle = useMemoizedFn((title: string) => {
|
||||
if (inputHasText(title)) {
|
||||
|
@ -24,8 +35,13 @@ export const MetricViewChart: React.FC<{
|
|||
});
|
||||
|
||||
return (
|
||||
<div className={cx(styles.container, 'm-5 flex h-full flex-col justify-between')}>
|
||||
<div className={cx(styles.chartCard, 'flex flex-col')}>
|
||||
<div
|
||||
className={cx(
|
||||
styles.container,
|
||||
'm-5 flex h-full flex-col justify-between space-y-3.5',
|
||||
'overflow-hidden'
|
||||
)}>
|
||||
<div className={cx(styles.chartCard, cardClass, 'flex flex-col')}>
|
||||
<MetricViewChartHeader
|
||||
className="px-4"
|
||||
title={title}
|
||||
|
@ -35,7 +51,6 @@ export const MetricViewChart: React.FC<{
|
|||
/>
|
||||
<div className={cx(styles.divider)} />
|
||||
<MetricViewChartContent
|
||||
className="px-4"
|
||||
chartConfig={metric.chart_config}
|
||||
metricData={metricData.data}
|
||||
dataMetadata={metricData.data_metadata}
|
||||
|
@ -60,6 +75,7 @@ const useStyles = createStyles(({ css, token }) => ({
|
|||
border-radius: ${token.borderRadiusLG}px;
|
||||
border: 0.5px solid ${token.colorBorder};
|
||||
background-color: ${token.colorBgContainer};
|
||||
overflow: hidden;
|
||||
`,
|
||||
divider: css`
|
||||
border-bottom: 0.5px solid ${token.colorBorder};
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import type { DataMetadata } from '@/api/asset_interfaces';
|
||||
import { BusterChart, ChartType } from '@/components/charts';
|
||||
import type { BusterMetricData, IBusterMetric } from '@/context/Metrics';
|
||||
import { createStyles } from 'antd-style';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
interface MetricViewChartContentProps {
|
||||
className?: string;
|
||||
|
@ -13,9 +14,27 @@ interface MetricViewChartContentProps {
|
|||
}
|
||||
|
||||
export const MetricViewChartContent: React.FC<MetricViewChartContentProps> = React.memo(
|
||||
({ className, chartConfig, metricData, dataMetadata, fetchedData, errorMessage }) => {
|
||||
({ className, chartConfig, metricData = null, dataMetadata, fetchedData, errorMessage }) => {
|
||||
const { styles, cx } = useStyles();
|
||||
return <div className={cx('flex flex-col py-4', className)}>MetricViewChartContent</div>;
|
||||
const columnMetadata = dataMetadata?.column_metadata;
|
||||
const isTable = chartConfig?.selectedChartType === ChartType.Table;
|
||||
|
||||
const cardClassName = useMemo(() => {
|
||||
if (isTable || !fetchedData) return '';
|
||||
return 'p-4';
|
||||
}, [isTable, fetchedData]);
|
||||
|
||||
return (
|
||||
<div className={cx('flex h-full flex-col overflow-hidden', cardClassName, className)}>
|
||||
<BusterChart
|
||||
loading={!fetchedData}
|
||||
error={errorMessage || undefined}
|
||||
data={metricData}
|
||||
columnMetadata={columnMetadata}
|
||||
{...chartConfig}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -6,8 +6,7 @@ import {
|
|||
BusterChartProps,
|
||||
ChartType,
|
||||
IColumnLabelFormat,
|
||||
Trendline,
|
||||
ViewType
|
||||
Trendline
|
||||
} from '@/components/charts';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { Checkbox, Select } from 'antd';
|
||||
|
@ -150,7 +149,6 @@ export default function ChartJS() {
|
|||
<BusterChart
|
||||
data={chartData}
|
||||
selectedChartType={chartType as ChartType.Pie}
|
||||
selectedView={ViewType.Table}
|
||||
loading={false}
|
||||
barAndLineAxis={barAndLineAxis}
|
||||
pieChartAxis={pieConfig.axis}
|
||||
|
|
|
@ -6,8 +6,7 @@ import {
|
|||
BusterChartProps,
|
||||
ChartType,
|
||||
IColumnLabelFormat,
|
||||
Trendline,
|
||||
ViewType
|
||||
Trendline
|
||||
} from '@/components/charts';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { Checkbox, Select } from 'antd';
|
||||
|
@ -191,7 +190,6 @@ export default function ChartJS() {
|
|||
<BusterChart
|
||||
data={chartData}
|
||||
selectedChartType={chartType as ChartType.Pie}
|
||||
selectedView={ViewType.Chart}
|
||||
loading={false}
|
||||
barAndLineAxis={barAndLineAxis}
|
||||
pieChartAxis={pieConfig.axis}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { BusterChartProps, ChartEncodes, ChartType, ViewType } from './interfaces';
|
||||
import { BusterChartProps, ChartEncodes, ChartType } from './interfaces';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { doesChartHaveValidAxis } from './helpers';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
|
@ -44,12 +44,11 @@ export const BusterChart: React.FC<BusterChartProps> = React.memo(
|
|||
onInitialAnimationEnd,
|
||||
editable,
|
||||
selectedChartType,
|
||||
selectedView,
|
||||
columnLabelFormats = DEFAULT_CHART_CONFIG.columnLabelFormats,
|
||||
renderType = 'chartjs',
|
||||
...props
|
||||
}) => {
|
||||
const isTable = selectedView === ViewType.Table || selectedChartType === ChartType.Table;
|
||||
const isTable = selectedChartType === ChartType.Table;
|
||||
const showNoData = !loading && (isEmpty(data) || data === null);
|
||||
|
||||
const selectedAxis: ChartEncodes | undefined = useMemo(() => {
|
||||
|
@ -73,7 +72,7 @@ export const BusterChart: React.FC<BusterChartProps> = React.memo(
|
|||
selectedAxis,
|
||||
isTable
|
||||
});
|
||||
}, [selectedChartType, selectedView, isTable, selectedAxis]);
|
||||
}, [selectedChartType, isTable, selectedAxis]);
|
||||
|
||||
const onChartMounted = useMemoizedFn((chart?: any) => {
|
||||
onChartMountedProp?.(chart);
|
||||
|
@ -152,12 +151,7 @@ export const BusterChart: React.FC<BusterChartProps> = React.memo(
|
|||
|
||||
return (
|
||||
<BusterChartErrorWrapper>
|
||||
<BusterChartWrapper
|
||||
id={id}
|
||||
className={className}
|
||||
bordered={bordered}
|
||||
loading={loading}
|
||||
useTableSizing={isTable && !loading && !showNoData && hasValidAxis}>
|
||||
<BusterChartWrapper id={id} className={className} bordered={bordered} loading={loading}>
|
||||
{SwitchComponent()}
|
||||
</BusterChartWrapper>
|
||||
</BusterChartErrorWrapper>
|
||||
|
|
|
@ -25,9 +25,7 @@ export class BusterChartErrorWrapper extends Component<Props, State> {
|
|||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div
|
||||
className="flex h-full w-full items-center justify-center rounded border p-5"
|
||||
role="alert">
|
||||
<div className="flex h-full w-full items-center justify-center" role="alert">
|
||||
<Alert
|
||||
message="Something went wrong rendering the chart. This is likely an error on our end. Please contact Buster support."
|
||||
type="error"
|
||||
|
|
|
@ -9,8 +9,7 @@ export const BusterChartWrapper = React.memo<{
|
|||
className: string | undefined;
|
||||
bordered: boolean;
|
||||
loading: boolean;
|
||||
useTableSizing: boolean;
|
||||
}>(({ children, id, className, bordered, loading, useTableSizing }) => {
|
||||
}>(({ children, id, className, bordered, loading }) => {
|
||||
const { styles, cx } = useStyles();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const size = useSize(ref);
|
||||
|
@ -22,12 +21,9 @@ export const BusterChartWrapper = React.memo<{
|
|||
ref={ref}
|
||||
id={id}
|
||||
className={cx(
|
||||
styles.card,
|
||||
className,
|
||||
'flex w-full flex-col',
|
||||
'flex h-full w-full flex-col',
|
||||
'transition duration-300',
|
||||
useTableSizing ? 'h-full' : 'h-full max-h-[600px] p-[18px]',
|
||||
bordered ? styles.cardBorder : '',
|
||||
loading ? '!bg-transparent' : undefined,
|
||||
'overflow-hidden'
|
||||
)}>
|
||||
|
@ -40,13 +36,5 @@ export const BusterChartWrapper = React.memo<{
|
|||
BusterChartWrapper.displayName = 'BusterChartWrapper';
|
||||
|
||||
const useStyles = createStyles(({ token }) => {
|
||||
return {
|
||||
card: {
|
||||
borderRadius: token.borderRadius,
|
||||
background: token.colorBgContainer
|
||||
},
|
||||
cardBorder: {
|
||||
border: `0.5px solid ${token.colorBorder}`
|
||||
}
|
||||
};
|
||||
return {};
|
||||
});
|
||||
|
|
|
@ -1,18 +1,5 @@
|
|||
import { AppMaterialIcons } from '@/components/icons';
|
||||
import { ViewType, ChartType } from './interfaces';
|
||||
|
||||
export const viewTypeOptions = [
|
||||
{
|
||||
label: 'Chart',
|
||||
value: ViewType.Chart,
|
||||
icon: <AppMaterialIcons icon="monitoring" />
|
||||
},
|
||||
{
|
||||
label: 'Table',
|
||||
value: ViewType.Table,
|
||||
icon: <AppMaterialIcons icon="table" />
|
||||
}
|
||||
];
|
||||
import { ChartType } from './interfaces';
|
||||
|
||||
export const chartOptions = [
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { BarAndLineAxis, ChartEncodes } from './interfaces';
|
||||
import { ChartType, ViewType } from './interfaces/enum';
|
||||
import { ChartEncodes } from './interfaces';
|
||||
import { ChartType } from './interfaces/enum';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
const defaultAxisCheck = (selectedAxis: ChartEncodes) => {
|
||||
|
|
|
@ -42,7 +42,6 @@ export interface BusterChartRenderComponentProps
|
|||
| 'id'
|
||||
| 'bordered'
|
||||
| 'editable'
|
||||
| 'selectedView'
|
||||
| 'groupByMethod'
|
||||
| 'error'
|
||||
| 'pieChartAxis'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ChartType, ViewType } from './enum';
|
||||
import { ChartType } from './enum';
|
||||
import type { BarAndLineAxis, ComboChartAxis, PieChartAxis, ScatterAxis } from './axisInterfaces';
|
||||
import type {
|
||||
CategoryAxisStyleConfig,
|
||||
|
@ -13,7 +13,6 @@ import type { IColumnLabelFormat } from './columnLabelInterfaces';
|
|||
|
||||
export type BusterChartConfigProps = {
|
||||
selectedChartType: ChartType;
|
||||
selectedView: ViewType;
|
||||
|
||||
//COLUMN SETTINGS
|
||||
columnSettings?: Record<string, ColumnSettings>; //OPTIONAL because the defaults will be determined by the UI
|
||||
|
|
|
@ -14,8 +14,3 @@ export type ChartTypePlottable =
|
|||
| ChartType.Scatter
|
||||
| ChartType.Pie
|
||||
| ChartType.Combo;
|
||||
|
||||
export enum ViewType {
|
||||
Chart = 'chart',
|
||||
Table = 'table'
|
||||
}
|
||||
|
|
|
@ -98,12 +98,14 @@ const useMetricData = () => {
|
|||
fetching: true
|
||||
});
|
||||
|
||||
//TODO: remove mock data
|
||||
// _setMetricData(metricId, { ...MOCK_DATA, fetched: true });
|
||||
onSetMetricData({
|
||||
...MOCK_DATA,
|
||||
metricId
|
||||
});
|
||||
setTimeout(() => {
|
||||
//TODO: remove mock data
|
||||
// _setMetricData(metricId, { ...MOCK_DATA, fetched: true });
|
||||
onSetMetricData({
|
||||
...MOCK_DATA,
|
||||
metricId
|
||||
});
|
||||
}, 1800);
|
||||
|
||||
return await busterSocket.emitAndOnce({
|
||||
emitEvent: {
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { BusterMetricData } from '../Metrics';
|
|||
import { faker } from '@faker-js/faker';
|
||||
|
||||
const mockData = (): Record<string, string | number | null>[] => {
|
||||
return Array.from({ length: 10 }, (x, index) => ({
|
||||
return Array.from({ length: 100 }, (x, index) => ({
|
||||
sales: faker.number.int({ min: index * 50, max: (index + 1) * 100 }),
|
||||
date: faker.date.past({ years: index + 1 }).toISOString(),
|
||||
product: faker.commerce.productName()
|
||||
|
@ -41,12 +41,19 @@ const dataMetadata: DataMetadata = {
|
|||
row_count: 10
|
||||
};
|
||||
|
||||
const data = mockData();
|
||||
data.push({
|
||||
sales: 1,
|
||||
date: '2024-01-01',
|
||||
product: 'NATE RULEZ'
|
||||
});
|
||||
|
||||
export const MOCK_DATA: Required<BusterMetricData> = {
|
||||
fetched: true,
|
||||
fetching: false,
|
||||
error: null,
|
||||
fetchedAt: Date.now(),
|
||||
data: mockData(),
|
||||
data: data,
|
||||
data_metadata: dataMetadata,
|
||||
dataFromRerun: null,
|
||||
code: `SELECT
|
||||
|
|
|
@ -505,10 +505,12 @@ export const useBusterMetrics = () => {
|
|||
}
|
||||
|
||||
//TODO: remove this
|
||||
_onGetMetricState({
|
||||
...MOCK_METRIC,
|
||||
id: metricId
|
||||
});
|
||||
setTimeout(() => {
|
||||
_onGetMetricState({
|
||||
...MOCK_METRIC,
|
||||
id: metricId
|
||||
});
|
||||
}, 500);
|
||||
|
||||
return await busterSocket.emitAndOnce({
|
||||
emitEvent: {
|
||||
|
|
|
@ -1,6 +1,55 @@
|
|||
import { DEFAULT_CHART_CONFIG, ShareRole, VerificationStatus } from '@/api/asset_interfaces';
|
||||
import {
|
||||
DataMetadata,
|
||||
DEFAULT_CHART_CONFIG,
|
||||
IBusterMetricChartConfig,
|
||||
ShareRole,
|
||||
VerificationStatus
|
||||
} from '@/api/asset_interfaces';
|
||||
import { IBusterMetric } from './interfaces';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { ChartType } from '@/components/charts';
|
||||
|
||||
const MOCK_CHART_CONFIG: IBusterMetricChartConfig = {
|
||||
...DEFAULT_CHART_CONFIG,
|
||||
selectedChartType: ChartType.Bar,
|
||||
barAndLineAxis: {
|
||||
x: ['date'],
|
||||
y: ['sales'],
|
||||
category: []
|
||||
// category: ['product']
|
||||
}
|
||||
};
|
||||
|
||||
const dataMetadata: DataMetadata = {
|
||||
column_count: 3,
|
||||
column_metadata: [
|
||||
{
|
||||
name: 'sales',
|
||||
min_value: 0,
|
||||
max_value: 1000,
|
||||
unique_values: 10,
|
||||
simple_type: 'number',
|
||||
type: 'integer'
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
min_value: '2024-01-01',
|
||||
max_value: '2024-01-31',
|
||||
unique_values: 31,
|
||||
simple_type: 'date',
|
||||
type: 'date'
|
||||
},
|
||||
{
|
||||
name: 'product',
|
||||
min_value: 'Product A',
|
||||
max_value: 'Product Z',
|
||||
unique_values: 26,
|
||||
simple_type: 'text',
|
||||
type: 'text'
|
||||
}
|
||||
],
|
||||
row_count: 10
|
||||
};
|
||||
|
||||
export const MOCK_METRIC: IBusterMetric = {
|
||||
id: '123',
|
||||
|
@ -8,14 +57,14 @@ export const MOCK_METRIC: IBusterMetric = {
|
|||
description: 'This is a mock metric',
|
||||
time_frame: '1d',
|
||||
type: 'metric',
|
||||
chart_config: DEFAULT_CHART_CONFIG,
|
||||
chart_config: MOCK_CHART_CONFIG,
|
||||
fetched: true,
|
||||
fetching: false,
|
||||
fetchedAt: 0,
|
||||
dataset_id: '123',
|
||||
dataset_name: 'Mock Dataset',
|
||||
error: null,
|
||||
data_metadata: null,
|
||||
data_metadata: dataMetadata,
|
||||
status: VerificationStatus.notRequested,
|
||||
evaluation_score: 'Moderate',
|
||||
evaluation_summary: faker.lorem.sentence(33),
|
||||
|
|
|
@ -25,7 +25,6 @@ describe('createDefaultChartConfig', () => {
|
|||
'#E83562'
|
||||
],
|
||||
selectedChartType: 'table',
|
||||
selectedView: 'table',
|
||||
yAxisShowAxisLabel: true,
|
||||
yAxisShowAxisTitle: true,
|
||||
yAxisAxisTitle: null,
|
||||
|
|
|
@ -158,7 +158,6 @@ const ChartPreviewImage = ({
|
|||
messageData: BusterMetricData;
|
||||
}) => {
|
||||
const data = messageData?.data || [];
|
||||
const selectedChartView = message?.chart_config?.selectedView;
|
||||
const chartOptions = message?.chart_config;
|
||||
|
||||
const chart = (
|
||||
|
@ -187,10 +186,6 @@ export const PreviewImageReactComponent: React.FC<{
|
|||
}> = ({ isDark = false, message, messageData }) => {
|
||||
const data = messageData?.data || [];
|
||||
|
||||
if (!message?.chart_config?.selectedView) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
const BusterLogo = (
|
||||
<div
|
||||
className="w-[165px] rounded p-1"
|
||||
|
|
Loading…
Reference in New Issue