Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1 

2from flask import render_template, redirect, flash, request, url_for, send_from_directory, jsonify, abort 

3from flask_login import login_required, current_user, login_user, logout_user 

4from flask.logging import default_handler 

5from app import app, db, models, forms, utils 

6from app.utils.xlsx_handler import importXLSX 

7from app.charts import DashboardCharts, SummaryCharts 

8from datetime import datetime 

9from sqlalchemy import func, desc 

10import requests 

11import json 

12import uuid 

13import os 

14 

15# handle favicon requests 

16@app.route('/favicon.ico') 

17def favicon(): 

18 return send_from_directory('static/media', 'favicon.ico', mimetype='image/vnd.microsoft.icon') 

19 

20@app.route('/') 

21@login_required 

22def index(): 

23 return render_template('testrun/index.html', items=utils.getItemCategories()) 

24 

25@app.route('/<string:item_type>') 

26@login_required 

27def item_list(item_type): 

28 # placeholder for import form 

29 form = None 

30 # get item list by type 

31 if item_type == 'testrun': 

32 items = models.Testrun.query.order_by(desc('created')).all() 

33 # build form for importing a testrun 

34 form = forms.TestrunImportForm() 

35 

36 elif item_type == 'testcase_sequence': 

37 items = models.TestCaseSequence.query.order_by(desc('created')).all() 

38 # DataFile update form 

39 form = forms.DataFileUpdateForm() 

40 elif item_type == 'testcase': 

41 items = models.TestCase.query.order_by(desc('created')).all() 

42 elif item_type == 'teststep_sequence': 

43 items = models.TestStepSequence.query.order_by(desc('created')).all() 

44 elif item_type == 'teststep': 

45 items = models.TestStepExecution.query.order_by(desc('created')).all() 

46 else: 

47 app.logger.warning(f'Item type "{item_type}" does not exist. Requested by "{current_user}".') 

48 flash(f'Item type "{item_type}" does not exist.', 'warning') 

49 return redirect(url_for('index')) 

50 

51 return render_template('testrun/item_list.html', type=item_type, items=items, form=form) 

52 

53 

54@app.route('/<string:item_type>/<string:item_id>/delete', methods=['POST', 'DELETE']) 

55@login_required 

56def delete_item(item_type, item_id): 

57 # 

58 # delete item 

59 # 

60 

61 # cascade delete 

62 if request.method == 'POST': 

63 app.logger.info(f'Cascade deletion of {utils.getItemType(item_type)} {item_id} requested by "{current_user}".') 

64 try: 

65 utils.deleteCascade(item_type, uuid.UUID(item_id).bytes) 

66 flash(f'Item {utils.getItemType(item_type)} {item_id} and its children have been successfully deleted.', 'success') 

67 app.logger.info( 

68 f'Item {utils.getItemType(item_type)} {item_id} and its children have been successfully deleted by "{current_user}".' 

69 ) 

70 return jsonify({'success': 'OK'}), 200 

71 except Exception as error: 

72 app.logger.error(f'Failed to cascade delete {utils.getItemType(item_type)} {item_id} requested by "{current_user}": {error}') 

73 return jsonify({'error': 'Bad Request'}), 400 

74 

75 # delete single item 

76 elif request.method == 'DELETE': 

77 # get item by type and id 

78 idAsBytes = uuid.UUID(item_id).bytes 

79 app.logger.info(f'Deletion of {utils.getItemType(item_type)} {item_id} requested by "{current_user}".') 

80 if item_type == 'testrun': 

81 item = models.Testrun.query.get(idAsBytes) 

82 elif item_type == 'testcase_sequence': 

83 item = models.TestCaseSequence.query.get(idAsBytes) 

84 elif item_type == 'testcase': 

85 item = models.TestCase.query.get(idAsBytes) 

86 elif item_type == 'teststep_sequence': 

87 item = models.TestStepSequence.query.get(idAsBytes) 

88 elif item_type == 'teststep': 

89 item = models.TestStepExecution.query.get(idAsBytes) 

90 else: 

91 app.logger.warning(f'Item type "{item_type}" does not exist. Requested by "{current_user}".') 

92 flash(f'Item type "{item_type}" does not exist.', 'warning') 

93 #return redirect(url_for('index')) 

94 return jsonify({'error': 'Bad Request'}), 400 

95 

96 if item is None: 

