Coverage for testrail_api_reporter/engines/plotly_reporter.py: 72%

106 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-30 15:42 +0200

1# -*- coding: utf-8 -*- 

2""" Confluence sender module """ 

3 

4from typing import Optional 

5 

6import plotly 

7 

8from ..utils.csv_parser import CSVParser 

9from ..utils.logger_config import setup_logger, DEFAULT_LOGGING_LEVEL 

10 

11# Set path to orca for plotly 

12plotly.io.orca.config.executable = "/usr/local/bin/orca" 

13 

14 

15class PlotlyReporter: 

16 """Class contains wrapper for generated reports (images) via plot charts""" 

17 

18 def __init__( 

19 self, 

20 pr_colors=None, 

21 pr_labels=None, 

22 ar_colors=None, 

23 lines=None, 

24 type_platforms=None, 

25 logger=None, 

26 log_level=DEFAULT_LOGGING_LEVEL, 

27 ): 

28 """ 

29 General init 

30 

31 :param pr_colors: default colors for different priorities, list with rgb, (usually 1-4 values), optional 

32 :param pr_labels: default labels for different priorities, list with strings (usually 1-4 values), optional 

33 :param ar_colors: default colors for different sections (platforms), list with rgb, optional 

34 :param lines: default settings for lines, dict like {'color': 'rgb(0,0,51)', 'width': 1.5}, optional 

35 :param type_platforms: list of dicts, with sections ids, where dict = {'name': 'UI', 

36 'sections': [16276]}, optional 

37 :param logger: logger object, optional 

38 :param log_level: logging level, optional, by default is 'logging.DEBUG' 

39 """ 

40 if not logger: 

41 self.___logger = setup_logger(name="PlotlyReporter", log_file="PlotlyReporter.log", level=log_level) 

42 else: 

43 self.___logger = logger 

44 self.___logger.debug("Initializing Plotly Reporter") 

45 if not type_platforms: 

46 raise ValueError("Platform types is not provided, Plotly Reporter cannot be initialized!") 

47 self.__pr_labels = pr_labels if pr_labels else ["Low", "Medium", "High", "Critical"] 

48 self.__pr_colors = ( 

49 pr_colors if pr_colors else ["rgb(173,216,230)", "rgb(34,139,34)", "rgb(255,255,51)", "rgb(255, 153, 153)"] 

50 ) 

51 self.__ar_colors = ( 

52 ar_colors 

53 if ar_colors 

54 else [ 

55 "rgb(255, 153, 153)", 

56 "rgb(255,255,51)", 

57 "rgb(34,139,34)", 

58 "rgb(173,216,230)", 

59 "rgb(65,105,225)", 

60 "rgb(192, 192, 192)", 

61 ] 

62 ) 

63 self.__lines = lines if lines else ({"color": "rgb(0,0,51)", "width": 1.5}) 

64 self.__type_platforms = type_platforms 

65 

66 def draw_automation_state_report(self, filename=None, reports=None, state_markers=None): 

67 """ 

68 Generates an image file (png) with staked distribution (bar chart) with automation type coverage (or similar). 

69 

70 :param filename: output filename for image, png expected, required 

71 :param reports: report with stacked distribution, usually it's output of 

72 ATCoverageReporter().automation_state_report() 

73 :param state_markers: list of dicts, contains settings for markers on chart like the following: 

74 { 

75 "marker": {"color": "rgb(34,139,34)", "line": {"color": "rgb(0,0,51)", "width": 1.5}}, 

76 "opacity": 0.6, 

77 "textposition": "auto", 

78 } 

79 :return: none 

80 """ 

81 if not reports: 

82 raise ValueError("No TestRail reports are provided, report aborted!") 

83 if not filename: 

84 raise ValueError("No output filename is provided, report aborted!") 

85 data = [] 

86 axis_x = [] 

87 axis_y_automated = [] 

88 axis_y_not_automated = [] 

89 axis_y_na = [] 

90 

91 for report in reports: 

92 axis_x.append(report.get_name()) 

93 axis_y_automated.append(report.get_automated()) 

94 axis_y_not_automated.append(report.get_not_automated()) 

95 axis_y_na.append(report.get_not_applicable()) 

96 

97 if not state_markers: 

98 state_markers = { 

99 "Automated": { 

100 "marker": {"color": "rgb(34,139,34)", "line": {"color": "rgb(0,0,51)", "width": 1.5}}, 

101 "opacity": 0.6, 

102 "textposition": "auto", 

103 }, 

104 "Not automated": { 

105 "marker": {"color": "rgb(255, 153, 153)", "line": {"color": "rgb(0,0,51)", "width": 1.5}}, 

106 "opacity": 0.6, 

107 "textposition": "auto", 

108 }, 

109 "N/A": { 

110 "marker": {"color": "rgb(192, 192, 192)", "line": {"color": "rgb(0,0,51)", "width": 1.5}}, 

111 "opacity": 0.6, 

112 "textposition": "auto", 

113 }, 

114 } 

