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:
Yevhen Shevchenko 2023-07-25 16:25:36 +03:00 committed by GitHub
parent 59deac01bd
commit e6fa1383f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 532 additions and 266 deletions

View File

@ -343,7 +343,7 @@ type CustomChartOptions = ChartOptions & {
};
};
interface GraphProps {
export interface GraphProps {
animate?: boolean;
type: ChartType;
data: Chart['data'];

View 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;

View File

@ -1,5 +1,4 @@
// ** Helpers
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderItemName';
import {
@ -24,7 +23,6 @@ import {
LogsAggregatorOperator,
MetricAggregateOperator,
NumberOperators,
PanelTypeKeys,
QueryAdditionalFilter,
QueryBuilderData,
ReduceOperators,
@ -124,7 +122,7 @@ export const initialFilters: TagFilter = {
op: 'AND',
};
const initialQueryBuilderFormValues: IBuilderQuery = {
export const initialQueryBuilderFormValues: IBuilderQuery = {
dataSource: DataSource.METRICS,
queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
aggregateOperator: MetricAggregateOperator.NOOP,
@ -238,14 +236,15 @@ export const operatorsByTypes: Record<LocalDataType, string[]> = {
bool: Object.values(BoolOperators),
};
export const PANEL_TYPES: Record<PanelTypeKeys, GRAPH_TYPES> = {
TIME_SERIES: 'graph',
VALUE: 'value',
TABLE: 'table',
LIST: 'list',
TRACE: 'trace',
EMPTY_WIDGET: 'EMPTY_WIDGET',
};
// eslint-disable-next-line @typescript-eslint/naming-convention
export enum PANEL_TYPES {
TIME_SERIES = 'graph',
VALUE = 'value',
TABLE = 'table',
LIST = 'list',
TRACE = 'trace',
EMPTY_WIDGET = 'EMPTY_WIDGET',
}
export type IQueryBuilderState = 'search';

View File

@ -2,7 +2,7 @@ import { InfoCircleOutlined } from '@ant-design/icons';
import { StaticLineProps } from 'components/Graph';
import Spinner from 'components/Spinner';
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 { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
import { Time } from 'container/TopNav/DateTimeSelection/config';
@ -113,13 +113,15 @@ function ChartPreview({
<Spinner size="large" tip="Loading..." height="70vh" />
)}
{chartDataSet && !queryResponse.isError && (
<GridGraphComponent
<GridPanelSwitch
panelType={graphType}
title={name}
data={chartDataSet}
isStacked
GRAPH_TYPES={graphType || PANEL_TYPES.TIME_SERIES}
name={name || 'Chart Preview'}
staticLine={staticLine}
panelData={queryResponse.data?.payload.data.newResult.data.result || []}
query={query || initialQueriesMap.metrics}
/>
)}
</ChartContainer>

View File

@ -3,6 +3,7 @@ import { Col, FormInstance, Modal, Tooltip, Typography } from 'antd';
import saveAlertApi from 'api/alerts/save';
import testAlertApi from 'api/alerts/testAlert';
import { FeatureKeys } from 'constants/features';
import { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag';
import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag';
@ -58,6 +59,7 @@ function FormAlertRules({
const {
currentQuery,
panelType,
stagedQuery,
handleRunQuery,
redirectWithQueryBuilderData,
@ -351,7 +353,12 @@ function FormAlertRules({
const renderQBChartPreview = (): JSX.Element => (
<ChartPreview
headline={<PlotTag queryType={currentQuery.queryType} />}
headline={
<PlotTag
queryType={currentQuery.queryType}
panelType={panelType || PANEL_TYPES.TIME_SERIES}
/>
}
name=""
threshold={alertDef.condition?.target}
query={stagedQuery}
@ -361,7 +368,12 @@ function FormAlertRules({
const renderPromChartPreview = (): JSX.Element => (
<ChartPreview
headline={<PlotTag queryType={currentQuery.queryType} />}
headline={
<PlotTag
queryType={currentQuery.queryType}
panelType={panelType || PANEL_TYPES.TIME_SERIES}
/>
}
name="Chart Preview"
threshold={alertDef.condition?.target}
query={stagedQuery}
@ -370,7 +382,12 @@ function FormAlertRules({
const renderChQueryChartPreview = (): JSX.Element => (
<ChartPreview
headline={<PlotTag queryType={currentQuery.queryType} />}
headline={
<PlotTag
queryType={currentQuery.queryType}
panelType={panelType || PANEL_TYPES.TIME_SERIES}
/>
}
name="Chart Preview"
threshold={alertDef.condition?.target}
query={stagedQuery}

View File

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

View File

@ -2,7 +2,7 @@ import { Button } from 'antd';
import { GraphOnClickHandler } from 'components/Graph';
import Spinner from 'components/Spinner';
import TimePreference from 'components/TimePreferenceDropDown';
import GridGraphComponent from 'container/GridGraphComponent';
import GridPanelSwitch from 'container/GridPanelSwitch';
import {
timeItems,
timePreferance,
@ -85,7 +85,7 @@ function FullView({
return (
<>
{fullViewOptions && (
<TimeContainer>
<TimeContainer $panelType={widget.panelTypes}>
<TimePreference
selectedTime={selectedTime}
setSelectedTime={setSelectedTime}
@ -101,8 +101,8 @@ function FullView({
</TimeContainer>
)}
<GridGraphComponent
GRAPH_TYPES={widget.panelTypes}
<GridPanelSwitch
panelType={widget.panelTypes}
data={chartDataSet}
isStacked={widget.isStacked}
opacity={widget.opacity}
@ -111,6 +111,8 @@ function FullView({
name={name}
yAxisUnit={yAxisUnit}
onDragSelect={onDragSelect}
panelData={response.data?.payload.data.newResult.data.result || []}
query={widget.query}
/>
</>
);

View File

@ -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`
display: flex;
@ -7,7 +13,13 @@ export const NotFoundContainer = styled.div`
min-height: 55vh;
`;
export const TimeContainer = styled.div`
export const TimeContainer = styled.div<Props>`
display: flex;
justify-content: flex-end;
${({ $panelType }): FlattenSimpleInterpolation =>
$panelType === PANEL_TYPES.TABLE
? css`
margin-bottom: 1rem;
`
: css``}
`;

View File

@ -2,8 +2,8 @@ import { Typography } from 'antd';
import { ChartData } from 'chart.js';
import { GraphOnClickHandler } from 'components/Graph';
import Spinner from 'components/Spinner';
import GridGraphComponent from 'container/GridGraphComponent';
import { UpdateDashboard } from 'container/GridGraphLayout/utils';
import GridPanelSwitch from 'container/GridPanelSwitch';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
import { useNotifications } from 'hooks/useNotifications';
@ -272,8 +272,8 @@ function GridCardGraph({
allowEdit={allowEdit}
/>
</div>
<GridGraphComponent
GRAPH_TYPES={widget?.panelTypes}
<GridPanelSwitch
panelType={widget?.panelTypes}
data={prevChartDataSetRef}
isStacked={widget?.isStacked}
opacity={widget?.opacity}
@ -281,6 +281,8 @@ function GridCardGraph({
name={name}
yAxisUnit={yAxisUnit}
onClickHandler={onClickHandler}
panelData={[]}
query={widget.query}
/>
</>
)}
@ -308,8 +310,8 @@ function GridCardGraph({
allowEdit={allowEdit}
/>
</div>
<GridGraphComponent
GRAPH_TYPES={widget.panelTypes}
<GridPanelSwitch
panelType={widget.panelTypes}
data={prevChartDataSetRef}
isStacked={widget.isStacked}
opacity={widget.opacity}
@ -317,6 +319,8 @@ function GridCardGraph({
name={name}
yAxisUnit={yAxisUnit}
onClickHandler={onClickHandler}
panelData={[]}
query={widget.query}
/>
</>
) : (
@ -363,8 +367,8 @@ function GridCardGraph({
{!isEmptyLayout && getModals()}
{!isEmpty(widget) && !!queryResponse.data?.payload && (
<GridGraphComponent
GRAPH_TYPES={widget.panelTypes}
<GridPanelSwitch
panelType={widget.panelTypes}
data={chartData}
isStacked={widget.isStacked}
opacity={widget.opacity}
@ -373,6 +377,8 @@ function GridCardGraph({
yAxisUnit={yAxisUnit}
onDragSelect={onDragSelect}
onClickHandler={onClickHandler}
panelData={queryResponse.data?.payload.data.newResult.data.result || []}
query={widget.query}
/>
)}

View File

@ -1,4 +1,5 @@
import { PlusOutlined, SaveFilled } from '@ant-design/icons';
import { PANEL_TYPES } from 'constants/queryBuilder';
import useComponentPermission from 'hooks/useComponentPermission';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { Dispatch, SetStateAction } from 'react';
@ -83,7 +84,7 @@ function GraphLayout({
key={currentWidget?.id || 'empty'} // don't change this key
data-grid={rest}
>
<Card>
<Card $panelType={currentWidget?.panelTypes || PANEL_TYPES.TIME_SERIES}>
<Component setLayout={setLayout} />
</Card>
</CardContainer>

View File

@ -1,11 +1,17 @@
import { Button as ButtonComponent, Card as CardComponent, Space } from 'antd';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { StyledCSS } from 'container/GantChart/Trace/styles';
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
import RGL, { WidthProvider } from 'react-grid-layout';
import styled, { css } from 'styled-components';
import styled, { css, FlattenSimpleInterpolation } from 'styled-components';
const ReactGridLayoutComponent = WidthProvider(RGL);
export const Card = styled(CardComponent)`
interface CardProps {
$panelType: ITEMS;
}
export const Card = styled(CardComponent)<CardProps>`
&&& {
height: 100%;
}
@ -13,6 +19,12 @@ export const Card = styled(CardComponent)`
.ant-card-body {
height: 95%;
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>`
overflow: auto;
:hover {
.react-resizable-handle {
position: absolute;
@ -44,6 +58,7 @@ export const CardContainer = styled.div<Props>`
background-image: ${(): string => `url("${uri}")`};
`;
}}
}
}
`;

View 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);

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

View 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',
};

View 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);

View 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;
}
`;

View 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'>;

View File

@ -0,0 +1,3 @@
import { GridValueComponentProps } from './types';
export const GridValueConfig: Pick<GridValueComponentProps, 'title'> = {};

View 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);

View File

@ -0,0 +1,7 @@
import { ChartData } from 'chart.js';
export type GridValueComponentProps = {
data: ChartData;
title?: string;
yAxisUnit?: string;
};

View File

@ -14,6 +14,7 @@ const Items: ItemsProps[] = [
Icon: ValueIcon,
display: 'Value',
},
{ name: PANEL_TYPES.TABLE, Icon: TimeSeries, display: 'Table' },
];
export type ITEMS =

View File

@ -1,20 +1,23 @@
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
import { EQueryType } from 'types/common/dashboard';
import QueryTypeTag from '../QueryTypeTag';
import { PlotTagWrapperStyled } from './styles';
interface IPlotTagProps {
queryType: EQueryType;
panelType: ITEMS;
}
function PlotTag({ queryType }: IPlotTagProps): JSX.Element | null {
function PlotTag({ queryType, panelType }: IPlotTagProps): JSX.Element | null {
if (queryType === undefined) {
return null;
}
return (
<div style={{ marginLeft: '2rem', position: 'absolute', top: '1rem' }}>
<PlotTagWrapperStyled $panelType={panelType}>
Plotted using <QueryTypeTag queryType={queryType} />
</div>
</PlotTagWrapperStyled>
);
}

View File

@ -1,8 +1,9 @@
import { Card, Typography } from 'antd';
import Spinner from 'components/Spinner';
import GridGraphComponent from 'container/GridGraphComponent';
import GridPanelSwitch from 'container/GridPanelSwitch';
import { WidgetGraphProps } from 'container/NewWidget/types';
import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import getChartData from 'lib/getChartData';
import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
@ -16,6 +17,7 @@ function WidgetGraph({
yAxisUnit,
selectedTime,
}: WidgetGraphProps): JSX.Element {
const { stagedQuery } = useQueryBuilder();
const { dashboards } = useSelector<AppState, DashboardReducer>(
(state) => state.dashboards,
);
@ -39,7 +41,7 @@ function WidgetGraph({
return <Card>Invalid widget</Card>;
}
const { title, opacity, isStacked } = selectedWidget;
const { title, opacity, isStacked, query } = selectedWidget;
if (getWidgetQueryRange.error) {
return (
@ -66,14 +68,18 @@ function WidgetGraph({
});
return (
<GridGraphComponent
<GridPanelSwitch
title={title}
isStacked={isStacked}
opacity={opacity}
data={chartDataSet}
GRAPH_TYPES={selectedGraph}
panelType={selectedGraph}
name={widgetId || 'legend_widget'}
yAxisUnit={yAxisUnit}
panelData={
getWidgetQueryRange.data?.payload.data.newResult.data.result || []
}
query={stagedQuery || query}
/>
);
}

View File

@ -41,12 +41,12 @@ function WidgetGraph({
});
if (selectedWidget === undefined) {
return <Card>Invalid widget</Card>;
return <Card $panelType={selectedGraph}>Invalid widget</Card>;
}
return (
<Container>
<PlotTag queryType={currentQuery.queryType} />
<Container $panelType={selectedGraph}>
<PlotTag queryType={currentQuery.queryType} panelType={selectedGraph} />
{getWidgetQueryRange.error && (
<AlertIconContainer color="red" title={getWidgetQueryRange.error.message}>
<InfoCircleOutlined />

View File

@ -1,15 +1,24 @@
import { Card, Tooltip } from 'antd';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
import styled from 'styled-components';
export const Container = styled(Card)`
interface Props {
$panelType: ITEMS;
}
export const Container = styled(Card)<Props>`
&&& {
position: relative;
}
.ant-card-body {
padding: 1.5rem 0;
padding: ${({ $panelType }): string =>
$panelType === PANEL_TYPES.TABLE ? '0 0' : '1.5rem 0'};
height: 57vh;
/* padding-bottom: 2rem; */
overflow: auto;
display: flex;
flex-direction: column;
}
`;
@ -23,5 +32,14 @@ export const NotFoundContainer = styled.div`
display: flex;
justify-content: 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'};
`;

View File

@ -23,11 +23,7 @@ export function QueryTable({
[query, queryTableData, renderColumnCell, renderActionCell],
);
const filteredColumns = columns.filter((item) => item.key !== 'timestamp');
const tableColumns = modifyColumns
? modifyColumns(filteredColumns)
: filteredColumns;
const tableColumns = modifyColumns ? modifyColumns(columns) : columns;
return (
<ResizeTable

View File

@ -1,5 +1,9 @@
import { ColumnsType } from 'antd/es/table';
import { ColumnType } from 'antd/lib/table';
import {
initialFormulaBuilderFormValues,
initialQueryBuilderFormValues,
} from 'constants/queryBuilder';
import { FORMULA_REGEXP } from 'constants/regExp';
import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces';
import { toCapitalize } from 'lib/toCapitalize';
@ -25,9 +29,10 @@ export type RowData = {
};
type DynamicColumn = {
key: keyof RowData;
query: IBuilderQuery | IBuilderFormula;
field: string;
dataIndex: string;
title: string;
sourceLabel: string;
data: (string | number)[];
type: 'field' | 'operator' | 'formula';
// sortable: boolean;
@ -55,7 +60,6 @@ type GetDynamicColumns = (
type ListItemData = ListItem['data'];
type ListItemKey = keyof ListItemData;
type SeriesItemLabels = SeriesItem['labels'];
const isFormula = (queryName: string): boolean =>
FORMULA_REGEXP.test(queryName);
@ -74,32 +78,31 @@ const getQueryByName = <T extends keyof QueryBuilderData>(
builder: QueryBuilderData,
currentQueryName: string,
type: T,
): (T extends 'queryData' ? IBuilderQuery : IBuilderFormula) | null => {
): T extends 'queryData' ? IBuilderQuery : IBuilderFormula => {
const queryArray = builder[type];
const defaultValue =
type === 'queryData'
? initialQueryBuilderFormValues
: initialFormulaBuilderFormValues;
const currentQuery =
queryArray.find((q) => q.queryName === currentQueryName) || null;
if (!currentQuery) return null;
queryArray.find((q) => q.queryName === currentQueryName) || defaultValue;
return currentQuery as T extends 'queryData' ? IBuilderQuery : IBuilderFormula;
};
const createLabels = <T extends ListItemData | SeriesItemLabels>(
// labels: T,
label: keyof T,
const addListLabels = (
query: IBuilderQuery | IBuilderFormula,
label: ListItemKey,
dynamicColumns: DynamicColumns,
): void => {
if (isValueExist('key', label as string, dynamicColumns)) return;
// const labelValue = labels[label];
// const isNumber = !Number.isNaN(parseFloat(String(labelValue)));
if (isValueExist('dataIndex', label, dynamicColumns)) return;
const fieldObj: DynamicColumn = {
key: label as string,
query,
field: 'label',
dataIndex: label as string,
title: label as string,
sourceLabel: label as string,
data: [],
type: 'field',
// sortable: isNumber,
@ -108,42 +111,59 @@ const createLabels = <T extends ListItemData | SeriesItemLabels>(
dynamicColumns.push(fieldObj);
};
const appendOperatorFormulaColumns = (
builder: QueryBuilderData,
currentQueryName: string,
const addSeriaLabels = (
label: string,
dynamicColumns: DynamicColumns,
query: IBuilderQuery | IBuilderFormula,
): void => {
const currentFormula = getQueryByName(
builder,
currentQueryName,
'queryFormulas',
);
if (currentFormula) {
let formulaLabel = `${currentFormula.queryName}(${currentFormula.expression})`;
if (isValueExist('dataIndex', label, dynamicColumns)) return;
if (currentFormula.legend) {
formulaLabel += ` - ${currentFormula.legend}`;
// const labelValue = labels[label];
// 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 = {
key: currentQueryName,
title: formulaLabel,
sourceLabel: formulaLabel,
query,
field: formulaQuery.queryName,
dataIndex: formulaQuery.queryName,
title: customLabel || formulaLabel,
data: [],
type: 'formula',
// sortable: isNumber,
};
dynamicColumns.push(formulaColumn);
return;
}
const currentQueryData = getQueryByName(
builder,
currentQueryName,
'queryData',
);
if (!currentQueryData) return;
const currentQueryData = query as IBuilderQuery;
let operatorLabel = `${currentQueryData.aggregateOperator}`;
if (currentQueryData.aggregateAttribute.key) {
@ -159,9 +179,10 @@ const appendOperatorFormulaColumns = (
const resultValue = `${toCapitalize(operatorLabel)}`;
const operatorColumn: DynamicColumn = {
key: currentQueryName,
title: resultValue,
sourceLabel: resultValue,
query,
field: currentQueryData.queryName,
dataIndex: currentQueryData.queryName,
title: customLabel || resultValue,
data: [],
type: 'operator',
// sortable: isNumber,
@ -170,59 +191,73 @@ const appendOperatorFormulaColumns = (
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 dynamicColumns: DynamicColumns = [];
queryTableData.forEach((currentQuery) => {
const currentStagedQuery = getQueryByName(
query.builder,
currentQuery.queryName,
isFormula(currentQuery.queryName) ? 'queryFormulas' : 'queryData',
);
if (currentQuery.list) {
currentQuery.list.forEach((listItem) => {
Object.keys(listItem.data).forEach((label) => {
createLabels<ListItemData>(label as ListItemKey, dynamicColumns);
addListLabels(currentStagedQuery, label as ListItemKey, dynamicColumns);
});
});
}
if (currentQuery.series) {
if (!isValueExist('key', 'timestamp', dynamicColumns)) {
dynamicColumns.push({
key: 'timestamp',
title: 'Timestamp',
sourceLabel: 'Timestamp',
data: [],
type: 'field',
// sortable: true,
});
}
appendOperatorFormulaColumns(
query.builder,
currentQuery.queryName,
dynamicColumns,
const isValuesColumnExist = currentQuery.series.some(
(item) => item.values.length > 0,
);
const isEveryValuesExist = currentQuery.series.every(
(item) => item.values.length > 0,
);
if (isValuesColumnExist) {
addOperatorFormulaColumns(
currentStagedQuery,
dynamicColumns,
isEveryValuesExist ? undefined : currentStagedQuery.queryName,
);
}
currentQuery.series.forEach((seria) => {
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) => {
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;
});
return transformColumnTitles(dynamicColumns);
};
const fillEmptyRowCells = (
@ -231,8 +266,8 @@ const fillEmptyRowCells = (
currentColumn: DynamicColumn,
): void => {
unusedColumnsKeys.forEach((key) => {
if (key === currentColumn.key) {
const unusedCol = sourceColumns.find((item) => item.key === key);
if (key === currentColumn.field) {
const unusedCol = sourceColumns.find((item) => item.field === key);
if (unusedCol) {
unusedCol.data.push('N/A');
@ -242,40 +277,49 @@ const fillEmptyRowCells = (
});
};
const fillData = (
seria: SeriesItem,
columns: DynamicColumns,
queryName: string,
value?: SeriesItem['values'][number],
): void => {
const labelEntries = Object.entries(seria.labels);
const unusedColumnsKeys = new Set<keyof RowData>(
columns.map((item) => item.field),
);
columns.forEach((column) => {
if (queryName === column.field && value) {
column.data.push(parseFloat(value.value).toFixed(2));
unusedColumnsKeys.delete(column.field);
return;
}
labelEntries.forEach(([key, currentValue]) => {
if (column.field === key) {
column.data.push(currentValue);
unusedColumnsKeys.delete(key);
}
});
fillEmptyRowCells(unusedColumnsKeys, columns, column);
});
};
const fillDataFromSeria = (
seria: SeriesItem,
columns: DynamicColumns,
queryName: string,
): void => {
const labelEntries = Object.entries(seria.labels);
if (seria.values.length === 0) {
fillData(seria, columns, queryName);
return;
}
seria.values.forEach((value) => {
const unusedColumnsKeys = new Set<keyof RowData>(
columns.map((item) => item.key),
);
columns.forEach((column) => {
if (column.key === 'timestamp') {
column.data.push(value.timestamp);
unusedColumnsKeys.delete('timestamp');
return;
}
if (queryName === column.key) {
column.data.push(parseFloat(value.value).toFixed(2));
unusedColumnsKeys.delete(column.key);
return;
}
labelEntries.forEach(([key, currentValue]) => {
if (column.key === key) {
column.data.push(currentValue);
unusedColumnsKeys.delete(key);
}
});
fillEmptyRowCells(unusedColumnsKeys, columns, column);
});
fillData(seria, columns, queryName, value);
});
};
@ -284,10 +328,10 @@ const fillDataFromList = (
columns: DynamicColumns,
): void => {
columns.forEach((column) => {
if (isFormula(column.key as string)) return;
if (isFormula(column.field)) return;
Object.keys(listItem.data).forEach((label) => {
if (column.key === label) {
if (column.dataIndex === label) {
if (listItem.data[label as ListItemKey] !== '') {
column.data.push(listItem.data[label as ListItemKey].toString());
} else {
@ -331,9 +375,9 @@ const generateData = (
for (let i = 0; i < rowsLength; i += 1) {
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();
return acc;
@ -353,10 +397,9 @@ const generateTableColumns = (
ColumnsType<RowData>
>((acc, item) => {
const column: ColumnType<RowData> = {
dataIndex: item.key,
key: item.key,
dataIndex: item.dataIndex,
title: item.title,
render: renderColumnCell && renderColumnCell[item.key],
render: renderColumnCell && renderColumnCell[item.dataIndex],
// sorter: item.sortable
// ? (a: RowData, b: RowData): number =>
// (a[item.key] as number) - (b[item.key] as number)