97 app.logger.error(f'Item {utils.getItemType(item_type)} {item_id} does not exist.') 

98 return jsonify({'error': 'Bad Request'}), 400 

99 

100 db.session.delete(item) 

101 db.session.commit() 

102 app.logger.info(f'Item {utils.getItemType(item_type)} {item_id} successfully deleted by {current_user}.') 

103 flash(f'Item "{item.name}" has been successfully deleted.', 'success') 

104 return jsonify({'success': 'OK'}), 200 

105 

106 return jsonify({'error': 'Method Not Allowed'}), 405 

107 

108 

109@app.route('/<string:item_type>/<string:item_id>/edit', methods=['GET', 'POST']) 

110@login_required 

111def edit_item(item_type, item_id): 

112 # 

113 # edit item 

114 # 

115 

116 idAsBytes = uuid.UUID(item_id).bytes 

117 if item_type == 'testrun': 

118 item = models.Testrun.query.get(idAsBytes) 

119 form = forms.TestrunCreateForm.new() 

120 if request.method == 'GET': 

121 form.global_settings.data = item.global_settings 

122 form.testcase_sequences.data = [f'{x.uuid}' for x in item.testcase_sequences] 

123 elif item_type == 'testcase_sequence': 

124 item = models.TestCaseSequence.query.get(idAsBytes) 

125 form = forms.TestCaseSequenceCreateForm.new() 

126 if request.method == 'GET': 

127 form.classname.data = f'{item.classname.id}' 

128 form.datafiles.data = [f'{x.uuid}' for x in item.datafiles] 

129 form.testcases.data = [f'{x.uuid}' for x in item.testcases] 

130 elif item_type == 'testcase': 

131 item = models.TestCase.query.get(idAsBytes) 

132 form = forms.TestCaseCreateForm.new() 

133 if request.method == 'GET': 

134 form.classname.data = f'{item.classname.id}' 

135 form.browser_type.data = f'{item.browser_type.id}' 

136 form.testcase_type.data = f'{item.testcase_type.id}' 

137 form.teststep_sequences.data = [f'{x.uuid}' for x in item.teststep_sequences] 

138 elif item_type == 'teststep_sequence': 

139 item = models.TestStepSequence.query.get(idAsBytes) 

140 form = forms.TestStepSequenceCreateForm.new(item_id=item_id) 

141 if request.method == 'GET': 

142 form.classname.data = f'{item.classname.id}' 

143 form.teststeps.data =[f'{x.uuid}' for x in item.teststeps] 

144 elif item_type == 'teststep': 

145 item = models.TestStepExecution.query.get(idAsBytes) 

146 form = forms.TestStepCreateForm.new() 

147 if request.method == 'GET': 

148 form.activity_type.data = f'{item.activity_type.id}' 

149 form.locator_type.data = f'{item.locator_type.id}' if item.locator_type else '0' 

150 # model extension 

151 form.locator.data = item.locator or '' 

152 if item.optional: 

153 form.optional.data = '1' 

154 else: 

155 form.optional.data = '0' 

156 if item.timeout: 

157 form.timeout.data = f'{item.timeout}' 

158 else: 

159 form.timeout.data = '' 

160 form.release.data = item.release or '' 

161 form.value.data = item.value or '' 

162 form.value2.data = item.value2 or '' 

163 form.comparison.data = utils.getComparisonId(item.comparison) 

164 

165 else: 

166 app.logger.warning(f'Item type "{item_type}" does not exist. Requested by "{current_user}".') 

167 flash(f'Item type "{item_type}" does not exist.', 'warning') 

168 return redirect(url_for('index')) 

169 

170 if request.method == 'GET': 

171 form.name.data = item.name 

172 form.description.data = item.description 

173 

174 if form.validate_on_submit(): 

175 # update item data 

176 item.name = form.name.data 

177 item.description = form.description.data 

178 # testrun 

179 if item_type == 'testrun': 

180 item.editor = current_user 

181 item.edited = datetime.utcnow() 

182 item.testcase_sequences=[models.TestCaseSequence.query.get(uuid.UUID(x).bytes) for x in form.testcase_sequences.data] 

183 item.global_settings = form.global_settings.data or None 

184 # testcase sequence 

185 elif item_type == 'testcase_sequence': 

186 item.edited = datetime.utcnow() 

