mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 01:15:52 +08:00
refactor: dashboard views (#3175)
* refactor: dashboard views * fix: switch component by another render approach * fix: build * feat: add table panel styles * fix: render query data in table * feat: add separated columns for group by * fix: add return for creation label for formula * fix: table columns Remove label repeating, remove aggregate column if values don’t exist * fix: render columns with empty value * fix: pr comments for panel swither * fix: change key to dataIndex * chore: removed gridgraph component --------- Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
parent
59deac01bd
commit
e6fa1383f3
@ -343,7 +343,7 @@ type CustomChartOptions = ChartOptions & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
interface GraphProps {
|
export interface GraphProps {
|
||||||
animate?: boolean;
|
animate?: boolean;
|
||||||
type: ChartType;
|
type: ChartType;
|
||||||
data: Chart['data'];
|
data: Chart['data'];
|
||||||
|
14
frontend/src/constants/panelTypes.ts
Normal file
14
frontend/src/constants/panelTypes.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import Graph from 'components/Graph';
|
||||||
|
import GridTableComponent from 'container/GridTableComponent';
|
||||||
|
import GridValueComponent from 'container/GridValueComponent';
|
||||||
|
|
||||||
|
import { PANEL_TYPES } from './queryBuilder';
|
||||||
|
|
||||||
|
export const PANEL_TYPES_COMPONENT_MAP = {
|
||||||
|
[PANEL_TYPES.TIME_SERIES]: Graph,
|
||||||
|
[PANEL_TYPES.VALUE]: GridValueComponent,
|
||||||
|
[PANEL_TYPES.TABLE]: GridTableComponent,
|
||||||
|
[PANEL_TYPES.TRACE]: null,
|
||||||
|
[PANEL_TYPES.LIST]: null,
|
||||||
|
[PANEL_TYPES.EMPTY_WIDGET]: null,
|
||||||
|
} as const;
|
@ -1,5 +1,4 @@
|
|||||||
// ** Helpers
|
// ** Helpers
|
||||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
|
||||||
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
|
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
|
||||||
import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderItemName';
|
import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderItemName';
|
||||||
import {
|
import {
|
||||||
@ -24,7 +23,6 @@ import {
|
|||||||
LogsAggregatorOperator,
|
LogsAggregatorOperator,
|
||||||
MetricAggregateOperator,
|
MetricAggregateOperator,
|
||||||
NumberOperators,
|
NumberOperators,
|
||||||
PanelTypeKeys,
|
|
||||||
QueryAdditionalFilter,
|
QueryAdditionalFilter,
|
||||||
QueryBuilderData,
|
QueryBuilderData,
|
||||||
ReduceOperators,
|
ReduceOperators,
|
||||||
@ -124,7 +122,7 @@ export const initialFilters: TagFilter = {
|
|||||||
op: 'AND',
|
op: 'AND',
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialQueryBuilderFormValues: IBuilderQuery = {
|
export const initialQueryBuilderFormValues: IBuilderQuery = {
|
||||||
dataSource: DataSource.METRICS,
|
dataSource: DataSource.METRICS,
|
||||||
queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
|
queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
|
||||||
aggregateOperator: MetricAggregateOperator.NOOP,
|
aggregateOperator: MetricAggregateOperator.NOOP,
|
||||||
@ -238,14 +236,15 @@ export const operatorsByTypes: Record<LocalDataType, string[]> = {
|
|||||||
bool: Object.values(BoolOperators),
|
bool: Object.values(BoolOperators),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PANEL_TYPES: Record<PanelTypeKeys, GRAPH_TYPES> = {
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
TIME_SERIES: 'graph',
|
export enum PANEL_TYPES {
|
||||||
VALUE: 'value',
|
TIME_SERIES = 'graph',
|
||||||
TABLE: 'table',
|
VALUE = 'value',
|
||||||
LIST: 'list',
|
TABLE = 'table',
|
||||||
TRACE: 'trace',
|
LIST = 'list',
|
||||||
EMPTY_WIDGET: 'EMPTY_WIDGET',
|
TRACE = 'trace',
|
||||||
};
|
EMPTY_WIDGET = 'EMPTY_WIDGET',
|
||||||
|
}
|
||||||
|
|
||||||
export type IQueryBuilderState = 'search';
|
export type IQueryBuilderState = 'search';
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { InfoCircleOutlined } from '@ant-design/icons';
|
|||||||
import { StaticLineProps } from 'components/Graph';
|
import { StaticLineProps } from 'components/Graph';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import GridGraphComponent from 'container/GridGraphComponent';
|
import GridPanelSwitch from 'container/GridPanelSwitch';
|
||||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||||
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
||||||
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
||||||
@ -113,13 +113,15 @@ function ChartPreview({
|
|||||||
<Spinner size="large" tip="Loading..." height="70vh" />
|
<Spinner size="large" tip="Loading..." height="70vh" />
|
||||||
)}
|
)}
|
||||||
{chartDataSet && !queryResponse.isError && (
|
{chartDataSet && !queryResponse.isError && (
|
||||||
<GridGraphComponent
|
<GridPanelSwitch
|
||||||
|
panelType={graphType}
|
||||||
title={name}
|
title={name}
|
||||||
data={chartDataSet}
|
data={chartDataSet}
|
||||||
isStacked
|
isStacked
|
||||||
GRAPH_TYPES={graphType || PANEL_TYPES.TIME_SERIES}
|
|
||||||
name={name || 'Chart Preview'}
|
name={name || 'Chart Preview'}
|
||||||
staticLine={staticLine}
|
staticLine={staticLine}
|
||||||
|
panelData={queryResponse.data?.payload.data.newResult.data.result || []}
|
||||||
|
query={query || initialQueriesMap.metrics}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
|
@ -3,6 +3,7 @@ import { Col, FormInstance, Modal, Tooltip, Typography } from 'antd';
|
|||||||
import saveAlertApi from 'api/alerts/save';
|
import saveAlertApi from 'api/alerts/save';
|
||||||
import testAlertApi from 'api/alerts/testAlert';
|
import testAlertApi from 'api/alerts/testAlert';
|
||||||
import { FeatureKeys } from 'constants/features';
|
import { FeatureKeys } from 'constants/features';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag';
|
import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag';
|
||||||
import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag';
|
import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag';
|
||||||
@ -58,6 +59,7 @@ function FormAlertRules({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
currentQuery,
|
currentQuery,
|
||||||
|
panelType,
|
||||||
stagedQuery,
|
stagedQuery,
|
||||||
handleRunQuery,
|
handleRunQuery,
|
||||||
redirectWithQueryBuilderData,
|
redirectWithQueryBuilderData,
|
||||||
@ -351,7 +353,12 @@ function FormAlertRules({
|
|||||||
|
|
||||||
const renderQBChartPreview = (): JSX.Element => (
|
const renderQBChartPreview = (): JSX.Element => (
|
||||||
<ChartPreview
|
<ChartPreview
|
||||||
headline={<PlotTag queryType={currentQuery.queryType} />}
|
headline={
|
||||||
|
<PlotTag
|
||||||
|
queryType={currentQuery.queryType}
|
||||||
|
panelType={panelType || PANEL_TYPES.TIME_SERIES}
|
||||||
|
/>
|
||||||
|
}
|
||||||
name=""
|
name=""
|
||||||
threshold={alertDef.condition?.target}
|
threshold={alertDef.condition?.target}
|
||||||
query={stagedQuery}
|
query={stagedQuery}
|
||||||
@ -361,7 +368,12 @@ function FormAlertRules({
|
|||||||
|
|
||||||
const renderPromChartPreview = (): JSX.Element => (
|
const renderPromChartPreview = (): JSX.Element => (
|
||||||
<ChartPreview
|
<ChartPreview
|
||||||
headline={<PlotTag queryType={currentQuery.queryType} />}
|
headline={
|
||||||
|
<PlotTag
|
||||||
|
queryType={currentQuery.queryType}
|
||||||
|
panelType={panelType || PANEL_TYPES.TIME_SERIES}
|
||||||
|
/>
|
||||||
|
}
|
||||||
name="Chart Preview"
|
name="Chart Preview"
|
||||||
threshold={alertDef.condition?.target}
|
threshold={alertDef.condition?.target}
|
||||||
query={stagedQuery}
|
query={stagedQuery}
|
||||||
@ -370,7 +382,12 @@ function FormAlertRules({
|
|||||||
|
|
||||||
const renderChQueryChartPreview = (): JSX.Element => (
|
const renderChQueryChartPreview = (): JSX.Element => (
|
||||||
<ChartPreview
|
<ChartPreview
|
||||||
headline={<PlotTag queryType={currentQuery.queryType} />}
|
headline={
|
||||||
|
<PlotTag
|
||||||
|
queryType={currentQuery.queryType}
|
||||||
|
panelType={panelType || PANEL_TYPES.TIME_SERIES}
|
||||||
|
/>
|
||||||
|
}
|
||||||
name="Chart Preview"
|
name="Chart Preview"
|
||||||
threshold={alertDef.condition?.target}
|
threshold={alertDef.condition?.target}
|
||||||
query={stagedQuery}
|
query={stagedQuery}
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
import { Typography } from 'antd';
|
|
||||||
import { ChartData } from 'chart.js';
|
|
||||||
import Graph, { GraphOnClickHandler, StaticLineProps } from 'components/Graph';
|
|
||||||
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
|
||||||
import ValueGraph from 'components/ValueGraph';
|
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
|
||||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
|
||||||
import history from 'lib/history';
|
|
||||||
|
|
||||||
import { TitleContainer, ValueContainer } from './styles';
|
|
||||||
|
|
||||||
function GridGraphComponent({
|
|
||||||
GRAPH_TYPES,
|
|
||||||
data,
|
|
||||||
title,
|
|
||||||
opacity,
|
|
||||||
isStacked,
|
|
||||||
onClickHandler,
|
|
||||||
name,
|
|
||||||
yAxisUnit,
|
|
||||||
staticLine,
|
|
||||||
onDragSelect,
|
|
||||||
}: GridGraphComponentProps): JSX.Element | null {
|
|
||||||
const location = history.location.pathname;
|
|
||||||
|
|
||||||
const isDashboardPage = location.split('/').length === 3;
|
|
||||||
|
|
||||||
if (GRAPH_TYPES === PANEL_TYPES.TIME_SERIES) {
|
|
||||||
return (
|
|
||||||
<Graph
|
|
||||||
{...{
|
|
||||||
data,
|
|
||||||
title,
|
|
||||||
type: 'line',
|
|
||||||
isStacked,
|
|
||||||
opacity,
|
|
||||||
xAxisType: 'time',
|
|
||||||
onClickHandler,
|
|
||||||
name,
|
|
||||||
yAxisUnit,
|
|
||||||
staticLine,
|
|
||||||
onDragSelect,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GRAPH_TYPES === PANEL_TYPES.VALUE) {
|
|
||||||
const value = (((data.datasets[0] || []).data || [])[0] || 0) as number;
|
|
||||||
|
|
||||||
if (data.datasets.length === 0) {
|
|
||||||
return (
|
|
||||||
<ValueContainer isDashboardPage={isDashboardPage}>
|
|
||||||
<Typography>No Data</Typography>
|
|
||||||
</ValueContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TitleContainer isDashboardPage={isDashboardPage}>
|
|
||||||
<Typography>{title}</Typography>
|
|
||||||
</TitleContainer>
|
|
||||||
<ValueContainer isDashboardPage={isDashboardPage}>
|
|
||||||
<ValueGraph
|
|
||||||
value={
|
|
||||||
yAxisUnit
|
|
||||||
? getYAxisFormattedValue(String(value), yAxisUnit)
|
|
||||||
: value.toString()
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ValueContainer>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GridGraphComponentProps {
|
|
||||||
GRAPH_TYPES: GRAPH_TYPES;
|
|
||||||
data: ChartData;
|
|
||||||
title?: string;
|
|
||||||
opacity?: string;
|
|
||||||
isStacked?: boolean;
|
|
||||||
onClickHandler?: GraphOnClickHandler;
|
|
||||||
name: string;
|
|
||||||
yAxisUnit?: string;
|
|
||||||
staticLine?: StaticLineProps;
|
|
||||||
onDragSelect?: (start: number, end: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
GridGraphComponent.defaultProps = {
|
|
||||||
title: undefined,
|
|
||||||
opacity: undefined,
|
|
||||||
isStacked: undefined,
|
|
||||||
onClickHandler: undefined,
|
|
||||||
yAxisUnit: undefined,
|
|
||||||
staticLine: undefined,
|
|
||||||
onDragSelect: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GridGraphComponent;
|
|
@ -2,7 +2,7 @@ import { Button } from 'antd';
|
|||||||
import { GraphOnClickHandler } from 'components/Graph';
|
import { GraphOnClickHandler } from 'components/Graph';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import TimePreference from 'components/TimePreferenceDropDown';
|
import TimePreference from 'components/TimePreferenceDropDown';
|
||||||
import GridGraphComponent from 'container/GridGraphComponent';
|
import GridPanelSwitch from 'container/GridPanelSwitch';
|
||||||
import {
|
import {
|
||||||
timeItems,
|
timeItems,
|
||||||
timePreferance,
|
timePreferance,
|
||||||
@ -85,7 +85,7 @@ function FullView({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{fullViewOptions && (
|
{fullViewOptions && (
|
||||||
<TimeContainer>
|
<TimeContainer $panelType={widget.panelTypes}>
|
||||||
<TimePreference
|
<TimePreference
|
||||||
selectedTime={selectedTime}
|
selectedTime={selectedTime}
|
||||||
setSelectedTime={setSelectedTime}
|
setSelectedTime={setSelectedTime}
|
||||||
@ -101,8 +101,8 @@ function FullView({
|
|||||||
</TimeContainer>
|
</TimeContainer>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<GridGraphComponent
|
<GridPanelSwitch
|
||||||
GRAPH_TYPES={widget.panelTypes}
|
panelType={widget.panelTypes}
|
||||||
data={chartDataSet}
|
data={chartDataSet}
|
||||||
isStacked={widget.isStacked}
|
isStacked={widget.isStacked}
|
||||||
opacity={widget.opacity}
|
opacity={widget.opacity}
|
||||||
@ -111,6 +111,8 @@ function FullView({
|
|||||||
name={name}
|
name={name}
|
||||||
yAxisUnit={yAxisUnit}
|
yAxisUnit={yAxisUnit}
|
||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
|
panelData={response.data?.payload.data.newResult.data.result || []}
|
||||||
|
query={widget.query}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
import styled from 'styled-components';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
|
||||||
|
import styled, { css, FlattenSimpleInterpolation } from 'styled-components';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
$panelType: ITEMS;
|
||||||
|
}
|
||||||
|
|
||||||
export const NotFoundContainer = styled.div`
|
export const NotFoundContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -7,7 +13,13 @@ export const NotFoundContainer = styled.div`
|
|||||||
min-height: 55vh;
|
min-height: 55vh;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TimeContainer = styled.div`
|
export const TimeContainer = styled.div<Props>`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
${({ $panelType }): FlattenSimpleInterpolation =>
|
||||||
|
$panelType === PANEL_TYPES.TABLE
|
||||||
|
? css`
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
`
|
||||||
|
: css``}
|
||||||
`;
|
`;
|
||||||
|
@ -2,8 +2,8 @@ import { Typography } from 'antd';
|
|||||||
import { ChartData } from 'chart.js';
|
import { ChartData } from 'chart.js';
|
||||||
import { GraphOnClickHandler } from 'components/Graph';
|
import { GraphOnClickHandler } from 'components/Graph';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import GridGraphComponent from 'container/GridGraphComponent';
|
|
||||||
import { UpdateDashboard } from 'container/GridGraphLayout/utils';
|
import { UpdateDashboard } from 'container/GridGraphLayout/utils';
|
||||||
|
import GridPanelSwitch from 'container/GridPanelSwitch';
|
||||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||||
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
@ -272,8 +272,8 @@ function GridCardGraph({
|
|||||||
allowEdit={allowEdit}
|
allowEdit={allowEdit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<GridGraphComponent
|
<GridPanelSwitch
|
||||||
GRAPH_TYPES={widget?.panelTypes}
|
panelType={widget?.panelTypes}
|
||||||
data={prevChartDataSetRef}
|
data={prevChartDataSetRef}
|
||||||
isStacked={widget?.isStacked}
|
isStacked={widget?.isStacked}
|
||||||
opacity={widget?.opacity}
|
opacity={widget?.opacity}
|
||||||
@ -281,6 +281,8 @@ function GridCardGraph({
|
|||||||
name={name}
|
name={name}
|
||||||
yAxisUnit={yAxisUnit}
|
yAxisUnit={yAxisUnit}
|
||||||
onClickHandler={onClickHandler}
|
onClickHandler={onClickHandler}
|
||||||
|
panelData={[]}
|
||||||
|
query={widget.query}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -308,8 +310,8 @@ function GridCardGraph({
|
|||||||
allowEdit={allowEdit}
|
allowEdit={allowEdit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<GridGraphComponent
|
<GridPanelSwitch
|
||||||
GRAPH_TYPES={widget.panelTypes}
|
panelType={widget.panelTypes}
|
||||||
data={prevChartDataSetRef}
|
data={prevChartDataSetRef}
|
||||||
isStacked={widget.isStacked}
|
isStacked={widget.isStacked}
|
||||||
opacity={widget.opacity}
|
opacity={widget.opacity}
|
||||||
@ -317,6 +319,8 @@ function GridCardGraph({
|
|||||||
name={name}
|
name={name}
|
||||||
yAxisUnit={yAxisUnit}
|
yAxisUnit={yAxisUnit}
|
||||||
onClickHandler={onClickHandler}
|
onClickHandler={onClickHandler}
|
||||||
|
panelData={[]}
|
||||||
|
query={widget.query}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@ -363,8 +367,8 @@ function GridCardGraph({
|
|||||||
{!isEmptyLayout && getModals()}
|
{!isEmptyLayout && getModals()}
|
||||||
|
|
||||||
{!isEmpty(widget) && !!queryResponse.data?.payload && (
|
{!isEmpty(widget) && !!queryResponse.data?.payload && (
|
||||||
<GridGraphComponent
|
<GridPanelSwitch
|
||||||
GRAPH_TYPES={widget.panelTypes}
|
panelType={widget.panelTypes}
|
||||||
data={chartData}
|
data={chartData}
|
||||||
isStacked={widget.isStacked}
|
isStacked={widget.isStacked}
|
||||||
opacity={widget.opacity}
|
opacity={widget.opacity}
|
||||||
@ -373,6 +377,8 @@ function GridCardGraph({
|
|||||||
yAxisUnit={yAxisUnit}
|
yAxisUnit={yAxisUnit}
|
||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
onClickHandler={onClickHandler}
|
onClickHandler={onClickHandler}
|
||||||
|
panelData={queryResponse.data?.payload.data.newResult.data.result || []}
|
||||||
|
query={widget.query}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { PlusOutlined, SaveFilled } from '@ant-design/icons';
|
import { PlusOutlined, SaveFilled } from '@ant-design/icons';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { Dispatch, SetStateAction } from 'react';
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
@ -83,7 +84,7 @@ function GraphLayout({
|
|||||||
key={currentWidget?.id || 'empty'} // don't change this key
|
key={currentWidget?.id || 'empty'} // don't change this key
|
||||||
data-grid={rest}
|
data-grid={rest}
|
||||||
>
|
>
|
||||||
<Card>
|
<Card $panelType={currentWidget?.panelTypes || PANEL_TYPES.TIME_SERIES}>
|
||||||
<Component setLayout={setLayout} />
|
<Component setLayout={setLayout} />
|
||||||
</Card>
|
</Card>
|
||||||
</CardContainer>
|
</CardContainer>
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
import { Button as ButtonComponent, Card as CardComponent, Space } from 'antd';
|
import { Button as ButtonComponent, Card as CardComponent, Space } from 'antd';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { StyledCSS } from 'container/GantChart/Trace/styles';
|
import { StyledCSS } from 'container/GantChart/Trace/styles';
|
||||||
|
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
|
||||||
import RGL, { WidthProvider } from 'react-grid-layout';
|
import RGL, { WidthProvider } from 'react-grid-layout';
|
||||||
import styled, { css } from 'styled-components';
|
import styled, { css, FlattenSimpleInterpolation } from 'styled-components';
|
||||||
|
|
||||||
const ReactGridLayoutComponent = WidthProvider(RGL);
|
const ReactGridLayoutComponent = WidthProvider(RGL);
|
||||||
|
|
||||||
export const Card = styled(CardComponent)`
|
interface CardProps {
|
||||||
|
$panelType: ITEMS;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Card = styled(CardComponent)<CardProps>`
|
||||||
&&& {
|
&&& {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@ -13,6 +19,12 @@ export const Card = styled(CardComponent)`
|
|||||||
.ant-card-body {
|
.ant-card-body {
|
||||||
height: 95%;
|
height: 95%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
${({ $panelType }): FlattenSimpleInterpolation =>
|
||||||
|
$panelType === PANEL_TYPES.TABLE
|
||||||
|
? css`
|
||||||
|
padding-top: 1.8rem;
|
||||||
|
`
|
||||||
|
: css``}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -21,6 +33,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const CardContainer = styled.div<Props>`
|
export const CardContainer = styled.div<Props>`
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
:hover {
|
:hover {
|
||||||
.react-resizable-handle {
|
.react-resizable-handle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -45,6 +59,7 @@ export const CardContainer = styled.div<Props>`
|
|||||||
`;
|
`;
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ReactGridLayout = styled(ReactGridLayoutComponent)`
|
export const ReactGridLayout = styled(ReactGridLayoutComponent)`
|
||||||
|
73
frontend/src/container/GridPanelSwitch/index.tsx
Normal file
73
frontend/src/container/GridPanelSwitch/index.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { PANEL_TYPES_COMPONENT_MAP } from 'constants/panelTypes';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { GRID_TABLE_CONFIG } from 'container/GridTableComponent/config';
|
||||||
|
import { FC, memo, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { GridPanelSwitchProps, PropsTypePropsMap } from './types';
|
||||||
|
|
||||||
|
function GridPanelSwitch({
|
||||||
|
panelType,
|
||||||
|
data,
|
||||||
|
title,
|
||||||
|
isStacked,
|
||||||
|
onClickHandler,
|
||||||
|
name,
|
||||||
|
yAxisUnit,
|
||||||
|
staticLine,
|
||||||
|
onDragSelect,
|
||||||
|
panelData,
|
||||||
|
query,
|
||||||
|
}: GridPanelSwitchProps): JSX.Element | null {
|
||||||
|
const currentProps: PropsTypePropsMap = useMemo(() => {
|
||||||
|
const result: PropsTypePropsMap = {
|
||||||
|
[PANEL_TYPES.TIME_SERIES]: {
|
||||||
|
type: 'line',
|
||||||
|
data,
|
||||||
|
title,
|
||||||
|
isStacked,
|
||||||
|
onClickHandler,
|
||||||
|
name,
|
||||||
|
yAxisUnit,
|
||||||
|
staticLine,
|
||||||
|
onDragSelect,
|
||||||
|
},
|
||||||
|
[PANEL_TYPES.VALUE]: {
|
||||||
|
title,
|
||||||
|
data,
|
||||||
|
yAxisUnit,
|
||||||
|
},
|
||||||
|
[PANEL_TYPES.TABLE]: { ...GRID_TABLE_CONFIG, data: panelData, query },
|
||||||
|
[PANEL_TYPES.LIST]: null,
|
||||||
|
[PANEL_TYPES.TRACE]: null,
|
||||||
|
[PANEL_TYPES.EMPTY_WIDGET]: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, [
|
||||||
|
data,
|
||||||
|
isStacked,
|
||||||
|
name,
|
||||||
|
onClickHandler,
|
||||||
|
onDragSelect,
|
||||||
|
staticLine,
|
||||||
|
title,
|
||||||
|
yAxisUnit,
|
||||||
|
panelData,
|
||||||
|
query,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const Component = PANEL_TYPES_COMPONENT_MAP[panelType] as FC<
|
||||||
|
PropsTypePropsMap[typeof panelType]
|
||||||
|
>;
|
||||||
|
const componentProps = useMemo(() => currentProps[panelType], [
|
||||||
|
panelType,
|
||||||
|
currentProps,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!Component || !componentProps) return null;
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return <Component {...componentProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(GridPanelSwitch);
|
37
frontend/src/container/GridPanelSwitch/types.ts
Normal file
37
frontend/src/container/GridPanelSwitch/types.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { ChartData } from 'chart.js';
|
||||||
|
import {
|
||||||
|
GraphOnClickHandler,
|
||||||
|
GraphProps,
|
||||||
|
StaticLineProps,
|
||||||
|
} from 'components/Graph';
|
||||||
|
import { GridTableComponentProps } from 'container/GridTableComponent/types';
|
||||||
|
import { GridValueComponentProps } from 'container/GridValueComponent/types';
|
||||||
|
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
|
||||||
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { QueryDataV3 } from 'types/api/widgets/getQuery';
|
||||||
|
|
||||||
|
import { PANEL_TYPES } from '../../constants/queryBuilder';
|
||||||
|
|
||||||
|
export type GridPanelSwitchProps = {
|
||||||
|
panelType: ITEMS;
|
||||||
|
data: ChartData;
|
||||||
|
title?: string;
|
||||||
|
opacity?: string;
|
||||||
|
isStacked?: boolean;
|
||||||
|
onClickHandler?: GraphOnClickHandler;
|
||||||
|
name: string;
|
||||||
|
yAxisUnit?: string;
|
||||||
|
staticLine?: StaticLineProps;
|
||||||
|
onDragSelect?: (start: number, end: number) => void;
|
||||||
|
panelData: QueryDataV3[];
|
||||||
|
query: Query;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PropsTypePropsMap = {
|
||||||
|
[PANEL_TYPES.TIME_SERIES]: GraphProps;
|
||||||
|
[PANEL_TYPES.VALUE]: GridValueComponentProps;
|
||||||
|
[PANEL_TYPES.TABLE]: GridTableComponentProps;
|
||||||
|
[PANEL_TYPES.TRACE]: null;
|
||||||
|
[PANEL_TYPES.LIST]: null;
|
||||||
|
[PANEL_TYPES.EMPTY_WIDGET]: null;
|
||||||
|
};
|
9
frontend/src/container/GridTableComponent/config.ts
Normal file
9
frontend/src/container/GridTableComponent/config.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { TableProps } from 'antd';
|
||||||
|
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||||
|
|
||||||
|
export const GRID_TABLE_CONFIG: Omit<
|
||||||
|
TableProps<RowData>,
|
||||||
|
'columns' | 'dataSource'
|
||||||
|
> = {
|
||||||
|
size: 'small',
|
||||||
|
};
|
25
frontend/src/container/GridTableComponent/index.tsx
Normal file
25
frontend/src/container/GridTableComponent/index.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { QueryTable } from 'container/QueryTable';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
import { WrapperStyled } from './styles';
|
||||||
|
import { GridTableComponentProps } from './types';
|
||||||
|
|
||||||
|
function GridTableComponent({
|
||||||
|
data,
|
||||||
|
query,
|
||||||
|
...props
|
||||||
|
}: GridTableComponentProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<WrapperStyled>
|
||||||
|
<QueryTable
|
||||||
|
query={query}
|
||||||
|
queryTableData={data}
|
||||||
|
loading={false}
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</WrapperStyled>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(GridTableComponent);
|
23
frontend/src/container/GridTableComponent/styles.ts
Normal file
23
frontend/src/container/GridTableComponent/styles.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const WrapperStyled = styled.div`
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
& .ant-table-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
& .ant-spin-nested-loading {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .ant-spin-container {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
& .ant-table {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
`;
|
10
frontend/src/container/GridTableComponent/types.ts
Normal file
10
frontend/src/container/GridTableComponent/types.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { TableProps } from 'antd';
|
||||||
|
import { LogsExplorerTableProps } from 'container/LogsExplorerTable/LogsExplorerTable.interfaces';
|
||||||
|
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||||
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
export type GridTableComponentProps = { query: Query } & Pick<
|
||||||
|
LogsExplorerTableProps,
|
||||||
|
'data'
|
||||||
|
> &
|
||||||
|
Omit<TableProps<RowData>, 'columns' | 'dataSource'>;
|
3
frontend/src/container/GridValueComponent/config.ts
Normal file
3
frontend/src/container/GridValueComponent/config.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { GridValueComponentProps } from './types';
|
||||||
|
|
||||||
|
export const GridValueConfig: Pick<GridValueComponentProps, 'title'> = {};
|
47
frontend/src/container/GridValueComponent/index.tsx
Normal file
47
frontend/src/container/GridValueComponent/index.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { Typography } from 'antd';
|
||||||
|
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||||
|
import ValueGraph from 'components/ValueGraph';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { TitleContainer, ValueContainer } from './styles';
|
||||||
|
import { GridValueComponentProps } from './types';
|
||||||
|
|
||||||
|
function GridValueComponent({
|
||||||
|
data,
|
||||||
|
title,
|
||||||
|
yAxisUnit,
|
||||||
|
}: GridValueComponentProps): JSX.Element {
|
||||||
|
const value = (((data.datasets[0] || []).data || [])[0] || 0) as number;
|
||||||
|
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const isDashboardPage = location.pathname.split('/').length === 3;
|
||||||
|
|
||||||
|
if (data.datasets.length === 0) {
|
||||||
|
return (
|
||||||
|
<ValueContainer isDashboardPage={isDashboardPage}>
|
||||||
|
<Typography>No Data</Typography>
|
||||||
|
</ValueContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TitleContainer isDashboardPage={isDashboardPage}>
|
||||||
|
<Typography>{title}</Typography>
|
||||||
|
</TitleContainer>
|
||||||
|
<ValueContainer isDashboardPage={isDashboardPage}>
|
||||||
|
<ValueGraph
|
||||||
|
value={
|
||||||
|
yAxisUnit
|
||||||
|
? getYAxisFormattedValue(String(value), yAxisUnit)
|
||||||
|
: value.toString()
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ValueContainer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(GridValueComponent);
|
7
frontend/src/container/GridValueComponent/types.ts
Normal file
7
frontend/src/container/GridValueComponent/types.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { ChartData } from 'chart.js';
|
||||||
|
|
||||||
|
export type GridValueComponentProps = {
|
||||||
|
data: ChartData;
|
||||||
|
title?: string;
|
||||||
|
yAxisUnit?: string;
|
||||||
|
};
|
@ -14,6 +14,7 @@ const Items: ItemsProps[] = [
|
|||||||
Icon: ValueIcon,
|
Icon: ValueIcon,
|
||||||
display: 'Value',
|
display: 'Value',
|
||||||
},
|
},
|
||||||
|
{ name: PANEL_TYPES.TABLE, Icon: TimeSeries, display: 'Table' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export type ITEMS =
|
export type ITEMS =
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
|
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
|
|
||||||
import QueryTypeTag from '../QueryTypeTag';
|
import QueryTypeTag from '../QueryTypeTag';
|
||||||
|
import { PlotTagWrapperStyled } from './styles';
|
||||||
|
|
||||||
interface IPlotTagProps {
|
interface IPlotTagProps {
|
||||||
queryType: EQueryType;
|
queryType: EQueryType;
|
||||||
|
panelType: ITEMS;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PlotTag({ queryType }: IPlotTagProps): JSX.Element | null {
|
function PlotTag({ queryType, panelType }: IPlotTagProps): JSX.Element | null {
|
||||||
if (queryType === undefined) {
|
if (queryType === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginLeft: '2rem', position: 'absolute', top: '1rem' }}>
|
<PlotTagWrapperStyled $panelType={panelType}>
|
||||||
Plotted using <QueryTypeTag queryType={queryType} />
|
Plotted using <QueryTypeTag queryType={queryType} />
|
||||||
</div>
|
</PlotTagWrapperStyled>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { Card, Typography } from 'antd';
|
import { Card, Typography } from 'antd';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import GridGraphComponent from 'container/GridGraphComponent';
|
import GridPanelSwitch from 'container/GridPanelSwitch';
|
||||||
import { WidgetGraphProps } from 'container/NewWidget/types';
|
import { WidgetGraphProps } from 'container/NewWidget/types';
|
||||||
import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange';
|
import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange';
|
||||||
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import getChartData from 'lib/getChartData';
|
import getChartData from 'lib/getChartData';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
@ -16,6 +17,7 @@ function WidgetGraph({
|
|||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
}: WidgetGraphProps): JSX.Element {
|
}: WidgetGraphProps): JSX.Element {
|
||||||
|
const { stagedQuery } = useQueryBuilder();
|
||||||
const { dashboards } = useSelector<AppState, DashboardReducer>(
|
const { dashboards } = useSelector<AppState, DashboardReducer>(
|
||||||
(state) => state.dashboards,
|
(state) => state.dashboards,
|
||||||
);
|
);
|
||||||
@ -39,7 +41,7 @@ function WidgetGraph({
|
|||||||
return <Card>Invalid widget</Card>;
|
return <Card>Invalid widget</Card>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, opacity, isStacked } = selectedWidget;
|
const { title, opacity, isStacked, query } = selectedWidget;
|
||||||
|
|
||||||
if (getWidgetQueryRange.error) {
|
if (getWidgetQueryRange.error) {
|
||||||
return (
|
return (
|
||||||
@ -66,14 +68,18 @@ function WidgetGraph({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridGraphComponent
|
<GridPanelSwitch
|
||||||
title={title}
|
title={title}
|
||||||
isStacked={isStacked}
|
isStacked={isStacked}
|
||||||
opacity={opacity}
|
opacity={opacity}
|
||||||
data={chartDataSet}
|
data={chartDataSet}
|
||||||
GRAPH_TYPES={selectedGraph}
|
panelType={selectedGraph}
|
||||||
name={widgetId || 'legend_widget'}
|
name={widgetId || 'legend_widget'}
|
||||||
yAxisUnit={yAxisUnit}
|
yAxisUnit={yAxisUnit}
|
||||||
|
panelData={
|
||||||
|
getWidgetQueryRange.data?.payload.data.newResult.data.result || []
|
||||||
|
}
|
||||||
|
query={stagedQuery || query}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -41,12 +41,12 @@ function WidgetGraph({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (selectedWidget === undefined) {
|
if (selectedWidget === undefined) {
|
||||||
return <Card>Invalid widget</Card>;
|
return <Card $panelType={selectedGraph}>Invalid widget</Card>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container $panelType={selectedGraph}>
|
||||||
<PlotTag queryType={currentQuery.queryType} />
|
<PlotTag queryType={currentQuery.queryType} panelType={selectedGraph} />
|
||||||
{getWidgetQueryRange.error && (
|
{getWidgetQueryRange.error && (
|
||||||
<AlertIconContainer color="red" title={getWidgetQueryRange.error.message}>
|
<AlertIconContainer color="red" title={getWidgetQueryRange.error.message}>
|
||||||
<InfoCircleOutlined />
|
<InfoCircleOutlined />
|
||||||
|
@ -1,15 +1,24 @@
|
|||||||
import { Card, Tooltip } from 'antd';
|
import { Card, Tooltip } from 'antd';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export const Container = styled(Card)`
|
interface Props {
|
||||||
|
$panelType: ITEMS;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Container = styled(Card)<Props>`
|
||||||
&&& {
|
&&& {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-body {
|
.ant-card-body {
|
||||||
padding: 1.5rem 0;
|
padding: ${({ $panelType }): string =>
|
||||||
|
$panelType === PANEL_TYPES.TABLE ? '0 0' : '1.5rem 0'};
|
||||||
height: 57vh;
|
height: 57vh;
|
||||||
/* padding-bottom: 2rem; */
|
overflow: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -23,5 +32,14 @@ export const NotFoundContainer = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 55vh;
|
min-height: 47vh;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const PlotTagWrapperStyled = styled.div<Props>`
|
||||||
|
margin-left: 2rem;
|
||||||
|
margin-top: ${({ $panelType }): string =>
|
||||||
|
$panelType === PANEL_TYPES.TABLE ? '1rem' : '0'};
|
||||||
|
|
||||||
|
margin-bottom: ${({ $panelType }): string =>
|
||||||
|
$panelType === PANEL_TYPES.TABLE ? '1rem' : '0'};
|
||||||
`;
|
`;
|
||||||
|
@ -23,11 +23,7 @@ export function QueryTable({
|
|||||||
[query, queryTableData, renderColumnCell, renderActionCell],
|
[query, queryTableData, renderColumnCell, renderActionCell],
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredColumns = columns.filter((item) => item.key !== 'timestamp');
|
const tableColumns = modifyColumns ? modifyColumns(columns) : columns;
|
||||||
|
|
||||||
const tableColumns = modifyColumns
|
|
||||||
? modifyColumns(filteredColumns)
|
|
||||||
: filteredColumns;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResizeTable
|
<ResizeTable
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { ColumnsType } from 'antd/es/table';
|
import { ColumnsType } from 'antd/es/table';
|
||||||
import { ColumnType } from 'antd/lib/table';
|
import { ColumnType } from 'antd/lib/table';
|
||||||
|
import {
|
||||||
|
initialFormulaBuilderFormValues,
|
||||||
|
initialQueryBuilderFormValues,
|
||||||
|
} from 'constants/queryBuilder';
|
||||||
import { FORMULA_REGEXP } from 'constants/regExp';
|
import { FORMULA_REGEXP } from 'constants/regExp';
|
||||||
import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces';
|
import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces';
|
||||||
import { toCapitalize } from 'lib/toCapitalize';
|
import { toCapitalize } from 'lib/toCapitalize';
|
||||||
@ -25,9 +29,10 @@ export type RowData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type DynamicColumn = {
|
type DynamicColumn = {
|
||||||
key: keyof RowData;
|
query: IBuilderQuery | IBuilderFormula;
|
||||||
|
field: string;
|
||||||
|
dataIndex: string;
|
||||||
title: string;
|
title: string;
|
||||||
sourceLabel: string;
|
|
||||||
data: (string | number)[];
|
data: (string | number)[];
|
||||||
type: 'field' | 'operator' | 'formula';
|
type: 'field' | 'operator' | 'formula';
|
||||||
// sortable: boolean;
|
// sortable: boolean;
|
||||||
@ -55,7 +60,6 @@ type GetDynamicColumns = (
|
|||||||
|
|
||||||
type ListItemData = ListItem['data'];
|
type ListItemData = ListItem['data'];
|
||||||
type ListItemKey = keyof ListItemData;
|
type ListItemKey = keyof ListItemData;
|
||||||
type SeriesItemLabels = SeriesItem['labels'];
|
|
||||||
|
|
||||||
const isFormula = (queryName: string): boolean =>
|
const isFormula = (queryName: string): boolean =>
|
||||||
FORMULA_REGEXP.test(queryName);
|
FORMULA_REGEXP.test(queryName);
|
||||||
@ -74,32 +78,31 @@ const getQueryByName = <T extends keyof QueryBuilderData>(
|
|||||||
builder: QueryBuilderData,
|
builder: QueryBuilderData,
|
||||||
currentQueryName: string,
|
currentQueryName: string,
|
||||||
type: T,
|
type: T,
|
||||||
): (T extends 'queryData' ? IBuilderQuery : IBuilderFormula) | null => {
|
): T extends 'queryData' ? IBuilderQuery : IBuilderFormula => {
|
||||||
const queryArray = builder[type];
|
const queryArray = builder[type];
|
||||||
|
const defaultValue =
|
||||||
|
type === 'queryData'
|
||||||
|
? initialQueryBuilderFormValues
|
||||||
|
: initialFormulaBuilderFormValues;
|
||||||
|
|
||||||
const currentQuery =
|
const currentQuery =
|
||||||
queryArray.find((q) => q.queryName === currentQueryName) || null;
|
queryArray.find((q) => q.queryName === currentQueryName) || defaultValue;
|
||||||
|
|
||||||
if (!currentQuery) return null;
|
|
||||||
|
|
||||||
return currentQuery as T extends 'queryData' ? IBuilderQuery : IBuilderFormula;
|
return currentQuery as T extends 'queryData' ? IBuilderQuery : IBuilderFormula;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createLabels = <T extends ListItemData | SeriesItemLabels>(
|
const addListLabels = (
|
||||||
// labels: T,
|
query: IBuilderQuery | IBuilderFormula,
|
||||||
label: keyof T,
|
label: ListItemKey,
|
||||||
dynamicColumns: DynamicColumns,
|
dynamicColumns: DynamicColumns,
|
||||||
): void => {
|
): void => {
|
||||||
if (isValueExist('key', label as string, dynamicColumns)) return;
|
if (isValueExist('dataIndex', label, dynamicColumns)) return;
|
||||||
|
|
||||||
// const labelValue = labels[label];
|
|
||||||
|
|
||||||
// const isNumber = !Number.isNaN(parseFloat(String(labelValue)));
|
|
||||||
|
|
||||||
const fieldObj: DynamicColumn = {
|
const fieldObj: DynamicColumn = {
|
||||||
key: label as string,
|
query,
|
||||||
|
field: 'label',
|
||||||
|
dataIndex: label as string,
|
||||||
title: label as string,
|
title: label as string,
|
||||||
sourceLabel: label as string,
|
|
||||||
data: [],
|
data: [],
|
||||||
type: 'field',
|
type: 'field',
|
||||||
// sortable: isNumber,
|
// sortable: isNumber,
|
||||||
@ -108,42 +111,59 @@ const createLabels = <T extends ListItemData | SeriesItemLabels>(
|
|||||||
dynamicColumns.push(fieldObj);
|
dynamicColumns.push(fieldObj);
|
||||||
};
|
};
|
||||||
|
|
||||||
const appendOperatorFormulaColumns = (
|
const addSeriaLabels = (
|
||||||
builder: QueryBuilderData,
|
label: string,
|
||||||
currentQueryName: string,
|
|
||||||
dynamicColumns: DynamicColumns,
|
dynamicColumns: DynamicColumns,
|
||||||
|
query: IBuilderQuery | IBuilderFormula,
|
||||||
): void => {
|
): void => {
|
||||||
const currentFormula = getQueryByName(
|
if (isValueExist('dataIndex', label, dynamicColumns)) return;
|
||||||
builder,
|
|
||||||
currentQueryName,
|
|
||||||
'queryFormulas',
|
|
||||||
);
|
|
||||||
if (currentFormula) {
|
|
||||||
let formulaLabel = `${currentFormula.queryName}(${currentFormula.expression})`;
|
|
||||||
|
|
||||||
if (currentFormula.legend) {
|
// const labelValue = labels[label];
|
||||||
formulaLabel += ` - ${currentFormula.legend}`;
|
|
||||||
|
// const isNumber = !Number.isNaN(parseFloat(String(labelValue)));
|
||||||
|
|
||||||
|
const fieldObj: DynamicColumn = {
|
||||||
|
query,
|
||||||
|
field: label as string,
|
||||||
|
dataIndex: label,
|
||||||
|
title: label,
|
||||||
|
data: [],
|
||||||
|
type: 'field',
|
||||||
|
// sortable: isNumber,
|
||||||
|
};
|
||||||
|
|
||||||
|
dynamicColumns.push(fieldObj);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addOperatorFormulaColumns = (
|
||||||
|
query: IBuilderFormula | IBuilderQuery,
|
||||||
|
dynamicColumns: DynamicColumns,
|
||||||
|
customLabel?: string,
|
||||||
|
): void => {
|
||||||
|
if (isFormula(query.queryName)) {
|
||||||
|
const formulaQuery = query as IBuilderFormula;
|
||||||
|
let formulaLabel = `${formulaQuery.queryName}(${formulaQuery.expression})`;
|
||||||
|
|
||||||
|
if (formulaQuery.legend) {
|
||||||
|
formulaLabel += ` - ${formulaQuery.legend}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formulaColumn: DynamicColumn = {
|
const formulaColumn: DynamicColumn = {
|
||||||
key: currentQueryName,
|
query,
|
||||||
title: formulaLabel,
|
field: formulaQuery.queryName,
|
||||||
sourceLabel: formulaLabel,
|
dataIndex: formulaQuery.queryName,
|
||||||
|
title: customLabel || formulaLabel,
|
||||||
data: [],
|
data: [],
|
||||||
type: 'formula',
|
type: 'formula',
|
||||||
// sortable: isNumber,
|
// sortable: isNumber,
|
||||||
};
|
};
|
||||||
|
|
||||||
dynamicColumns.push(formulaColumn);
|
dynamicColumns.push(formulaColumn);
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentQueryData = getQueryByName(
|
const currentQueryData = query as IBuilderQuery;
|
||||||
builder,
|
|
||||||
currentQueryName,
|
|
||||||
'queryData',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!currentQueryData) return;
|
|
||||||
|
|
||||||
let operatorLabel = `${currentQueryData.aggregateOperator}`;
|
let operatorLabel = `${currentQueryData.aggregateOperator}`;
|
||||||
if (currentQueryData.aggregateAttribute.key) {
|
if (currentQueryData.aggregateAttribute.key) {
|
||||||
@ -159,9 +179,10 @@ const appendOperatorFormulaColumns = (
|
|||||||
const resultValue = `${toCapitalize(operatorLabel)}`;
|
const resultValue = `${toCapitalize(operatorLabel)}`;
|
||||||
|
|
||||||
const operatorColumn: DynamicColumn = {
|
const operatorColumn: DynamicColumn = {
|
||||||
key: currentQueryName,
|
query,
|
||||||
title: resultValue,
|
field: currentQueryData.queryName,
|
||||||
sourceLabel: resultValue,
|
dataIndex: currentQueryData.queryName,
|
||||||
|
title: customLabel || resultValue,
|
||||||
data: [],
|
data: [],
|
||||||
type: 'operator',
|
type: 'operator',
|
||||||
// sortable: isNumber,
|
// sortable: isNumber,
|
||||||
@ -170,59 +191,73 @@ const appendOperatorFormulaColumns = (
|
|||||||
dynamicColumns.push(operatorColumn);
|
dynamicColumns.push(operatorColumn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const transformColumnTitles = (
|
||||||
|
dynamicColumns: DynamicColumns,
|
||||||
|
): DynamicColumns =>
|
||||||
|
dynamicColumns.map((item) => {
|
||||||
|
if (isFormula(item.field as string)) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sameValues = dynamicColumns.filter(
|
||||||
|
(column) => column.title === item.title,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sameValues.length > 1) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
dataIndex: `${item.title} - ${item.query.queryName}`,
|
||||||
|
title: `${item.title} - ${item.query.queryName}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
const getDynamicColumns: GetDynamicColumns = (queryTableData, query) => {
|
const getDynamicColumns: GetDynamicColumns = (queryTableData, query) => {
|
||||||
const dynamicColumns: DynamicColumns = [];
|
const dynamicColumns: DynamicColumns = [];
|
||||||
|
|
||||||
queryTableData.forEach((currentQuery) => {
|
queryTableData.forEach((currentQuery) => {
|
||||||
|
const currentStagedQuery = getQueryByName(
|
||||||
|
query.builder,
|
||||||
|
currentQuery.queryName,
|
||||||
|
isFormula(currentQuery.queryName) ? 'queryFormulas' : 'queryData',
|
||||||
|
);
|
||||||
if (currentQuery.list) {
|
if (currentQuery.list) {
|
||||||
currentQuery.list.forEach((listItem) => {
|
currentQuery.list.forEach((listItem) => {
|
||||||
Object.keys(listItem.data).forEach((label) => {
|
Object.keys(listItem.data).forEach((label) => {
|
||||||
createLabels<ListItemData>(label as ListItemKey, dynamicColumns);
|
addListLabels(currentStagedQuery, label as ListItemKey, dynamicColumns);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentQuery.series) {
|
if (currentQuery.series) {
|
||||||
if (!isValueExist('key', 'timestamp', dynamicColumns)) {
|
const isValuesColumnExist = currentQuery.series.some(
|
||||||
dynamicColumns.push({
|
(item) => item.values.length > 0,
|
||||||
key: 'timestamp',
|
|
||||||
title: 'Timestamp',
|
|
||||||
sourceLabel: 'Timestamp',
|
|
||||||
data: [],
|
|
||||||
type: 'field',
|
|
||||||
// sortable: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
appendOperatorFormulaColumns(
|
|
||||||
query.builder,
|
|
||||||
currentQuery.queryName,
|
|
||||||
dynamicColumns,
|
|
||||||
);
|
);
|
||||||
|
const isEveryValuesExist = currentQuery.series.every(
|
||||||
|
(item) => item.values.length > 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isValuesColumnExist) {
|
||||||
|
addOperatorFormulaColumns(
|
||||||
|
currentStagedQuery,
|
||||||
|
dynamicColumns,
|
||||||
|
isEveryValuesExist ? undefined : currentStagedQuery.queryName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
currentQuery.series.forEach((seria) => {
|
currentQuery.series.forEach((seria) => {
|
||||||
Object.keys(seria.labels).forEach((label) => {
|
Object.keys(seria.labels).forEach((label) => {
|
||||||
createLabels<SeriesItemLabels>(label, dynamicColumns);
|
if (label === currentQuery?.queryName) return;
|
||||||
|
|
||||||
|
addSeriaLabels(label as string, dynamicColumns, currentStagedQuery);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return dynamicColumns.map((item) => {
|
return transformColumnTitles(dynamicColumns);
|
||||||
if (isFormula(item.key as string)) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sameValues = dynamicColumns.filter(
|
|
||||||
(column) => column.sourceLabel === item.sourceLabel,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (sameValues.length > 1) {
|
|
||||||
return { ...item, title: `${item.title} - ${item.key}` };
|
|
||||||
}
|
|
||||||
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const fillEmptyRowCells = (
|
const fillEmptyRowCells = (
|
||||||
@ -231,8 +266,8 @@ const fillEmptyRowCells = (
|
|||||||
currentColumn: DynamicColumn,
|
currentColumn: DynamicColumn,
|
||||||
): void => {
|
): void => {
|
||||||
unusedColumnsKeys.forEach((key) => {
|
unusedColumnsKeys.forEach((key) => {
|
||||||
if (key === currentColumn.key) {
|
if (key === currentColumn.field) {
|
||||||
const unusedCol = sourceColumns.find((item) => item.key === key);
|
const unusedCol = sourceColumns.find((item) => item.field === key);
|
||||||
|
|
||||||
if (unusedCol) {
|
if (unusedCol) {
|
||||||
unusedCol.data.push('N/A');
|
unusedCol.data.push('N/A');
|
||||||
@ -242,33 +277,27 @@ const fillEmptyRowCells = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const fillDataFromSeria = (
|
const fillData = (
|
||||||
seria: SeriesItem,
|
seria: SeriesItem,
|
||||||
columns: DynamicColumns,
|
columns: DynamicColumns,
|
||||||
queryName: string,
|
queryName: string,
|
||||||
|
value?: SeriesItem['values'][number],
|
||||||
): void => {
|
): void => {
|
||||||
const labelEntries = Object.entries(seria.labels);
|
const labelEntries = Object.entries(seria.labels);
|
||||||
|
|
||||||
seria.values.forEach((value) => {
|
|
||||||
const unusedColumnsKeys = new Set<keyof RowData>(
|
const unusedColumnsKeys = new Set<keyof RowData>(
|
||||||
columns.map((item) => item.key),
|
columns.map((item) => item.field),
|
||||||
);
|
);
|
||||||
|
|
||||||
columns.forEach((column) => {
|
columns.forEach((column) => {
|
||||||
if (column.key === 'timestamp') {
|
if (queryName === column.field && value) {
|
||||||
column.data.push(value.timestamp);
|
|
||||||
unusedColumnsKeys.delete('timestamp');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queryName === column.key) {
|
|
||||||
column.data.push(parseFloat(value.value).toFixed(2));
|
column.data.push(parseFloat(value.value).toFixed(2));
|
||||||
unusedColumnsKeys.delete(column.key);
|
unusedColumnsKeys.delete(column.field);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
labelEntries.forEach(([key, currentValue]) => {
|
labelEntries.forEach(([key, currentValue]) => {
|
||||||
if (column.key === key) {
|
if (column.field === key) {
|
||||||
column.data.push(currentValue);
|
column.data.push(currentValue);
|
||||||
unusedColumnsKeys.delete(key);
|
unusedColumnsKeys.delete(key);
|
||||||
}
|
}
|
||||||
@ -276,6 +305,21 @@ const fillDataFromSeria = (
|
|||||||
|
|
||||||
fillEmptyRowCells(unusedColumnsKeys, columns, column);
|
fillEmptyRowCells(unusedColumnsKeys, columns, column);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const fillDataFromSeria = (
|
||||||
|
seria: SeriesItem,
|
||||||
|
columns: DynamicColumns,
|
||||||
|
queryName: string,
|
||||||
|
): void => {
|
||||||
|
if (seria.values.length === 0) {
|
||||||
|
fillData(seria, columns, queryName);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
seria.values.forEach((value) => {
|
||||||
|
fillData(seria, columns, queryName, value);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -284,10 +328,10 @@ const fillDataFromList = (
|
|||||||
columns: DynamicColumns,
|
columns: DynamicColumns,
|
||||||
): void => {
|
): void => {
|
||||||
columns.forEach((column) => {
|
columns.forEach((column) => {
|
||||||
if (isFormula(column.key as string)) return;
|
if (isFormula(column.field)) return;
|
||||||
|
|
||||||
Object.keys(listItem.data).forEach((label) => {
|
Object.keys(listItem.data).forEach((label) => {
|
||||||
if (column.key === label) {
|
if (column.dataIndex === label) {
|
||||||
if (listItem.data[label as ListItemKey] !== '') {
|
if (listItem.data[label as ListItemKey] !== '') {
|
||||||
column.data.push(listItem.data[label as ListItemKey].toString());
|
column.data.push(listItem.data[label as ListItemKey].toString());
|
||||||
} else {
|
} else {
|
||||||
@ -331,9 +375,9 @@ const generateData = (
|
|||||||
|
|
||||||
for (let i = 0; i < rowsLength; i += 1) {
|
for (let i = 0; i < rowsLength; i += 1) {
|
||||||
const rowData: RowData = dynamicColumns.reduce((acc, item) => {
|
const rowData: RowData = dynamicColumns.reduce((acc, item) => {
|
||||||
const { key } = item;
|
const { dataIndex } = item;
|
||||||
|
|
||||||
acc[key] = item.data[i];
|
acc[dataIndex] = item.data[i];
|
||||||
acc.key = uuid();
|
acc.key = uuid();
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
@ -353,10 +397,9 @@ const generateTableColumns = (
|
|||||||
ColumnsType<RowData>
|
ColumnsType<RowData>
|
||||||
>((acc, item) => {
|
>((acc, item) => {
|
||||||
const column: ColumnType<RowData> = {
|
const column: ColumnType<RowData> = {
|
||||||
dataIndex: item.key,
|
dataIndex: item.dataIndex,
|
||||||
key: item.key,
|
|
||||||
title: item.title,
|
title: item.title,
|
||||||
render: renderColumnCell && renderColumnCell[item.key],
|
render: renderColumnCell && renderColumnCell[item.dataIndex],
|
||||||
// sorter: item.sortable
|
// sorter: item.sortable
|
||||||
// ? (a: RowData, b: RowData): number =>
|
// ? (a: RowData, b: RowData): number =>
|
||||||
// (a[item.key] as number) - (b[item.key] as number)
|
// (a[item.key] as number) - (b[item.key] as number)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user