Skip to content

Commit

Permalink
feat: introduce poc of adhoc visualization on query results [#641]
Browse files Browse the repository at this point in the history
  • Loading branch information
vrozaev committed Jul 5, 2024
1 parent cc19d6b commit 6dd9896
Show file tree
Hide file tree
Showing 35 changed files with 2,175 additions and 1 deletion.
3 changes: 2 additions & 1 deletion packages/ui/src/ui/UIFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {UISettingsMonitoring} from '../shared/ui-settings';
import {DefaultSubjectCard, type SubjectCardProps} from './components/SubjectLink/SubjectLink';
import type {QueryItem} from './pages/query-tracker/module/api';
import type {DropdownMenuItem} from '@gravity-ui/uikit';
import {CUSTOM_QUERY_REQULT_TAB} from './pages/query-tracker/QueryResultsVisualization';

type HeaderItemOrPage =
| {
Expand Down Expand Up @@ -653,7 +654,7 @@ const uiFactory: UIFactory = {
},

getCustomQueryResultTab() {
return undefined;
return CUSTOM_QUERY_REQULT_TAB;
},

getExternalSettings() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.chart-error-boundary {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;

text-align: center;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import * as React from 'react';
import {useEffect, useState} from 'react';
import {ChartKitError} from '@gravity-ui/chartkit';
import {Button} from '@gravity-ui/uikit';

import './ChartErrorBoundary.scss';

const renderError = ({
error,
refreshClick,
}: {
error: ChartKitError | Error;
refreshClick(): void;
}) => {
return (
<div className="chart-error-boundary">
<div>
<p>
{error?.message}
<br />
</p>
<Button view="action" onClick={refreshClick}>
Try again
</Button>
</div>
</div>
);
};

interface ChartErrorBoundaryProps {
deps: Record<string, unknown>;
children: (args: {handleError({error}: {error: ChartKitError}): void}) => React.ReactElement;
}

export const ChartErrorHandler = ({children, deps}: ChartErrorBoundaryProps) => {
const [error, setError] = useState<ChartKitError>();

const handleError = ({error}: {error: ChartKitError}) => {
setError(error);
};

const handleRefreshClick = () => {
setError(undefined);
};

useEffect(() => {
setError(undefined);
}, [deps]);

if (error) {
return renderError({error, refreshClick: handleRefreshClick});
}

return children({handleError});
};

export class ChartErrorBoundary extends React.Component<
React.PropsWithChildren<Readonly<{}>>,
{error?: Error | ChartKitError}
> {
static getDerivedStateFromError(error: Error) {
return {error};
}

constructor(props: React.PropsWithChildren<Readonly<{}>>) {
super(props);

this.state = {error: undefined};
}

componentDidCatch() {}

handleRefreshClick = () => {
this.setState({error: undefined});
};

render() {
if (this.state.error) {
return renderError({
error: this.state.error,
refreshClick: this.handleRefreshClick,
});
}

return this.props.children;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.empty-placeholders-message {
align-items: center;
display: flex;
height: 100%;
justify-content: center;
text-align: center;
width: 100%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import block from 'bem-cn-lite';
import './EmptyPlaceholdersMessage.scss';

const b = block('empty-placeholders-message');

export function EmptyPlaceholdersMessage() {
return <div className={b()}>Add fields in X and Y placeholders</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, {useMemo} from 'react';
import ChartKit from '../../../../../components/YagrChartKit/YagrChartKit';
import {settings} from '@gravity-ui/chartkit';
import type {ChartKitRef} from '@gravity-ui/chartkit';
import {prepareWidgetData} from '../../preparers/prepareWidgetData';
import {useSelector} from 'react-redux';
import {
selectIsPlaceholdersMissSomeFields,
selectQueryResultVisualization,
} from '../../store/selectors';
import type {QueryResult} from '../../preparers/types';
import {ChartErrorHandler} from '../../components/ChartErrorBoundary/ChartErrorBoundary';
import {EmptyPlaceholdersMessage} from '../../components/EmptyPlaceholdersMessage/EmptyPlaceholdersMessage';
import {D3Plugin} from '@gravity-ui/chartkit/d3';

settings.set({plugins: [...settings.get('plugins'), D3Plugin]});

type LineBasicProps = {
result: QueryResult;
};

export const BaseChart = React.forwardRef<ChartKitRef | undefined, LineBasicProps>(
function BaseChartComponent({result}, ref) {
const visualization = useSelector(selectQueryResultVisualization);
const isPlaceholdersMissSomeFields = useSelector(selectIsPlaceholdersMissSomeFields);
const widgetData = useMemo(() => {
return prepareWidgetData({result, visualization});
}, [result, visualization]);

if (isPlaceholdersMissSomeFields) {
return <EmptyPlaceholdersMessage />;
}

return (
<ChartErrorHandler deps={widgetData}>
{({handleError}) => (
<ChartKit type="d3" data={widgetData} ref={ref} onError={handleError} />
)}
</ChartErrorHandler>
);
},
);

export const Chart = React.memo(BaseChart);
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import React, {useCallback, useState} from 'react';
import {Gear} from '@gravity-ui/icons';
import {Icon} from '@gravity-ui/uikit';
import Button from '../../../../../components/Button/Button';
import {
DialogField,
DialogTabField,
FormApi,
YTDFDialog,
} from '../../../../../components/Dialog/Dialog';
import type {ChartSettings} from '../../types';
import {useSelector} from 'react-redux';
import {selectQueryResultChartSettings} from '../../store/selectors';
import {useThunkDispatch} from '../../../../../store/thunkDispatch';

type FormValues = ChartSettings;

function pixelIntervalValidator(value: string) {
return !value || /^\d+$/.test(value) ? undefined : 'Should be a number';
}

const xAxisTab: DialogTabField<DialogField<FormValues>> = {
type: 'tab',
name: 'xAxis',
title: 'X axis',
fields: [
{
name: 'legend',
caption: 'Legend',
type: 'radio',
extras: {
options: [
{
value: 'on',
label: 'On',
},
{
value: 'off',
label: 'Off',
},
],
},
},
{
name: 'labels',
caption: 'Labels',
type: 'radio',
extras: {
options: [
{
value: 'on',
label: 'On',
},
{
value: 'off',
label: 'Off',
},
],
},
},
{
caption: 'Title',
name: 'title',
type: 'text',
},
{
name: 'grid',
caption: 'Grid',
type: 'radio',
extras: {
options: [
{
value: 'on',
label: 'On',
},
{
value: 'off',
label: 'Off',
},
],
},
},
{
caption: 'Grid step, px',
name: 'pixelInterval',
type: 'text',
validator: pixelIntervalValidator,
},
],
};

const yAxisTab: DialogTabField<DialogField<FormValues>> = {
type: 'tab',
name: 'yAxis',
title: 'Y axis',
fields: [
{
name: 'labels',
caption: 'Labels',
type: 'radio',
extras: {
options: [
{
value: 'on',
label: 'On',
},
{
value: 'off',
label: 'Off',
},
],
},
},
{
caption: 'Title',
name: 'title',
type: 'text',
},
{
name: 'grid',
caption: 'Grid',
type: 'radio',
extras: {
options: [
{
value: 'on',
label: 'On',
},
{
value: 'off',
label: 'Off',
},
],
},
},
{
caption: 'Grid step, px',
name: 'pixelInterval',
type: 'text',
validator: pixelIntervalValidator,
},
],
};

const fields: DialogTabField<DialogField<FormValues>>[] = [xAxisTab, yAxisTab];

export function ChartSettingsComponent() {
const [visible, setVisilbility] = useState(false);
const chartSettings = useSelector(selectQueryResultChartSettings);
const dispatch = useThunkDispatch();

const handleOnOpen = useCallback(() => {
setVisilbility(true);
}, [setVisilbility]);

const handleOnClose = useCallback(() => {
setVisilbility(false);
}, [setVisilbility]);

const handleOnAdd = useCallback((form: FormApi<FormValues, Partial<FormValues>>) => {
const data = form.getState().values;

dispatch({
type: 'set-chart-settings',
data,
});

return Promise.resolve();
}, []);

return (
<span>
<Button onClick={handleOnOpen}>
<Icon data={Gear} size={16} />
</Button>
<YTDFDialog<FormValues>
visible={visible}
initialValues={chartSettings}
onClose={handleOnClose}
onAdd={handleOnAdd}
fields={fields}
/>
</span>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.chart-validation {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;

&__alert {
padding: 12px;
font-size: 10px;
}

&__icon {
&_invalid {
color: var(--g-color-line-warning);
}
}
}
Loading

0 comments on commit 6dd9896

Please sign in to comment.