Skip to content

Commit e119384

Browse files
authored
Generate Comparison Charts (#3500)
* PBENCH-1208 Generate comparison charts for uperf.
1 parent abfc995 commit e119384

File tree

12 files changed

+532
-116
lines changed

12 files changed

+532
-116
lines changed

dashboard/src/App.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,17 +96,16 @@ const App = () => {
9696
path={APP_ROUTES.ANALYSIS}
9797
element={<ComingSoonPage />}
9898
/>
99+
<Route
100+
path={APP_ROUTES.VISUALIZATION}
101+
element={<ComparisonComponent />}
102+
/>
99103
</Route>
100104
<Route
101105
path={APP_ROUTES.SEARCH}
102106
element={<ComingSoonPage />}
103107
/>
104-
<Route
105-
path={APP_ROUTES.COMPARISON}
106-
element={<ComparisonComponent />}
107-
/>
108108
</Route>
109-
110109
<Route path="*" element={<NoMatchingPage />} />
111110
</Route>
112111
</Routes>

dashboard/src/actions/comparisonActions.js

Lines changed: 126 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -37,28 +37,29 @@ export const getQuisbyData = (dataset) => async (dispatch, getState) => {
3737
dispatch(parseChartData());
3838
}
3939
} catch (error) {
40-
if (error?.response && error.response?.data) {
41-
const errorMsg = error.response.data?.message;
42-
const isUnsupportedType = errorMsg
40+
if (
41+
error?.response?.data &&
42+
error.response.data?.message
4343
?.toLowerCase()
44-
.includes("unsupported benchmark");
45-
if (isUnsupportedType) {
46-
dispatch({
47-
type: TYPES.IS_UNSUPPORTED_TYPE,
48-
payload: errorMsg,
49-
});
50-
}
44+
.includes("unsupported benchmark")
45+
) {
46+
dispatch({
47+
type: TYPES.IS_UNSUPPORTED_TYPE,
48+
payload: error.response.data.message,
49+
});
5150
} else {
5251
dispatch(showToast(DANGER, ERROR_MSG));
5352
}
5453
dispatch({ type: TYPES.NETWORK_ERROR });
5554
}
5655
dispatch({ type: TYPES.COMPLETED });
5756
};
58-
57+
const COLORS = ["#8BC1F7", "#0066CC", "#519DE9", "#004B95", "#002F5D"];
5958
export const parseChartData = () => (dispatch, getState) => {
6059
const response = getState().comparison.data.data;
60+
const isCompareSwitchChecked = getState().comparison.isCompareSwitchChecked;
6161
const chartData = [];
62+
let i = 0;
6263

6364
for (const run of response) {
6465
const options = {
@@ -97,26 +98,130 @@ export const parseChartData = () => (dispatch, getState) => {
9798
},
9899
};
99100

100-
const datasets = [
101-
{
102-
label: run.instances[0].dataset_name,
103-
data: run.instances.map((i) => i.time_taken),
104-
backgroundColor: "#8BC1F7",
105-
},
106-
];
107-
101+
const datasets = [];
108102
const data = {
109-
labels: run.instances.map((i) => i.name),
103+
labels: [...new Set(run.instances.map((i) => i.name))],
110104
id: `${run.test_name}_${run.metrics_unit}`,
111105
datasets,
112106
};
107+
const result = run.instances.reduce(function (r, a) {
108+
r[a.dataset_name] = r[a.dataset_name] || [];
109+
r[a.dataset_name].push(a);
110+
return r;
111+
}, Object.create(null));
112+
113+
for (const [key, value] of Object.entries(result)) {
114+
console.log(key);
115+
116+
const map = {};
117+
for (const element of value) {
118+
map[element.name] = element.time_taken.trim();
119+
}
120+
const mappedData = data.labels.map((label) => {
121+
return map[label];
122+
});
123+
const obj = { label: key, backgroundColor: COLORS[i], data: mappedData };
124+
i++;
125+
datasets.push(obj);
126+
}
113127

114128
const obj = { options, data };
115129
chartData.push(obj);
130+
i = 0;
116131
}
132+
const type = isCompareSwitchChecked
133+
? TYPES.SET_COMPARE_DATA
134+
: TYPES.SET_PARSED_DATA;
117135

118136
dispatch({
119-
type: TYPES.SET_PARSED_DATA,
137+
type,
120138
payload: chartData,
121139
});
122140
};
141+
142+
export const toggleCompareSwitch = () => (dispatch, getState) => {
143+
dispatch({
144+
type: TYPES.TOGGLE_COMPARE_SWITCH,
145+
payload: !getState().comparison.isCompareSwitchChecked,
146+
});
147+
};
148+
149+
export const setSelectedId = (isChecked, rId) => (dispatch, getState) => {
150+
let selectedIds = [...getState().comparison.selectedResourceIds];
151+
if (isChecked) {
152+
selectedIds = [...selectedIds, rId];
153+
} else {
154+
selectedIds = selectedIds.filter((item) => item !== rId);
155+
}
156+
dispatch({
157+
type: TYPES.SET_SELECTED_RESOURCE_ID,
158+
payload: selectedIds,
159+
});
160+
};
161+
162+
export const compareMultipleDatasets = () => async (dispatch, getState) => {
163+
try {
164+
dispatch({ type: TYPES.LOADING });
165+
166+
const endpoints = getState().apiEndpoint.endpoints;
167+
const selectedIds = [...getState().comparison.selectedResourceIds];
168+
169+
const params = new URLSearchParams();
170+
params.append("datasets", selectedIds.toString());
171+
const response = await API.get(
172+
uriTemplate(endpoints, "datasets_compare", {}),
173+
{ params }
174+
);
175+
if (response.status === 200 && response.data.json_data) {
176+
dispatch({
177+
type: TYPES.SET_QUISBY_DATA,
178+
payload: response.data.json_data,
179+
});
180+
dispatch({
181+
type: TYPES.UNMATCHED_BENCHMARK_TYPES,
182+
payload: "",
183+
});
184+
dispatch(parseChartData());
185+
}
186+
} catch (error) {
187+
if (
188+
error?.response?.data &&
189+
error.response.data?.message
190+
?.toLowerCase()
191+
.includes("benchmarks must match")
192+
) {
193+
dispatch({
194+
type: TYPES.UNMATCHED_BENCHMARK_TYPES,
195+
payload: error.response.data.message,
196+
});
197+
} else {
198+
dispatch(showToast(DANGER, ERROR_MSG));
199+
}
200+
dispatch({ type: TYPES.NETWORK_ERROR });
201+
}
202+
dispatch({ type: TYPES.COMPLETED });
203+
};
204+
205+
export const setChartModalContent = (chartId) => (dispatch, getState) => {
206+
const isCompareSwitchChecked = getState().comparison.isCompareSwitchChecked;
207+
const data = isCompareSwitchChecked
208+
? getState().comparison.compareChartData
209+
: getState().comparison.chartData;
210+
211+
const activeChart = data.filter((item) => item.data.id === chartId)[0];
212+
213+
dispatch({
214+
type: TYPES.SET_CURRENT_CHARTID,
215+
payload: activeChart,
216+
});
217+
};
218+
219+
export const setChartModal = (isOpen) => ({
220+
type: TYPES.SET_CHART_MODAL,
221+
payload: isOpen,
222+
});
223+
224+
export const setSearchValue = (value) => ({
225+
type: TYPES.SET_SEARCH_VALUE,
226+
payload: value,
227+
});

dashboard/src/actions/types.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,10 @@ export const SET_QUISBY_DATA = "SET_QUISBY_DATA";
6565
export const SET_PARSED_DATA = "SET_PARSED_DATA";
6666
export const SET_ACTIVE_RESOURCEID = "SET_ACTIVE_RESOURCEID";
6767
export const IS_UNSUPPORTED_TYPE = "IS_UNSUPPORTED_TYPE";
68+
export const TOGGLE_COMPARE_SWITCH = "TOGGLE_COMPARE_SWITCH";
69+
export const SET_SELECTED_RESOURCE_ID = "SET_SELECTED_RESOURCE_ID";
70+
export const UNMATCHED_BENCHMARK_TYPES = "UNMATCHED_BENCHMARK_TYPES";
71+
export const SET_CHART_MODAL = "SET_CHART_MODAL";
72+
export const SET_CURRENT_CHARTID = "SET_CURRENT_CHARTID";
73+
export const SET_COMPARE_DATA = "SET_COMPARE_DATA";
74+
export const SET_SEARCH_VALUE = "SET_SEARCH_VALUE";

dashboard/src/modules/components/ComparisonComponent/ChartGallery.jsx

Lines changed: 41 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,13 @@ import {
77
Title,
88
Tooltip,
99
} from "chart.js";
10-
import {
11-
Card,
12-
EmptyState,
13-
EmptyStateBody,
14-
EmptyStateVariant,
15-
Gallery,
16-
GalleryItem,
17-
} from "@patternfly/react-core";
10+
import { Card, Gallery, GalleryItem } from "@patternfly/react-core";
11+
import { setChartModal, setChartModalContent } from "actions/comparisonActions";
1812

1913
import { Bar } from "react-chartjs-2";
14+
import { ExpandArrowsAltIcon } from "@patternfly/react-icons";
2015
import React from "react";
21-
import { useSelector } from "react-redux";
16+
import { useDispatch } from "react-redux";
2217

2318
ChartJS.register(
2419
BarElement,
@@ -28,55 +23,46 @@ ChartJS.register(
2823
CategoryScale,
2924
LinearScale
3025
);
31-
const EmptyStateExtraSmall = (props) => (
32-
<EmptyState variant={EmptyStateVariant.xs}>
33-
<div>{props.message}</div>
34-
<EmptyStateBody>Benchmark type is currently unsupported!</EmptyStateBody>
35-
</EmptyState>
36-
);
37-
const ChartGallery = () => {
38-
const { chartData, unsupportedType } = useSelector(
39-
(state) => state.comparison
40-
);
26+
27+
const ChartGallery = (props) => {
28+
const dispatch = useDispatch();
29+
30+
const handleExpandClick = (chartId) => {
31+
dispatch(setChartModal(true));
32+
dispatch(setChartModalContent(chartId));
33+
};
4134
return (
4235
<>
43-
{unsupportedType ? (
44-
<EmptyStateExtraSmall message={unsupportedType} />
45-
) : (
46-
<>
47-
{chartData && chartData.length > 0 && (
48-
<Gallery
49-
className="chart-wrapper"
50-
hasGutter
51-
minWidths={{
52-
default: "100%",
53-
md: "30rem",
54-
xl: "35rem",
55-
}}
56-
maxWidths={{
57-
md: "40rem",
58-
xl: "1fr",
59-
}}
60-
>
61-
{chartData.map((chart) => (
62-
<Card
63-
className="chart-card"
64-
isRounded
65-
isLarge
66-
key={chart.data.id}
36+
{props.dataToPlot && props.dataToPlot.length > 0 && (
37+
<Gallery
38+
className="chart-wrapper"
39+
hasGutter
40+
minWidths={{
41+
default: "100%",
42+
md: "30rem",
43+
xl: "35rem",
44+
}}
45+
maxWidths={{
46+
md: "40rem",
47+
xl: "1fr",
48+
}}
49+
>
50+
{props.dataToPlot.map((chart) => (
51+
<Card className="chart-card" isRounded isLarge key={chart.data.id}>
52+
<div className="expand-icon-container">
53+
<div
54+
className="icon-wrapper"
55+
onClick={() => handleExpandClick(chart.data.id)}
6756
>
68-
<GalleryItem className="galleryItem chart-holder">
69-
<Bar
70-
options={chart.options}
71-
data={chart.data}
72-
width={450}
73-
/>
74-
</GalleryItem>
75-
</Card>
76-
))}
77-
</Gallery>
78-
)}
79-
</>
57+
<ExpandArrowsAltIcon />
58+
</div>
59+
</div>
60+
<GalleryItem className="galleryItem chart-holder">
61+
<Bar options={chart.options} data={chart.data} width={450} />
62+
</GalleryItem>
63+
</Card>
64+
))}
65+
</Gallery>
8066
)}
8167
</>
8268
);

0 commit comments

Comments
 (0)