187 item.classname = models.ClassName.query.get(int(form.classname.data)) 

188 item.datafiles = [models.DataFile.query.get(uuid.UUID(x).bytes) for x in form.datafiles.data] 

189 item.testcases = [models.TestCase.query.get(uuid.UUID(x).bytes) for x in form.testcases.data] 

190 # testcase 

191 elif item_type == 'testcase': 

192 item.editor = current_user 

193 item.edited = datetime.utcnow() 

194 item.classname = models.ClassName.query.get(int(form.classname.data)) 

195 item.browser_type = models.BrowserType.query.get(int(form.browser_type.data)) 

196 item.testcase_type = models.TestCaseType.query.get(int(form.testcase_type.data)) 

197 item.teststep_sequences = [models.TestStepSequence.query.get(uuid.UUID(x).bytes) for x in form.teststep_sequences.data] 

198 # step sequence 

199 elif item_type == 'teststep_sequence': 

200 item.editor = current_user 

201 item.edited = datetime.utcnow() 

202 item.classname = models.ClassName.query.get(int(form.classname.data)) 

203 item.teststeps = [models.TestStepExecution.query.get(uuid.UUID(x).bytes) for x in form.teststeps.data] 

204 # test step 

205 elif item_type == 'teststep': 

206 item.editor = current_user 

207 item.edited = datetime.utcnow() 

208 item.activity_type = models.ActivityType.query.get(int(form.activity_type.data)) 

209 item.locator_type = models.LocatorType.query.get(int(form.locator_type.data)) 

210 # model extension 

211 item.locator = form.locator.data 

212 item.optional = [False, True][int(form.optional.data)] 

213 try: 

214 item.timeout = float(form.timeout.data) 

215 except ValueError: 

216 item.timeout = None 

217 item.release = form.release.data or None 

218 item.value = form.value.data or None 

219 item.value2 = form.value2.data or None 

220 if form.comparison.data == '0': 

221 item.comparison = None 

222 else: 

223 item.comparison = utils.COMPARISONS[int(form.comparison.data)] 

224 

225 

226 # update item in db 

227 db.session.commit() 

228 app.logger.info(f'Edited {item_type} {item_id} by {current_user}.') 

229 flash(f'Item "{item.name}" successfully updated.', 'success') 

230 return redirect(url_for('item_list', item_type=item_type)) 

231 

232 

233 return render_template('testrun/edit_item.html', type=item_type, item=item, form=form) 

234 

235 

236@app.route('/<string:item_type>/new', methods=['GET', 'POST']) 

237@login_required 

238def new_item(item_type): 

239 # 

240 # create new item 

241 # 

242 if item_type == 'testrun': 

243 form = forms.TestrunCreateForm.new() 

244 chips = ['testcase_sequences'] 

245 elif item_type == 'testcase_sequence': 

246 form = forms.TestCaseSequenceCreateForm.new(defaults=(request.method == 'GET')) 

247 chips = ['datafiles', 'testcases'] 

248 elif item_type == 'testcase': 

249 form = forms.TestCaseCreateForm.new(defaults=(request.method == 'GET')) 

250 chips = ['teststep_sequences'] 

251 elif item_type == 'teststep_sequence': 

252 form = forms.TestStepSequenceCreateForm.new(defaults=(request.method == 'GET')) 

253 chips = ['teststeps'] 

254 elif item_type == 'teststep': 

255 form = forms.TestStepCreateForm.new(defaults=(request.method == 'GET')) 

256 chips = [] 

257 else: 

258 app.logger.warning(f'Item type "{item_type}" does not exist. Requested by "{current_user}".') 

259 flash(f'Item type "{item_type}" does not exist.', 'warning') 

260 return redirect(url_for('index')) 

261 

262 if form.validate_on_submit(): 

263 # create new item 

264 # testrun 

265 if item_type == 'testrun': 

266 item = models.Testrun( 

267 name=form.name.data, 

268 description=form.description.data, 

269 creator=current_user, 

270 testcase_sequences=[models.TestCaseSequence.query.get(uuid.UUID(x).bytes) for x in form.testcase_sequences.data], 

271 global_settings=form.global_settings.data or None, 

272 ) 

273 # testcase sequence 

274 elif item_type == 'testcase_sequence': 

