Skip to content
This repository has been archived by the owner on Feb 18, 2023. It is now read-only.

Introduce collapsible blocks for charts #92

Merged
merged 1 commit into from
Jun 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions kubernetes/v1alpha1/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,19 @@ type MonitoringDashboardItem struct {
}

type MonitoringDashboardChart struct {
Name string `json:"name"`
Unit string `json:"unit"` // Stands for the base unit (regardless its scale in datasource)
UnitScale float64 `json:"unitScale"` // Stands for the scale of the values in datasource, related to the base unit provided. E.g. unit: "seconds" and unitScale: 0.001 means that values in datasource are actually in milliseconds.
Spans int `json:"spans"`
ChartType *string `json:"chartType"`
Min *int `json:"min"`
Max *int `json:"max"`
MetricName string `json:"metricName"` // Deprecated; use Metrics instead
Metrics []MonitoringDashboardMetric `json:"metrics"`
DataType string `json:"dataType"` // DataType is either "raw", "rate" or "histogram"
Aggregator string `json:"aggregator"` // Aggregator can be set for raw data. Ex: "sum", "avg". See https://prometheus.io/docs/prometheus/latest/querying/operators/#aggregation-operators
Aggregations []MonitoringDashboardAggregation `json:"aggregations"`
Name string `json:"name"`
Unit string `json:"unit"` // Stands for the base unit (regardless its scale in datasource)
UnitScale float64 `json:"unitScale"` // Stands for the scale of the values in datasource, related to the base unit provided. E.g. unit: "seconds" and unitScale: 0.001 means that values in datasource are actually in milliseconds.
Spans int `json:"spans"`
StartCollapsed bool `json:"startCollapsed"`
ChartType *string `json:"chartType"`
Min *int `json:"min"`
Max *int `json:"max"`
MetricName string `json:"metricName"` // Deprecated; use Metrics instead
Metrics []MonitoringDashboardMetric `json:"metrics"`
DataType string `json:"dataType"` // DataType is either "raw", "rate" or "histogram"
Aggregator string `json:"aggregator"` // Aggregator can be set for raw data. Ex: "sum", "avg". See https://prometheus.io/docs/prometheus/latest/querying/operators/#aggregation-operators
Aggregations []MonitoringDashboardAggregation `json:"aggregations"`
}

type MonitoringDashboardMetric struct {
Expand Down
32 changes: 17 additions & 15 deletions model/dashboards.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ type MonitoringDashboard struct {

// Chart is the model representing a custom chart, transformed from charts in MonitoringDashboard k8s resource
type Chart struct {
Name string `json:"name"`
Unit string `json:"unit"`
Spans int `json:"spans"`
ChartType *string `json:"chartType,omitempty"`
Min *int `json:"min,omitempty"`
Max *int `json:"max,omitempty"`
Metrics []*SampleStream `json:"metrics"`
Error string `json:"error"`
Name string `json:"name"`
Unit string `json:"unit"`
Spans int `json:"spans"`
StartCollapsed bool `json:"startCollapsed"`
ChartType *string `json:"chartType,omitempty"`
Min *int `json:"min,omitempty"`
Max *int `json:"max,omitempty"`
Metrics []*SampleStream `json:"metrics"`
Error string `json:"error"`
}

// BuildLabelsMap initiates a labels map out of a given metric name and optionally histogram stat
Expand Down Expand Up @@ -128,13 +129,14 @@ func convertSamplePair(from *pmod.SamplePair, scale float64) SamplePair {
// ConvertChart converts a k8s chart (from MonitoringDashboard k8s resource) into this models chart
func ConvertChart(from v1alpha1.MonitoringDashboardChart) Chart {
return Chart{
Name: from.Name,
Unit: from.Unit,
Spans: from.Spans,
ChartType: from.ChartType,
Min: from.Min,
Max: from.Max,
Metrics: []*SampleStream{},
Name: from.Name,
Unit: from.Unit,
Spans: from.Spans,
StartCollapsed: from.StartCollapsed,
ChartType: from.ChartType,
Min: from.Min,
Max: from.Max,
Metrics: []*SampleStream{},
}
}

Expand Down
1 change: 1 addition & 0 deletions web/common/types/Dashboards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface ChartModel {
max?: number;
metrics: TimeSeries[];
error?: string;
startCollapsed: boolean;
}

export interface AggregationModel {
Expand Down
7 changes: 6 additions & 1 deletion web/pf4/src/components/KChart.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,9 @@ storiesOf('PF4 KChart', module)
))
.add('with error', () => (
<KChart chart={error} data={getDataSupplier(empty, emptyLabels, colors)!()} />
));
))
.add('start collapsed', () => {
reset();
const chart = { ...metric, startCollapsed: true };
return <KChart chart={chart} data={getDataSupplier(metric, emptyLabels, colors)!()} />;
});
109 changes: 61 additions & 48 deletions web/pf4/src/components/KChart.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from 'react';
import { style } from 'typestyle';
import { Button, Text, TextContent, TextVariants } from '@patternfly/react-core';
import { Button, EmptyState, EmptyStateIcon, EmptyStateBody, Expandable } from '@patternfly/react-core';
import { ChartArea, ChartBar, ChartScatter, ChartLine } from '@patternfly/react-charts';
import { ExpandArrowsAltIcon, ErrorCircleOIcon } from '@patternfly/react-icons';
import { CubesIcon, ExpandArrowsAltIcon, ErrorCircleOIcon } from '@patternfly/react-icons';

