make a chart card

This commit is contained in:
Nate Kelley 2025-02-03 20:32:45 -07:00
parent 679295cff8
commit af5e599dab
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
20 changed files with 133 additions and 89 deletions

View File

@ -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,

View File

@ -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}

View File

@ -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};

View File

@ -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>
);
}
);

View File

@ -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}

View File

@ -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}

View File

@ -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>

View File

@ -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"

View File

@ -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 {};
});

View File

@ -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 = [
{

View File

@ -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) => {

View File

@ -42,7 +42,6 @@ export interface BusterChartRenderComponentProps
| 'id'
| 'bordered'
| 'editable'
| 'selectedView'
| 'groupByMethod'
| 'error'
| 'pieChartAxis'

View File

@ -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

View File

@ -14,8 +14,3 @@ export type ChartTypePlottable =
| ChartType.Scatter
| ChartType.Pie
| ChartType.Combo;
export enum ViewType {
Chart = 'chart',
Table = 'table'
}

View File

@ -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: {

View File

@ -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

View File

@ -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: {

View File

@ -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),

View File

@ -25,7 +25,6 @@ describe('createDefaultChartConfig', () => {
'#E83562'
],
selectedChartType: 'table',
selectedView: 'table',
yAxisShowAxisLabel: true,
yAxisShowAxisTitle: true,
yAxisAxisTitle: null,

View File

@ -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"