275 item = models.TestCaseSequence( 

276 name=form.name.data, 

277 description=form.description.data, 

278 creator=current_user, 

279 classname=models.ClassName.query.get(int(form.classname.data)), 

280 datafiles=[models.DataFile.query.get(uuid.UUID(x).bytes) for x in form.datafiles.data], 

281 testcases=[models.TestCase.query.get(uuid.UUID(x).bytes) for x in form.testcases.data], 

282 ) 

283 # testcase 

284 elif item_type == 'testcase': 

285 item = models.TestCase( 

286 name=form.name.data, 

287 description=form.description.data, 

288 creator=current_user, 

289 classname=models.ClassName.query.get(int(form.classname.data)), 

290 browser_type=models.BrowserType.query.get(int(form.browser_type.data)), 

291 testcase_type=models.TestCaseType.query.get(int(form.testcase_type.data)), 

292 teststep_sequences=[models.TestStepSequence.query.get(uuid.UUID(x).bytes) for x in form.teststep_sequences.data], 

293 ) 

294 # step sequence 

295 elif item_type == 'teststep_sequence': 

296 item = models.TestStepSequence( 

297 name=form.name.data, 

298 description=form.description.data, 

299 creator=current_user, 

300 classname=models.ClassName.query.get(int(form.classname.data)), 

301 teststeps=[models.TestStepExecution.query.get(uuid.UUID(x).bytes) for x in form.teststeps.data], 

302 ) 

303 # test step 

304 elif item_type == 'teststep': 

305 item = models.TestStepExecution( 

306 name=form.name.data, 

307 description=form.description.data, 

308 creator=current_user, 

309 activity_type=models.ActivityType.query.get(int(form.activity_type.data)), 

310 locator_type=models.LocatorType.query.get(int(form.locator_type.data)), 

311 # model extension 

312 locator=form.locator.data or None, 

313 optional=[False, True][int(form.optional.data)], 

314 ) 

315 try: 

316 item.timeout = float(form.timeout.data) 

317 except ValueError: 

318 item.timeout = None 

319 item.release = form.release.data or None 

320 item.value = form.value.data or None 

321 item.value2 = form.value2.data or None 

322 if form.comparison.data == '0': 

323 item.comparison = None 

324 else: 

325 item.comparison = utils.COMPARISONS[int(form.comparison.data)] 

326 

327 # save item to db 

328 db.session.add(item) 

329 db.session.commit() 

330 app.logger.info(f'Created {item_type} {item.uuid} by {current_user}.') 

331 flash(f'Item "{item.name}" successfully created.', 'success') 

332 return redirect(url_for('item_list', item_type=item_type)) 

333 

334 return render_template('testrun/create_item.html', type=item_type, chips=chips, form=form) 

335 

336 

337@app.route('/testrun/<string:item_id>/run') 

338@login_required 

339def run_testrun(item_id): 

340 # 

341 # runs Testrun via API Web Service 

342 # 

343 

344 # check for complete set of Testrun's layers 

345 try: 

346 utils.getTestrunIfCorrect(item_id) 

347 except Exception as error: 

348 app.logger.error(f'Failed to execute Testrun {item_id} by {current_user}. {error}.') 

349 flash(f'ERROR: Testrun {item_id} cannot be executed. {error}', 'danger') 

350 return redirect(url_for('item_list', item_type='testrun')) 

351 

352 # export to JSON 

353 try: 

354 json_testrun = utils.exportJSON(item_id, shouldBeSaved=False) 

355 except Exception as error: 

356 app.logger.error(f'Failed to export Testrun {item_id} by {current_user}. {error}') 

357 flash(f'ERROR: Failed to export Testrun {item_id}. {error}', 'danger') 

358 return redirect(url_for('item_list', item_type='testrun')) 

359 

360 # call API 

361 url = '/'.join(('http:/', app.config.get('BAANGT_API_HOST'), app.config.get('BAANGT_API_EXECUTE'))) 

362 app.logger.info(f'Call execution of Testrun {item_id} by {current_user}. API URL: {url}') 

363 try: 

364 r = requests.post(url, json=json_testrun) 

365 except Exception as error: 

366 app.logger.error(f'Failed to connect to {app.config.get("BAANGT_API_HOST")} by {current_user}. {error}') 

367 flash(f'ERROR: Cannot establish connection: {app.config.get("BAANGT_API_HOST")}', 'danger') 

368 return redirect(url_for('item_list', item_type='testrun')) 

369 

