-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: introduce poc of adhoc visualization on query results [#641]
- Loading branch information
Showing
35 changed files
with
2,175 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
...y-tracker/QueryResultsVisualization/components/ChartErrorBoundary/ChartErrorBoundary.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
87 changes: 87 additions & 0 deletions
87
...ry-tracker/QueryResultsVisualization/components/ChartErrorBoundary/ChartErrorBoundary.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
...eryResultsVisualization/components/EmptyPlaceholdersMessage/EmptyPlaceholdersMessage.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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%; | ||
} |
9 changes: 9 additions & 0 deletions
9
...ueryResultsVisualization/components/EmptyPlaceholdersMessage/EmptyPlaceholdersMessage.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; | ||
} |
44 changes: 44 additions & 0 deletions
44
packages/ui/src/ui/pages/query-tracker/QueryResultsVisualization/containers/Chart/Chart.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
185 changes: 185 additions & 0 deletions
185
.../pages/query-tracker/QueryResultsVisualization/containers/ChartSettings/ChartSettings.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
18 changes: 18 additions & 0 deletions
18
...s/query-tracker/QueryResultsVisualization/containers/ChartValidation/ChartValidation.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
Oops, something went wrong.