115 

116 data.append( 

117 plotly.graph_objs.Bar( 

118 x=axis_x, 

119 y=axis_y_automated, 

120 text=axis_y_automated, 

121 name="Automated", 

122 textposition=state_markers["Automated"]["textposition"], 

123 marker=state_markers["Automated"]["marker"], 

124 opacity=state_markers["Automated"]["opacity"], 

125 ) 

126 ) 

127 data.append( 

128 plotly.graph_objs.Bar( 

129 x=axis_x, 

130 y=axis_y_not_automated, 

131 text=axis_y_not_automated, 

132 name="Not automated", 

133 textposition=state_markers["Not automated"]["textposition"], 

134 marker=state_markers["Not automated"]["marker"], 

135 opacity=state_markers["Not automated"]["opacity"], 

136 ) 

137 ) 

138 data.append( 

139 plotly.graph_objs.Bar( 

140 x=axis_x, 

141 y=axis_y_na, 

142 text=axis_y_na, 

143 name="N/A", 

144 textposition=state_markers["N/A"]["textposition"], 

145 marker=state_markers["N/A"]["marker"], 

146 opacity=state_markers["N/A"]["opacity"], 

147 ) 

148 ) 

149 

150 layout = plotly.graph_objs.Layout(barmode="stack") 

151 self.___logger.debug("Drawing chart to file %s", filename) 

152 fig = plotly.graph_objs.Figure(data=data, layout=layout) 

153 plotly.io.write_image(fig, filename) 

154 

155 def draw_test_case_by_priority(self, filename=None, values=None, pr_labels=None, pr_colors=None, lines=None): 

156 """ 

157 Generates an image file (png) with priority distribution (pie chart) 

158 

159 :param filename: output filename for image, png expected, required 

160 :param values: list of values to draw report with priority distribution, usually it's output from 

161 ATCoverageReporter().test_case_by_priority() 

162 :param pr_labels: default labels for different priorities, list with strings (usually 1-4 values), optional 

163 :param pr_colors: default colors for different priorities, list with rgb, (usually 1-4 values), optional 

164 :param lines: default settings for lines, dict like {'color': 'rgb(0,0,51)', 'width': 1.5}, optional 

165 :return: none 

166 """ 

167 if not values: 

168 raise ValueError("No TestRail values are provided, report aborted!") 

169 if not filename: 

170 raise ValueError("No output filename is provided, report aborted!") 

171 pr_labels = pr_labels if pr_labels else self.__pr_labels 

172 pr_colors = pr_colors if pr_colors else self.__pr_colors 

173 lines = lines if lines else self.__lines 

174 fig = { 

175 "data": [ 

176 { 

177 "values": values, 

178 "labels": pr_labels, 

179 "domain": {"column": 0}, 

180 "name": "Test cases by priority", 

181 "hoverinfo": "label+percent+name", 

182 "textinfo": "value+percent", 

183 "type": "pie", 

184 "marker": {"colors": pr_colors, "line": lines}, 

185 }, 

186 ] 

187 } 

188 self.___logger.debug("Drawing chart to file %s", filename) 

189 plotly.io.write_image(fig, filename) 

190 

191 def draw_test_case_by_area(self, filename=None, cases=None, ar_colors=None, lines=None): 

192 """ 

193 Generates an image file (png) with sections distribution (pie chart) 

194 

195 :param filename: output filename for image, png expected, required 

196 :param cases: list of values to draw report with priority distribution, usually it's output from 

197 ATCoverageReporter().test_case_by_type() 

198 :param ar_colors: default colors for different sections (platforms), list with rgb, optional 

199 :param lines: default settings for lines, dict like {'color': 'rgb(0,0,51)', 'width': 1.5}, optional 

200 :return: none 

201 """ 

202 if not cases: 

203 raise ValueError("No TestRail cases are provided, report aborted!") 

204 if not filename: 

205 raise ValueError("No output filename is provided, report aborted!") 

206 # priority distribution 

207 ar_colors = ar_colors if ar_colors else self.__ar_colors 

208 lines = lines if lines else self.__lines 

209 # area distribution 

210 ar_labels = [] 

211 ar_values = [] 

212 for case in cases: 

213 ar_labels.append(case.get_name()) 

214 ar_values.append(case.get_total()) 

215 

216 fig = { 

217 "data": [ 

218 { 

219 "values": ar_values, 

220 "labels": ar_labels, 

221 "domain": {"column": 0}, 

222 "name": "Test cases by area", 

223 "hoverinfo": "label+percent+name", 

224 "textinfo": "value+percent", 

225 "type": "pie", 

226 "marker": {"colors": ar_colors, "line": lines}, 

227 }, 

228 ] 

229 } 

230 

231 self.___logger.debug("Drawing chart to file %s", filename) 

232 plotly.io.write_image(fig, filename) 

233 