370 # API server error handler 

371 if r.status_code == 500: 

372 app.logger.error('ERROR: API Server internal error') 

373 flash('ERROR: API Server internal error', 'danger') 

374 return redirect(url_for('item_list', item_type='testrun')) 

375 

376 app.logger.info(f'API response: status code {r.status_code}\n {r.text}') 

377 

378 if r.status_code == 202: 

379 # get results 

380 jsonResult = json.loads(r.text) 

381 # store to db 

382 tr_call = models.TestRunCall( 

383 id=uuid.UUID(jsonResult['id']).bytes, 

384 creator=current_user, 

385 testrun_id=utils.idAsBytes(item_id), 

386 ) 

387 db.session.add(tr_call) 

388 db.session.commit() 

389 app.logger.info(f'Created Testrun Call {jsonResult["id"]} by {current_user}.') 

390 return render_template('testrun/testrun_status.html', call=tr_call) 

391 else: 

392 flash(f'ERROR: {r.text}', 'danger') 

393 return redirect(url_for('item_list', item_type='testrun')) 

394 

395 

396@app.route('/testrun/<string:item_id>/export/<string:export_format>')#, methods=['GET', 'POST']) 

397@login_required 

398def export_testrun(item_id, export_format): 

399 # 

400 # export Testrun object to <export_format> 

401 # <export_format> is one of the following: 

402 # XLSX 

403 # JSON 

404 # 

405 

406 if export_format.upper() == 'XLSX': 

407 # export to XLSX 

408 try: 

409 result = utils.exportXLSX(item_id) 

410 except Exception as e: 

411 app.logger.error(f'Failed to export Testrun {item_id} to XLSX by {current_user}. {e}') 

412 return jsonify({'error': f'Failed to export Testrun: {e}'}), 400 

413 

414 elif export_format.upper() == 'JSON': 

415 # export to JSON 

416 try: 

417 result = utils.exportJSON(item_id) 

418 except Exception as e: 

419 app.logger.error(f'Failed to export Testrun {item_id} to JSON by {current_user}. {e}') 

420 return jsonify({'error': f'Failed to export Testrun: {e}'}), 400 

421 

422 else: 

423 # invalid format 

424 app.logger.error(f'Failed to export Testrun {item_id} by {current_user}. Invalid format: {export_format}') 

425 return jsonify({'error': f'format {export_format} is not supported'}), 400 

426 

427 # successful export 

428 url = url_for('static', filename=f'files/{result}') 

429 app.logger.info(f'Testrun {item_id} exported to {export_format} by {current_user}. Target: static/files/{result}') 

430 

431 #return f'Success: <a href="{url}">{result}</a>' 

432 return jsonify({ 

433 'success': 'OK', 

434 'filename': result, 

435 'url': url, 

436 }), 200 

437 

438@app.route('/testrun/import', methods=['POST']) 

439@login_required 

440def import_testsun(): 

441 # 

442 # imports testrun from file 

443 # 

444 

445 # import only from XLSX is available now 

446 form = forms.TestrunImportForm() 

447 

448 if form.validate_on_submit(): 

449 try: 

450 #utils.importXLSX(form.file.data, datafiles=form.dataFiles) 

451 importXLSX(form.file.data, datafiles=form.dataFiles) 

452 app.logger.info(f'Testrun successfully imported from "{form.file.data.filename}" by {current_user}.') 

453 flash(f'Testrun successfully imported from "{form.file.data.filename}"', 'success') 

454 except Exception as error: 

455 # discard changes 

456 db.session.rollback() 

457 app.logger.error(f'Failed to import Testrun from "{form.file.data.filename}" by {current_user}. {error}.') 

458 flash(f'ERROR: Cannot import Testrun from "{form.file.data.filename}". {error}.', 'danger') 

459 else: 

460 flash(f'File is required for import', 'warning') 

461 

462 return redirect(url_for('item_list', item_type='testrun')) 

463 

464@app.route('/datafile/upload', methods=['POST']) 

465@login_required 

466def upload_datafile(): 

467 # get datafile 

468 file = request.files.get('datafile') 

469 if file is None: 

470 return jsonify({'error': 'Request does not contain dataFile'}), 400 

471 

472 # upload data file 

473 try: 

474 file_id = utils.importDataFile(file) 

475 except Exception as error: 