import { ChartModel } from '../../../common/types/Dashboards';
import { VCLines, VCDataPoint } from '../types/VictoryChartInfo';
Expand Down Expand Up @@ -40,7 +40,18 @@ const noMetricsStyle = style({
}
});

class KChart extends React.Component<KChartProps, {}> {
type State = {
collapsed: boolean
};

class KChart extends React.Component<KChartProps, State> {
constructor(props: KChartProps) {
super(props);
this.state = {
collapsed: this.props.chart.startCollapsed || (!this.props.chart.error && this.isEmpty(this.props.data))
};
}

onExpandHandler = () => {
this.props.expandHandler!();
}
Expand All @@ -56,12 +67,25 @@ class KChart extends React.Component<KChartProps, {}> {
}

render() {
if (this.props.chart.error) {
return this.renderError();
} else if (this.isEmpty(this.props.data)) {
return this.renderEmpty();
}
return (
<Expandable
toggleText={this.props.chart.name}
onToggle={() => {
this.setState({ collapsed: !this.state.collapsed });
}}
isExpanded={!this.state.collapsed}
>
{this.props.chart.error ? this.renderError()
: (this.isEmpty(this.props.data) ? this.renderEmpty()
: this.renderChart())}
</Expandable>
);
}

renderChart() {
if (this.state.collapsed) {
return undefined;
}
let fill = false;
let stroke = true;
let seriesComponent = (<ChartLine/>);
Expand All @@ -84,58 +108,47 @@ class KChart extends React.Component<KChartProps, {}> {
const maxDomain = this.props.chart.max === undefined ? undefined : { y: this.props.chart.max };

return (
<>
<TextContent>
<Text component={TextVariants.h4} style={{textAlign: 'center'}}>{this.props.chart.name}</Text>
</TextContent>
<ChartWithLegend
data={this.props.data}
seriesComponent={seriesComponent}
fill={fill}
stroke={stroke}
groupOffset={groupOffset}
overlay={this.props.overlay}
unit={this.props.chart.unit}
moreChartProps={{ minDomain: minDomain, maxDomain: maxDomain }}
onClick={this.props.onClick}
brushHandlers={this.props.brushHandlers}
timeWindow={this.props.timeWindow}
/>
</>
<ChartWithLegend
data={this.props.data}
seriesComponent={seriesComponent}
fill={fill}
stroke={stroke}
groupOffset={groupOffset}
overlay={this.props.overlay}
unit={this.props.chart.unit}
moreChartProps={{ minDomain: minDomain, maxDomain: maxDomain }}
onClick={this.props.onClick}
brushHandlers={this.props.brushHandlers}
timeWindow={this.props.timeWindow}
/>
);
}

private isEmpty(data: VCLines): boolean {
return !data.some(s => s.datapoints.length !== 0);
}

private renderNoMetric(jsx: JSX.Element) {
private renderEmpty() {
return (
<div className={noMetricsStyle}>
<TextContent>
<Text component={TextVariants.h4}>
{this.props.chart.name}
</Text>
</TextContent>
<TextContent style={{paddingTop: 20, paddingBottom: 30}}>
<Text component={TextVariants.h5}>{jsx}</Text>
</TextContent>
</div>
<EmptyState>
<EmptyStateIcon icon={CubesIcon} />
<EmptyStateBody>No data available</EmptyStateBody>
</EmptyState>
);
}

private renderEmpty() {
return this.renderNoMetric(<>No data available</>);
}

private renderError() {
return this.renderNoMetric((
<>
<ErrorCircleOIcon style={{color: '#cc0000', marginRight: 5}} />
An error occured while fetching this metric:
<p><i>{this.props.chart.error}</i></p>
</>
));
return (
<EmptyState>
<EmptyStateIcon icon={() => (
<ErrorCircleOIcon style={{color: '#cc0000'}} width={32} height={32} />
)} />
<EmptyStateBody>
An error occured while fetching this metric:
<p><i>{this.props.chart.error}</i></p>
</EmptyStateBody>
</EmptyState>
);
}
}

Expand Down
24 changes: 16 additions & 8 deletions web/pf4/src/types/__mocks__/Charts.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ export const generateRandomScatterChart = (title: string, names: string[], spans
unit: 'seconds',
chartType: 'scatter',
spans: spans,
metrics: genSeries(names.map(n => genLabels(n)))
metrics: genSeries(names.map(n => genLabels(n))),
startCollapsed: false
};
};

Expand All @@ -66,7 +67,8 @@ export const generateRandomMetricChartWithLabels = (title: string, labels: Label
name: title,
unit: 'bytes',
spans: spans,
metrics: genSeries(labels)
metrics: genSeries(labels),
startCollapsed: false
};
};

Expand All @@ -92,7 +94,8 @@ export const generateRandomHistogramChart = (title: string, spans: SpanValue, se
name: title,
unit: 'bitrate',
spans: spans,
metrics: histo
metrics: histo,
startCollapsed: false
};
};

