Skip to content

Commit bae88c4

Browse files
Merge pull request #485 from jjaquish/jjaquish/importance-by-component-widget
Initial draft commit for importance widget component
2 parents 80f23de + 8d17fea commit bae88c4

File tree

7 files changed

+301
-1
lines changed

7 files changed

+301
-1
lines changed

backend/ibutsu_server/constants.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,49 @@
229229
],
230230
"type": "widget",
231231
},
232+
"importance-component": {
233+
"id": "importance-component",
234+
"title": "Importance by component",
235+
"description": "Test results filtered by component and broken down by importance",
236+
"params": [
237+
{
238+
"name": "job_name",
239+
"description": "The name of the jenkins job to pull from",
240+
"type": "string",
241+
"required": True,
242+
"default": "",
243+
},
244+
{
245+
"name": "group_field",
246+
"description": "the field in a result to group by, typically 'component'",
247+
"type": "string",
248+
"required": True,
249+
"default": "component",
250+
},
251+
{
252+
"name": "env",
253+
"description": "The environment to filter by",
254+
"type": "string",
255+
"required": False,
256+
"default": "",
257+
},
258+
{
259+
"name": "components",
260+
"description": "The component(s) to filter by",
261+
"type": "string",
262+
"required": True,
263+
"default": "",
264+
},
265+
{
266+
"name": "builds",
267+
"description": "The number of Jenkins builds to analyze.",
268+
"type": "integer",
269+
"default": 5,
270+
"required": False,
271+
},
272+
],
273+
"type": "widget",
274+
},
232275
"accessibility-dashboard-view": {
233276
"id": "accessibility-dashboard-view",
234277
"title": "Accessibility Dashboard View",

backend/ibutsu_server/controllers/widget_controller.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from ibutsu_server.widgets.accessibility_dashboard_view import get_accessibility_dashboard_view
77
from ibutsu_server.widgets.compare_runs_view import get_comparison_data
88
from ibutsu_server.widgets.filter_heatmap import get_filter_heatmap
9+
from ibutsu_server.widgets.importance_component import get_importance_component
910
from ibutsu_server.widgets.jenkins_heatmap import get_jenkins_heatmap
1011
from ibutsu_server.widgets.jenkins_job_analysis import get_jenkins_analysis_data
1112
from ibutsu_server.widgets.jenkins_job_analysis import get_jenkins_bar_chart
@@ -24,6 +25,7 @@
2425
"jenkins-bar-chart": get_jenkins_bar_chart,
2526
"jenkins-heatmap": get_jenkins_heatmap,
2627
"filter-heatmap": get_filter_heatmap,
28+
"importance-component": get_importance_component,
2729
"jenkins-job-view": get_jenkins_job_view,
2830
"jenkins-line-chart": get_jenkins_line_chart,
2931
"run-aggregator": get_recent_run_data,
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from ibutsu_server.constants import BARCHART_MAX_BUILDS
2+
from ibutsu_server.constants import JJV_RUN_LIMIT
3+
from ibutsu_server.db.models import Result
4+
from ibutsu_server.db.models import Run
5+
from ibutsu_server.filters import string_to_column
6+
from ibutsu_server.widgets.jenkins_job_view import get_jenkins_job_view
7+
8+
9+
def get_importance_component(
10+
env="prod", group_field="component", job_name="", builds=5, components="", project=None
11+
):
12+
# taken from get_jenkins_line_chart in jenkins_job_analysis.py
13+
run_limit = int((JJV_RUN_LIMIT / BARCHART_MAX_BUILDS) * builds)
14+
jobs = get_jenkins_job_view(
15+
filter_=f"job_name={job_name}", page_size=builds, project=project, run_limit=run_limit
16+
).get("jobs")
17+
18+
# A list of job build numbers to filter our runs by
19+
job_ids = []
20+
for job in jobs:
21+
job_ids.append(job["build_number"])
22+
23+
# query for RUN ids
24+
# metadata has to have a string to column to work
25+
# because it is a sqlalchemy property otherwise (AFAIK)
26+
bnumdat = string_to_column("metadata.jenkins.build_number", Run)
27+
run_data = (
28+
Run.query.filter(bnumdat.in_(job_ids), Run.component.in_(components.split(",")))
29+
.add_columns(Run.id, bnumdat.label("build_number"))
30+
.all()
31+
)
32+
33+
# get a list of the job IDs
34+
run_info = {}
35+
for run in run_data:
36+
run_info[run.id] = run.build_number
37+
38+
mdat = string_to_column("metadata.importance", Result)
39+
result_data = (
40+
Result.query.filter(
41+
Result.run_id.in_(run_info.keys()), Result.component.in_(components.split(","))
42+
)
43+
.add_columns(
44+
Result.run_id, Result.component, Result.id, Result.result, mdat.label("importance")
45+
)
46+
.all()
47+
)
48+
49+
"""
50+
This starts a (probably) over complicated bit of data maniplation
51+
to get sdatdict in a proper state to be broken down into
52+
sdatret, which is the format we need for the widget.
53+
"""
54+
sdatdict = {}
55+
bnums = set()
56+
importances = ["critical", "high", "medium", "low"]
57+
for datum in result_data:
58+
# getting the components from the results
59+
if datum.component not in sdatdict.keys():
60+
sdatdict[datum.component] = {}
61+
62+
# getting the build numbers from the results
63+
if run_info[datum.run_id] not in sdatdict[datum.component].keys():
64+
bnums.add(run_info[datum.run_id])
65+
sdatdict[datum.component][run_info[datum.run_id]] = {}
66+
67+
# Adding all importances from our constant
68+
if datum.importance not in sdatdict[datum.component][run_info[datum.run_id]].keys():
69+
sdatdict[datum.component][run_info[datum.run_id]][datum.importance] = []
70+
# adding the result value
71+
sdatdict[datum.component][run_info[datum.run_id]][datum.importance].append(
72+
{"result": datum.result, "result_id": datum.id}
73+
)
74+
75+
# This adds the extra importance values that didn't appear in the results
76+
for component in sdatdict.keys():
77+
for bnum in sdatdict[component].keys():
78+
for importance in importances:
79+
if importance not in sdatdict[component][bnum].keys():
80+
sdatdict[component][bnum][importance] = []
81+
82+
# this is to change result values into numbers
83+
# TODO: This doesn't handle xpassed, xfailed, skipped, etc. so figure that out
84+
for component in sdatdict.keys():
85+
for bnum in sdatdict[component].keys():
86+
for importance in sdatdict[component][bnum].keys():
87+
total = 0
88+
passed = 0
89+
res_list = []
90+
for item in sdatdict[component][bnum][importance]:
91+
total += 1
92+
res_list.append(item["result_id"])
93+
if item["result"] == "passed":
94+
passed += 1
95+
96+
if total != 0:
97+
sdatdict[component][bnum][importance] = {
98+
"percentage": round(passed / total, 2),
99+
"result_list": res_list,
100+
}
101+
else:
102+
sdatdict[component][bnum][importance] = {
103+
"percentage": 0,
104+
"result_list": res_list,
105+
}
106+
107+
for bnum in bnums:
108+
if bnum not in sdatdict[component].keys():
109+
sdatdict[component][bnum] = {}
110+
for importance in importances:
111+
sdatdict[component][bnum][importance] = {"percentage": "NA", "result_list": []}
112+
113+
# Need this broken down more for the table
114+
table_data = []
115+
for key in sdatdict.keys():
116+
table_data.append(
117+
{
118+
"component": key,
119+
"bnums": sorted(list(bnums)),
120+
"importances": importances,
121+
"data": sdatdict[key],
122+
}
123+
)
124+
125+
# return data, for sanity
126+
data = {"table_data": table_data}
127+
return data

frontend/src/constants.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ export const KNOWN_WIDGETS = [
99
'result-summary',
1010
'result-aggregator',
1111
'jenkins-bar-chart',
12-
'jenkins-line-chart'
12+
'jenkins-line-chart',
13+
'importance-component'
1314
];
1415
export const STRING_OPERATIONS = {
1516
'eq': '=',

frontend/src/dashboard.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
GenericAreaWidget,
3636
GenericBarWidget,
3737
FilterHeatmapWidget,
38+
ImportanceComponentWidget,
3839
ResultAggregatorWidget,
3940
ResultSummaryWidget
4041
} from './widgets';
@@ -411,6 +412,18 @@ export class Dashboard extends React.Component {
411412
onEditClick={() => this.onEditWidgetClick(widget.id)}
412413
/>
413414
}
415+
{(widget.type === "widget" && widget.widget === "importance-component") &&
416+
<ImportanceComponentWidget
417+
title={widget.title}
418+
params={widget.params}
419+
barWidth={20}
420+
horizontal={true}
421+
hideDropdown={true}
422+
widgetEndpoint="importance-component"
423+
onDeleteClick={() => this.onDeleteWidgetClick(widget.id)}
424+
onEditClick={() => this.onEditWidgetClick(widget.id)}
425+
/>
426+
}
414427
</GridItem>
415428
);
416429
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import {
5+
Card,
6+
CardBody,
7+
Text
8+
} from '@patternfly/react-core';
9+
10+
import {
11+
Table,
12+
Thead,
13+
Th,
14+
Tbody,
15+
Tr,
16+
Td
17+
} from '@patternfly/react-table';
18+
19+
import { Link } from 'react-router-dom';
20+
21+
import { HttpClient } from '../services/http';
22+
import { Settings } from '../settings';
23+
import { WidgetHeader } from '../components/widget-components';
24+
25+
export class ImportanceComponentWidget extends React.Component {
26+
static propTypes = {
27+
title: PropTypes.string,
28+
params: PropTypes.object,
29+
onDeleteClick: PropTypes.func,
30+
onEditClick: PropTypes.func
31+
}
32+
33+
constructor(props) {
34+
super(props);
35+
this.title = props.title || 'Importance Component Widget';
36+
this.params = props.params || {};
37+
this.state = {
38+
data: {
39+
table_data: []
40+
},
41+
isLoading: true,
42+
};
43+
}
44+
45+
getData = () => {
46+
this.setState({isLoading: true})
47+
HttpClient.get([Settings.serverUrl, 'widget', 'importance-component'], this.params)
48+
.then(response => {
49+
response = HttpClient.handleResponse(response, 'response');
50+
if (!response.ok) {
51+
throw Error(response.statusText);
52+
}
53+
return response.json();
54+
})
55+
.then(data => this.setState({data: data, isLoading: false}))
56+
.catch(error => {
57+
this.setState({dataError: true});
58+
console.log(error);
59+
});
60+
}
61+
62+
componentDidMount() {
63+
this.getData();
64+
}
65+
66+
componentDidUpdate(prevProps) {
67+
if (prevProps.params !== this.props.params) {
68+
this.params = this.props.params;
69+
this.getData();
70+
}
71+
}
72+
73+
render() {
74+
return (
75+
<Card>
76+
<WidgetHeader title={this.title} getDataFunc={this.getData} onEditClick={this.props.onEditClick} onDeleteClick={this.props.onDeleteClick}/>
77+
{(!this.state.dataError && this.state.isLoading) &&
78+
<CardBody>
79+
<Text component="h2">Loading ...</Text>
80+
</CardBody>
81+
}
82+
{(!this.state.dataError && !this.state.isLoading) &&
83+
<CardBody>
84+
{this.state.data.table_data.map((tdat) => (
85+
<>
86+
<Text key={tdat.component} component="h2">{tdat.component}</Text>
87+
<Table aria-label="importance-component-table" variant="compact">
88+
<Thead>
89+
<Tr>
90+
{["-", ...tdat.bnums].map((buildnum) => (
91+
<Th key={buildnum}>{buildnum}</Th>
92+
))}
93+
</Tr>
94+
</Thead>
95+
<Tbody>
96+
{tdat.importances.map((importance) => (
97+
<Tr key={importance}>
98+
<Text component="h2">{importance}</Text>
99+
{tdat.bnums.map((buildnum) => (
100+
<Td key={buildnum}><Link to={`/results?id[in]=${tdat.data[buildnum][importance]["result_list"].join(";")}`}>{tdat.data[buildnum][importance]["percentage"]}</Link></Td>
101+
))}
102+
</Tr>
103+
))}
104+
</Tbody>
105+
</Table>
106+
</>
107+
))}
108+
</CardBody>
109+
}
110+
</Card>
111+
);
112+
}
113+
}

frontend/src/widgets/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export { GenericAreaWidget } from './genericarea';
33
export { GenericBarWidget } from './genericbar';
44
export { FilterHeatmapWidget } from './filterheatmap';
55
export { ResultAggregatorWidget } from './resultaggregator';
6+
export { ImportanceComponentWidget } from './importancecomponent';

0 commit comments

Comments
 (0)