476 app.logger.error(f'Failed to uplod DataFile "{file.filename}" by {current_user}. {error}.') 

477 return jsonify({'error': f'Failed upload DataFile "{file.filename}". {error}.'}), 400 

478 

479 return jsonify({'id': file_id, 'name': file.filename}), 200 

480 

481@app.route('/datafile/update', methods=['POST']) 

482@login_required 

483def update_datafile(): 

484 # 

485 # update datafile with specified id 

486 # 

487 

488 form = forms.DataFileUpdateForm() 

489 

490 if form.validate_on_submit(): 

491 file = form.datafile.data 

492 

493 # upload data file 

494 try: 

495 file_id = utils.importDataFile( 

496 file, 

497 datafile_id=form.datafile_id.data, 

498 ) 

499 except Exception as error: 

500 app.logger.error(f'Failed to uplod DataFile "{file.filename}" by {current_user}. {error}.') 

501 flash(f'ERROR: Failed to upload DataFile "{file.filename}". {error}.', 'danger') 

502 return redirect(url_for('item_list', item_type='testcase_sequence')) 

503 

504 return redirect(url_for( 

505 'item_list', 

506 item_type='testcase_sequence', 

507 show=form.testcaseseq_id.data, 

508 _anchor=f'item{form.testcaseseq_id.data}', 

509 )) 

510 

511 flash(f'ERROR: Failed to upload DataFile. {form.datafile.errors}.', 'danger') 

512 return redirect(url_for('item_list', item_type='testcase_sequence')) 

513 

514 

515 

516 

517@app.route('/testrun/<string:item_id>/import', methods=['POST']) 

518@login_required 

519def update_testsun(item_id): 

520 # 

521 # imports testrun from file 

522 # 

523 

524 # update only from XLSX is available now 

525 form = forms.TestrunImportForm() 

526 

527 if form.validate_on_submit(): 

528 

529 # update items 

530 try: 

531 #utils.importXLSX(form.file.data, item_id=uuid.UUID(item_id).bytes) 

532 importXLSX(form.file.data, testrun_id=item_id) 

533 app.logger.info(f'Testrun {item_id} successfully updated from "{form.file.data.filename}" by {current_user}.') 

534 flash(f'Testrun {item_id} has been successfully updated from "{form.file.data.filename}"', 'success') 

535 except Exception as error: 

536 # discard changes 

537 db.session.rollback() 

538 app.logger.error(f'Failed to update Testrun {item_id} from "{form.file.data.filename}" by {current_user}. {error}.') 

539 flash(f'ERROR: Cannot update Testrun {item_id} from "{form.file.data.filename}". {error}.', 'danger') 

540 else: 

541 flash(f'File is required for import', 'warning') 

542 

543 return redirect(url_for('item_list', item_type='testrun')) 

544 

545 

546# 

547# Testrun Reports 

548# 

549 

550@app.route('/summary/<string:call_id>', methods=['GET', 'POST']) 

551@login_required 

552def get_results(call_id): 

553 # 

554 # show Testrun call results 

555 # 

556 

557 # fetch call data 

558 if request.method == 'POST': 

559 

560 # call API 

561 url = '/'.join(('http:/', app.config.get('BAANGT_API_HOST'), app.config.get('BAANGT_API_STATUS'), call_id)) 

562 app.logger.info(f'Call results by {current_user}. API URL: {url}') 

563 try: 

564 r = requests.get(url) 

565 except Exception as error: 

566 app.logger.error(f'Failed to connect to {app.config.get("TESTRUN_SERVICE_HOST")} by {current_user}. {error}') 

567 return jsonify({'error': f'Cannot establish connection: {app.config.get("TESTRUN_SERVICE_HOST")}'}), 400 

568 

569 app.logger.info(f'API response: status code {r.status_code} - JSON {r.text}') 

570 

571 # API server error handler 

572 if r.status_code == 500: 

573 app.logger.error('ERROR: API Server internal error') 

574 return jsonify({'error': 'API Server internal error'}), 400 

575 

576 if r.status_code == 200: 

577 # build charts 

578 try: 

579 charts = SummaryCharts(json.loads(r.text)) 

580 return jsonify(charts.get_charts()), 200 

581 except Exception as e: 

582 app.logger.error(f'ERROR: {e}') 

583 return jsonify({'error': f'{e}'}), 400 

584 