234 def draw_history_state_chart( 

235 self, 

236 chart_name: Optional[str] = None, 

237 history_data=None, 

238 filename=None, 

239 trace1_decor=None, 

240 trace2_decor=None, 

241 filename_pattern="current_automation", 

242 reverse_traces=False, 

243 ): 

244 """ 

245 Generates image file (png) with state distribution (staked line chart) 

246 

247 :param chart_name: chart name, string, required 

248 :param history_data: history data, previously stored in CSV, by default it is CSVParser().load_history_data() 

249 :param filename: output filename for image, png expected, optional 

250 :param trace1_decor: decoration for distribution stack (1), dict like {"fill": "tonexty", 

251 "line": {"width": 0.5, 

252 "color": "rgb(255, 153, 153)"}, 

253 "mode": "none"} 

254 :param trace2_decor: decoration for distribution stack (2), dict like {"fill": "tozeroy", 

255 "line": {"width": 0.5, 

256 "color": "rgb(34,139,34)"}, 

257 "mode": "none"} 

258 :param filename_pattern: pattern, what is prefix will be for filename, string, optional 

259 :param reverse_traces: reverse traces order 

260 :return: none 

261 """ 

262 if chart_name is None: 

263 raise ValueError("No chart name is provided, report aborted!") 

264 filename = filename if filename else f"{filename_pattern}_{chart_name.replace(' ', '_')}.csv" 

265 trace1_decor = ( 

266 trace1_decor 

267 if trace1_decor 

268 else {"fill": "tonexty", "line": {"width": 0.5, "color": "rgb(255, 153, 153)"}, "mode": "none"} 

269 ) 

270 trace2_decor = ( 

271 trace2_decor 

272 if trace2_decor 

273 else {"fill": "tozeroy", "line": {"width": 0.5, "color": "rgb(34,139,34)"}, "mode": "none"} 

274 ) 

275 

276 history_data = ( 

277 history_data 

278 if history_data 

279 else CSVParser(log_level=self.___logger.level, filename=filename).load_history_data() 

280 ) 

281 trace1 = plotly.graph_objs.Scatter( 

282 x=history_data[0], 

283 y=history_data[1], 

284 fill=trace1_decor["fill"], 

285 name="Total", 

286 line=trace1_decor["line"], 

287 ) 

288 trace2 = plotly.graph_objs.Scatter( 

289 x=history_data[0], 

290 y=history_data[2], 

291 fill=trace2_decor["fill"], 

292 name="Automated", 

293 line=trace2_decor["line"], 

294 ) 

295 

296 fig = plotly.graph_objs.Figure() 

297 if reverse_traces: 

298 fig.add_trace(trace2) 

299 fig.add_trace(trace1) 

300 else: 

301 fig.add_trace(trace1) 

302 fig.add_trace(trace2) 

303 fig.update_layout(yaxis={"nticks": 30}, autotypenumbers="convert types") 

304 fig.update_yaxes(range=[0, max((eval(i) for i in history_data[1]))]) # pylint: disable=eval-used 

305 

306 filename = f"{filename[:-3]}png" 

307 self.___logger.debug("Drawing chart to file %s", filename) 

308 plotly.io.write_image(fig, filename) 

309 return filename 

310 

311 def draw_history_type_chart( 

312 self, 

313 filename=None, 

314 type_platforms=None, 

315 history_filename_pattern="current_area_distribution", 

316 ar_colors=None, 

317 lines=None, 

318 ): 

319 """ 

320 Generates an image file (png) with state distribution (staked line chart) 

321 

322 :param filename: output filename for image, png expected, required 

323 :param type_platforms: list of dicts, with sections ids, where dict = {'name': 'UI', 

324 'sections': [16276]}, optional 

325 :param history_filename_pattern: pattern, what is prefix will be for filename, string, optional 

326 :param ar_colors: default colors for different sections (platforms), list with rgb, optional 

327 :param lines: default settings for lines, dict like {'color': 'rgb(0,0,51)', 'width': 1.5}, optional 

328 :return: none 

329 """ 

330 if not filename: 

331 raise ValueError("No output filename is provided, report aborted!") 

332 type_platforms = type_platforms if type_platforms else self.__type_platforms 

333 ar_colors = ar_colors if ar_colors else self.__ar_colors 

334 data = [] 

335 lines = lines if lines else self.__lines 

336 index = 0 

337 for platform in type_platforms: 

338 type_name = platform["name"] 

339 history_filename = f"{history_filename_pattern}_{type_name.replace(' ', '_')}.csv" 

340 history_data = CSVParser(log_level=self.___logger.level, filename=history_filename).load_history_data() 

341 data.append( 

342 plotly.graph_objs.Scatter( 

343 x=history_data[0], 

344 y=history_data[1], 

345 name=type_name, 

346 marker={"color": ar_colors[index], "line": lines}, 

347 ) 

348 ) 

349 index += 1 

350 fig = {"data": data} 

351 self.___logger.debug("Drawing chart to file %s", filename) 

352 plotly.io.write_image(fig, filename)