From e6fa1383f3450e0b5785a716a5c5e81b6c37ea71 Mon Sep 17 00:00:00 2001 From: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Date: Tue, 25 Jul 2023 16:25:36 +0300 Subject: [PATCH] refactor: dashboard views (#3175) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- frontend/src/components/Graph/index.tsx | 2 +- frontend/src/constants/panelTypes.ts | 14 + frontend/src/constants/queryBuilder.ts | 21 +- .../FormAlertRules/ChartPreview/index.tsx | 8 +- .../src/container/FormAlertRules/index.tsx | 23 +- .../container/GridGraphComponent/index.tsx | 103 ------- .../GridGraphLayout/Graph/FullView/index.tsx | 10 +- .../GridGraphLayout/Graph/FullView/styles.ts | 16 +- .../container/GridGraphLayout/Graph/index.tsx | 20 +- .../container/GridGraphLayout/GraphLayout.tsx | 3 +- .../src/container/GridGraphLayout/styles.ts | 19 +- .../src/container/GridPanelSwitch/index.tsx | 73 +++++ .../src/container/GridPanelSwitch/types.ts | 37 +++ .../container/GridTableComponent/config.ts | 9 + .../container/GridTableComponent/index.tsx | 25 ++ .../container/GridTableComponent/styles.ts | 23 ++ .../src/container/GridTableComponent/types.ts | 10 + .../container/GridValueComponent/config.ts | 3 + .../container/GridValueComponent/index.tsx | 47 ++++ .../styles.ts | 0 .../src/container/GridValueComponent/types.ts | 7 + .../ComponentsSlider/menuItems.ts | 1 + .../LeftContainer/WidgetGraph/PlotTag.tsx | 9 +- .../LeftContainer/WidgetGraph/WidgetGraph.tsx | 14 +- .../LeftContainer/WidgetGraph/index.tsx | 6 +- .../LeftContainer/WidgetGraph/styles.ts | 26 +- .../src/container/QueryTable/QueryTable.tsx | 6 +- .../lib/query/createTableColumnsFromQuery.ts | 263 ++++++++++-------- 28 files changed, 532 insertions(+), 266 deletions(-) create mode 100644 frontend/src/constants/panelTypes.ts delete mode 100644 frontend/src/container/GridGraphComponent/index.tsx create mode 100644 frontend/src/container/GridPanelSwitch/index.tsx create mode 100644 frontend/src/container/GridPanelSwitch/types.ts create mode 100644 frontend/src/container/GridTableComponent/config.ts create mode 100644 frontend/src/container/GridTableComponent/index.tsx create mode 100644 frontend/src/container/GridTableComponent/styles.ts create mode 100644 frontend/src/container/GridTableComponent/types.ts create mode 100644 frontend/src/container/GridValueComponent/config.ts create mode 100644 frontend/src/container/GridValueComponent/index.tsx rename frontend/src/container/{GridGraphComponent => GridValueComponent}/styles.ts (100%) create mode 100644 frontend/src/container/GridValueComponent/types.ts diff --git a/frontend/src/components/Graph/index.tsx b/frontend/src/components/Graph/index.tsx index cd988286cd..621f57ebf7 100644 --- a/frontend/src/components/Graph/index.tsx +++ b/frontend/src/components/Graph/index.tsx @@ -343,7 +343,7 @@ type CustomChartOptions = ChartOptions & { }; }; -interface GraphProps { +export interface GraphProps { animate?: boolean; type: ChartType; data: Chart['data']; diff --git a/frontend/src/constants/panelTypes.ts b/frontend/src/constants/panelTypes.ts new file mode 100644 index 0000000000..811e06f0d0 --- /dev/null +++ b/frontend/src/constants/panelTypes.ts @@ -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; diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index 9ff71e1712..509b97340c 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -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 = { bool: Object.values(BoolOperators), }; -export const PANEL_TYPES: Record = { - 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'; diff --git a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx index f08d22df96..c27d69f8ba 100644 --- a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx +++ b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx @@ -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({ )} {chartDataSet && !queryResponse.isError && ( - )} diff --git a/frontend/src/container/FormAlertRules/index.tsx b/frontend/src/container/FormAlertRules/index.tsx index 59c8333f06..109e3ea9d5 100644 --- a/frontend/src/container/FormAlertRules/index.tsx +++ b/frontend/src/container/FormAlertRules/index.tsx @@ -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 => ( } + headline={ + + } name="" threshold={alertDef.condition?.target} query={stagedQuery} @@ -361,7 +368,12 @@ function FormAlertRules({ const renderPromChartPreview = (): JSX.Element => ( } + headline={ + + } name="Chart Preview" threshold={alertDef.condition?.target} query={stagedQuery} @@ -370,7 +382,12 @@ function FormAlertRules({ const renderChQueryChartPreview = (): JSX.Element => ( } + headline={ + + } name="Chart Preview" threshold={alertDef.condition?.target} query={stagedQuery} diff --git a/frontend/src/container/GridGraphComponent/index.tsx b/frontend/src/container/GridGraphComponent/index.tsx deleted file mode 100644 index 22fa25f98d..0000000000 --- a/frontend/src/container/GridGraphComponent/index.tsx +++ /dev/null @@ -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 ( - - ); - } - - if (GRAPH_TYPES === PANEL_TYPES.VALUE) { - const value = (((data.datasets[0] || []).data || [])[0] || 0) as number; - - if (data.datasets.length === 0) { - return ( - - No Data - - ); - } - - return ( - <> - - {title} - - - - - - ); - } - - 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; diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx index 3208517b86..b177de9722 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx @@ -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 && ( - + )} - ); diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts b/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts index 4fbc1a18c0..e2b3384af3 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts @@ -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` display: flex; justify-content: flex-end; + ${({ $panelType }): FlattenSimpleInterpolation => + $panelType === PANEL_TYPES.TABLE + ? css` + margin-bottom: 1rem; + ` + : css``} `; diff --git a/frontend/src/container/GridGraphLayout/Graph/index.tsx b/frontend/src/container/GridGraphLayout/Graph/index.tsx index 401530329c..1ee8cedeb8 100644 --- a/frontend/src/container/GridGraphLayout/Graph/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/index.tsx @@ -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} /> - )} @@ -308,8 +310,8 @@ function GridCardGraph({ allowEdit={allowEdit} /> - ) : ( @@ -363,8 +367,8 @@ function GridCardGraph({ {!isEmptyLayout && getModals()} {!isEmpty(widget) && !!queryResponse.data?.payload && ( - )} diff --git a/frontend/src/container/GridGraphLayout/GraphLayout.tsx b/frontend/src/container/GridGraphLayout/GraphLayout.tsx index 02cbb7ed6c..316b6877cc 100644 --- a/frontend/src/container/GridGraphLayout/GraphLayout.tsx +++ b/frontend/src/container/GridGraphLayout/GraphLayout.tsx @@ -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} > - + diff --git a/frontend/src/container/GridGraphLayout/styles.ts b/frontend/src/container/GridGraphLayout/styles.ts index 9bb4b219bb..ca54bd6140 100644 --- a/frontend/src/container/GridGraphLayout/styles.ts +++ b/frontend/src/container/GridGraphLayout/styles.ts @@ -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)` &&& { 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` + overflow: auto; + :hover { .react-resizable-handle { position: absolute; @@ -44,6 +58,7 @@ export const CardContainer = styled.div` background-image: ${(): string => `url("${uri}")`}; `; }} + } } `; diff --git a/frontend/src/container/GridPanelSwitch/index.tsx b/frontend/src/container/GridPanelSwitch/index.tsx new file mode 100644 index 0000000000..790ed404da --- /dev/null +++ b/frontend/src/container/GridPanelSwitch/index.tsx @@ -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 ; +} + +export default memo(GridPanelSwitch); diff --git a/frontend/src/container/GridPanelSwitch/types.ts b/frontend/src/container/GridPanelSwitch/types.ts new file mode 100644 index 0000000000..26dca8be31 --- /dev/null +++ b/frontend/src/container/GridPanelSwitch/types.ts @@ -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; +}; diff --git a/frontend/src/container/GridTableComponent/config.ts b/frontend/src/container/GridTableComponent/config.ts new file mode 100644 index 0000000000..fe9b8b8eb3 --- /dev/null +++ b/frontend/src/container/GridTableComponent/config.ts @@ -0,0 +1,9 @@ +import { TableProps } from 'antd'; +import { RowData } from 'lib/query/createTableColumnsFromQuery'; + +export const GRID_TABLE_CONFIG: Omit< + TableProps, + 'columns' | 'dataSource' +> = { + size: 'small', +}; diff --git a/frontend/src/container/GridTableComponent/index.tsx b/frontend/src/container/GridTableComponent/index.tsx new file mode 100644 index 0000000000..e3659c1c95 --- /dev/null +++ b/frontend/src/container/GridTableComponent/index.tsx @@ -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 ( + + + + ); +} + +export default memo(GridTableComponent); diff --git a/frontend/src/container/GridTableComponent/styles.ts b/frontend/src/container/GridTableComponent/styles.ts new file mode 100644 index 0000000000..29e4d7ad83 --- /dev/null +++ b/frontend/src/container/GridTableComponent/styles.ts @@ -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; + } +`; diff --git a/frontend/src/container/GridTableComponent/types.ts b/frontend/src/container/GridTableComponent/types.ts new file mode 100644 index 0000000000..cd8e446822 --- /dev/null +++ b/frontend/src/container/GridTableComponent/types.ts @@ -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, 'columns' | 'dataSource'>; diff --git a/frontend/src/container/GridValueComponent/config.ts b/frontend/src/container/GridValueComponent/config.ts new file mode 100644 index 0000000000..fa3386c522 --- /dev/null +++ b/frontend/src/container/GridValueComponent/config.ts @@ -0,0 +1,3 @@ +import { GridValueComponentProps } from './types'; + +export const GridValueConfig: Pick = {}; diff --git a/frontend/src/container/GridValueComponent/index.tsx b/frontend/src/container/GridValueComponent/index.tsx new file mode 100644 index 0000000000..73275bee90 --- /dev/null +++ b/frontend/src/container/GridValueComponent/index.tsx @@ -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 ( + + No Data + + ); + } + + return ( + <> + + {title} + + + + + + ); +} + +export default memo(GridValueComponent); diff --git a/frontend/src/container/GridGraphComponent/styles.ts b/frontend/src/container/GridValueComponent/styles.ts similarity index 100% rename from frontend/src/container/GridGraphComponent/styles.ts rename to frontend/src/container/GridValueComponent/styles.ts diff --git a/frontend/src/container/GridValueComponent/types.ts b/frontend/src/container/GridValueComponent/types.ts new file mode 100644 index 0000000000..cef4a69e30 --- /dev/null +++ b/frontend/src/container/GridValueComponent/types.ts @@ -0,0 +1,7 @@ +import { ChartData } from 'chart.js'; + +export type GridValueComponentProps = { + data: ChartData; + title?: string; + yAxisUnit?: string; +}; diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts b/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts index c17893dbc9..baadf921fe 100644 --- a/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts +++ b/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts @@ -14,6 +14,7 @@ const Items: ItemsProps[] = [ Icon: ValueIcon, display: 'Value', }, + { name: PANEL_TYPES.TABLE, Icon: TimeSeries, display: 'Table' }, ]; export type ITEMS = diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx index 6fc58de892..6359295fae 100644 --- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx @@ -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 ( -
+ Plotted using -
+ ); } diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraph.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraph.tsx index 3728daec4c..6f4430ec38 100644 --- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraph.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraph.tsx @@ -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( (state) => state.dashboards, ); @@ -39,7 +41,7 @@ function WidgetGraph({ return Invalid widget; } - const { title, opacity, isStacked } = selectedWidget; + const { title, opacity, isStacked, query } = selectedWidget; if (getWidgetQueryRange.error) { return ( @@ -66,14 +68,18 @@ function WidgetGraph({ }); return ( - ); } diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx index a30b7e9fe5..62cab5f861 100644 --- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx @@ -41,12 +41,12 @@ function WidgetGraph({ }); if (selectedWidget === undefined) { - return Invalid widget; + return Invalid widget; } return ( - - + + {getWidgetQueryRange.error && ( diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/styles.ts b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/styles.ts index 32a8c15f08..b3fd9fcb2d 100644 --- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/styles.ts +++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/styles.ts @@ -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)` &&& { 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` + margin-left: 2rem; + margin-top: ${({ $panelType }): string => + $panelType === PANEL_TYPES.TABLE ? '1rem' : '0'}; + + margin-bottom: ${({ $panelType }): string => + $panelType === PANEL_TYPES.TABLE ? '1rem' : '0'}; `; diff --git a/frontend/src/container/QueryTable/QueryTable.tsx b/frontend/src/container/QueryTable/QueryTable.tsx index 5ec9dd9121..6220540b9a 100644 --- a/frontend/src/container/QueryTable/QueryTable.tsx +++ b/frontend/src/container/QueryTable/QueryTable.tsx @@ -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 ( FORMULA_REGEXP.test(queryName); @@ -74,32 +78,31 @@ const getQueryByName = ( 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 = ( - // 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 = ( 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(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(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( + 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( - 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 >((acc, item) => { const column: ColumnType = { - 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)