585 return jsonify({'error': f'Response: {r.status_code}'}), 400 

586 

587 # show results 

588 call = models.TestRunCall.query.get(uuid.UUID(call_id).bytes) 

589 if not call: 

590 abort(404) 

591 

592 if not call.is_finished and not call.is_failed: 

593 # update Testrun calls 

594 try: 

595 utils.update_call(call) 

596 except Exception as e: 

597 flash(f'{e}', 'warning') 

598 

599 if not call.is_finished or call.is_failed: 

600 return render_template('testrun/testrun_status.html', call=call) 

601 

602 return render_template('testrun/summary.html', call=call) 

603 

604 

605@app.route('/chart/<string:testrun_id>/<string:stage>', methods=['POST']) 

606@login_required 

607def get_charts(testrun_id, stage): 

608 # 

609 # fetches chart data 

610 # 

611 

612 testrun = models.Testrun.query.get(uuid.UUID(testrun_id).bytes) 

613 

614 # build charts 

615 try: 

616 charts = DashboardCharts(testrun.finished_calls(stage=stage)) 

617 return jsonify(charts.get_charts()), 200 

618 except Exception as e: 

619 print(f'ERROR: {e}') 

620 return jsonify({'error': 'error'}), 400 

621 

622 

623@app.route('/dashboard') 

624@login_required 

625def dashboard(): 

626 # 

627 # Dashboard view 

628 # 

629 

630 # update Testrun calls 

631 try: 

632 utils.update_all_calls() 

633 except Exception as e: 

634 flash(f'{e}', 'warning') 

635 

636 testruns = [tr for tr in models.Testrun.query.order_by(func.lower(models.Testrun.name)).all() if tr.finished_calls()] 

637 stages = [] 

638 stage_set = set() 

639 for tr in testruns: 

640 stages.append({call.stage for call in tr.calls if call.is_finished}) 

641 stage_set.update(stages[-1]) 

642 

643 return render_template('testrun/dashboard.html', testruns=testruns, stages=stages, stage_set=stage_set) 

644 

645 

646# 

647# user authentication 

648# 

649 

650@app.route('/signup', methods=['GET', 'POST']) 

651def register(): 

652 if current_user.is_authenticated: 

653 return redirect(url_for('index')) 

654 

655 form = forms.SingupForm() 

656 if form.validate_on_submit(): 

657 # create user 

658 user = models.User(username=form.username.data) 

659 user.set_password(form.password.data) 

660 db.session.add(user) 

661 db.session.commit() 

662 # login 

663 login_user(user, remember=True) 

664 flash(f'User {user.username.capitalize()} successfully created!', 'success') 

665 app.logger.info(f"User '{user.username}' successfully created") 

666 return redirect(url_for('index')) 

667 

668 return render_template('auth/signup.html', form=form) 

669 

670@app.route('/login', methods=['GET', 'POST']) 

671def login(): 

672 if current_user.is_authenticated: 

673 return redirect(url_for('index')) 

674 

675 form = forms.LoginForm() 

676 if form.validate_on_submit(): 

677 user = models.User.query.filter_by(username=form.username.data).first() 

678 if user and user.verify_password(form.password.data): 

679 login_user(user, remember=True) 

680 flash(f'Welcome {user.username.capitalize()}!', 'success') 

681 app.logger.info(f"User '{user.username}' logged in") 

682 return redirect(url_for('index')) 

683 

684 return render_template('auth/login.html', form=form) 

685 

686@app.route('/logout') 

687@login_required 

688def logout(): 

689 app.logger.info(f"User '{current_user.username}' logged out") 

690 logout_user() 

691 

692 return redirect(url_for('login')) 

693 

694 

695# 

696# BAANGT documentations 

697# 

698 

699@app.route('/docs/') 

700def help_index(): 

701 return render_template('docs/index.html') 

702 

703@app.route('/docs/<string:topic>') 

704def help(topic): 

705 if topic == 'testrun': 

706 try: 

707 with open(app.config.get('BAANGT_TESTRUN_GLOBALS_DEFAULT'), 'r') as f: 

708 global_settings = json.load(f) 

709 return render_template('docs/index.html', topic=topic, globals=json.dumps(global_settings, indent=2)) 

710 except Exception as e: 

711 app.logger.error(f'Failed to load Testrun global settings: {e}') 

712 

713 return render_template('docs/index.html', topic=topic)