Expand All @@ -104,15 +107,17 @@ export const empty: ChartModel = {
name: 'Empty metric chart',
unit: 'bytes',
spans: 6,
metrics: []
metrics: [],
startCollapsed: false
};

export const error: ChartModel = {
name: 'Chart with error',
unit: 'bytes',
spans: 6,
metrics: [],
error: 'Unable to fetch metrics'
error: 'Unable to fetch metrics',
startCollapsed: false
};

export const metric: ChartModel = {
Expand All @@ -122,7 +127,8 @@ export const metric: ChartModel = {
metrics: [{
values: [[t0, 50.4], [t0 + increment, 48.2], [t0 + 2 * increment, 42.0]],
labelSet: genLabels('Metric chart')
}]
}],
startCollapsed: false
};

export const histogram: ChartModel = {
Expand All @@ -136,7 +142,8 @@ export const histogram: ChartModel = {
values: [[t0, 150.4], [t0 + increment, 148.2], [t0 + 2 * increment, 142.0]],
labelSet: genLabels('Metric chart', '0.99')
}
]
],
startCollapsed: false
};

export const emptyLabels: LabelsInfo = {
Expand Down Expand Up @@ -177,7 +184,8 @@ export const metricWithLabels: ChartModel = {
}, {
values: [[0, 0]],
labelSet: {'code': 'foobaz'}
}]
}],
startCollapsed: false
};

export const genDates = (n: number): Date[] => {
Expand Down
8 changes: 4 additions & 4 deletions web/pf4/src/types/__mocks__/Dashboards.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ export const generateRandomDashboard = (title: string, seed?: string): Dashboard
genLabels('oranges', undefined, [['color', 'orange'], ['size', 'small']]),
genLabels('bananas', undefined, [['color', 'yellow'], ['size', 'medium']])
], 6),
{ name: 'empty 1', unit: 'bytes', spans: 6, metrics: [] },
{ name: 'empty 2', unit: 'bytes', spans: 6, metrics: [] },
{ name: 'error 1', unit: 'bytes', spans: 6, metrics: [], error: 'Unexpected error occurred ... blah blah blah' },
{ name: 'error 2', unit: 'bytes', spans: 6, metrics: [], error: 'Unexpected error occurred ... blah blah blah' },
{ name: 'empty 1', unit: 'bytes', spans: 6, metrics: [], startCollapsed: false },
{ name: 'empty 2', unit: 'bytes', spans: 6, metrics: [], startCollapsed: false },
{ name: 'error 1', unit: 'bytes', spans: 6, metrics: [], startCollapsed: false, error: 'Unexpected error occurred ... blah blah blah' },
{ name: 'error 2', unit: 'bytes', spans: 6, metrics: [], startCollapsed: false, error: 'Unexpected error occurred ... blah blah blah' },
generateRandomMetricChart('Best animal++', ['dogs', 'cats', 'birds', 'stunning animal with very very long name that you\'ve never about', 'mermaids', 'escherichia coli', 'wohlfahrtiimonas', 'Chuck Norris'], 4),
generateRandomMetricChart('Best fruit++', ['apples', 'oranges', 'bananas', 'peaches', 'peers', 'cherries', 'leetchies', 'pineapple'], 4),
generateRandomScatterChart('Best traces++', ['apples', 'oranges', 'bananas', 'peaches', 'peers', 'cherries', 'leetchies', 'pineapple'], 4),
Expand Down
3 changes: 2 additions & 1 deletion web/pf4/src/utils/__tests__/victoryChartsUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ describe('Victory Charts Utils', () => {
metrics: [{
values: [[1, 1], [2, 2], [3, NaN], [4, 4]],
labelSet: {}
}]
}],
startCollapsed: false
};

const res = getDataSupplier(withNaN, emptyLabels, colors)!();
Expand Down