|
1 | 1 | import { Component, OnInit } from '@angular/core';
|
2 | 2 | import { Router, ActivatedRoute } from '@angular/router';
|
3 |
| -import { SimpleRequester } from '../../../services/simple-requester'; |
4 | 3 | import { TestRun } from '../../../shared/models/testRun';
|
5 | 4 | import { Project } from '../../../shared/models/project';
|
6 | 5 | import { TestRunService } from '../../../services/testRun.service';
|
7 | 6 | import { ProjectService } from '../../../services/project.service';
|
8 |
| -import { TestSuiteService } from '../../../services/testSuite.service'; |
9 | 7 | import { TestRunStat } from '../../../shared/models/testrunStats';
|
10 | 8 | import { AuditService } from '../../../services/audits.service';
|
11 | 9 | import { Audit, AuditNotification } from '../../../shared/models/audit';
|
12 | 10 | import { GlobalDataService } from '../../../services/globaldata.service';
|
13 | 11 | import { TFColumn, TFColumnType } from '../../../elements/table/tfColumn';
|
| 12 | +import { IssueService } from '../../../services/issue.service'; |
| 13 | +import { Issue } from '../../../shared/models/issue'; |
| 14 | +import { SingleLineBarChartData } from '../../../elements/single-line-bar-chart/single-line-bar-chart.component'; |
| 15 | +import { TestSuiteService } from '../../../services/testSuite.service'; |
| 16 | +import { TestSuite } from '../../../shared/models/testSuite'; |
14 | 17 |
|
15 | 18 | @Component({
|
16 | 19 | templateUrl: './project-view.component.html',
|
17 |
| - providers: [ |
18 |
| - TestRunService, |
19 |
| - SimpleRequester, |
20 |
| - ProjectService, |
21 |
| - TestSuiteService, |
22 |
| - AuditService |
23 |
| - ] |
| 20 | + styleUrls: ['./project-view.component.css'] |
24 | 21 | })
|
25 | 22 | export class ProjectViewComponent implements OnInit {
|
26 | 23 | audits: Audit[];
|
| 24 | + suites: TestSuite[]; |
27 | 25 | project: Project;
|
28 | 26 | testRuns: TestRun[];
|
29 | 27 | testRun: TestRun;
|
30 | 28 | testRunStats: TestRunStat[];
|
31 | 29 | hideAll = true;
|
32 | 30 | auditNotification: AuditNotification;
|
33 | 31 | notification: { text: string, type: string };
|
34 |
| - columns: TFColumn[]; |
| 32 | + testRunColumns: TFColumn[] = [ |
| 33 | + { name: 'Build Name', property: 'build_name', type: TFColumnType.text }, |
| 34 | + { name: 'Execution Environment', property: 'execution_environment', type: TFColumnType.text }, |
| 35 | + { name: 'Start Time', property: 'start_time', type: TFColumnType.date, class: 'fit' }, |
| 36 | + { name: 'Finish Time', property: 'finish_time', type: TFColumnType.date, class: 'fit' }, |
| 37 | + { name: 'Pass Rate', property: 'failed', type: TFColumnType.percent, class: 'fit' }, |
| 38 | + ]; |
| 39 | + issueColumns: TFColumn[] = [{ name: 'Id', property: 'id', type: TFColumnType.text, class: 'fit' }, |
| 40 | + { name: 'Title', property: 'title', type: TFColumnType.text }, |
| 41 | + { |
| 42 | + name: 'Status', property: 'status', type: TFColumnType.colored, |
| 43 | + lookup: { |
| 44 | + values: [], |
| 45 | + propToShow: ['name'] |
| 46 | + }, class: 'fit' |
| 47 | + }, { |
| 48 | + name: 'Resolution', property: 'resolution', type: TFColumnType.colored, |
| 49 | + lookup: { |
| 50 | + values: [], |
| 51 | + propToShow: ['name'] |
| 52 | + }, class: 'fit' |
| 53 | + }]; |
| 54 | + allIssues: Issue[]; |
| 55 | + myIssues: Issue[]; |
| 56 | + quality: { current: { quality: number, NA: number, testIssue: number, other: number, appIssue: number, passed: number, total: number }, average: number }; |
| 57 | + chartData: SingleLineBarChartData[]; |
| 58 | + issueStat: { open: number, openApp: number, total: number, totalApp: number }; |
35 | 59 |
|
36 | 60 | constructor(
|
37 | 61 | public globaldata: GlobalDataService,
|
38 | 62 | private projectService: ProjectService,
|
39 | 63 | private testrunService: TestRunService,
|
| 64 | + private issueService: IssueService, |
40 | 65 | private auditService: AuditService,
|
| 66 | + private testSuiteService: TestSuiteService, |
41 | 67 | private route: ActivatedRoute,
|
42 | 68 | private router: Router
|
43 | 69 | ) { }
|
44 | 70 |
|
45 | 71 | async ngOnInit() {
|
46 |
| - this.testRun = { project_id: this.route.snapshot.params['projectId'], debug: 0 }; |
47 |
| - this.project = { id: this.route.snapshot.params['projectId'] }; |
48 |
| - |
49 |
| - this.projectService.getProjects(this.project).subscribe(async (projects) => { |
50 |
| - this.project = projects[0]; |
51 |
| - if (this.globaldata.auditModule) { |
52 |
| - this.auditService.getAudits({ project: { id: this.project.id } }).subscribe(audits => { |
53 |
| - this.audits = audits; |
54 |
| - this.generateAuditNotification(); |
55 |
| - this.setAuditNotification(); |
56 |
| - }); |
57 |
| - } |
58 |
| - this.testRuns = await this.testrunService.getTestRun(this.testRun, 5); |
59 |
| - this.testRunStats = await this.testrunService.getTestsRunStats(this.testRun); |
60 |
| - this.testRuns.forEach(testrun => { |
61 |
| - testrun['failed'] = this.getNumberOfFails(testrun.id); |
| 72 | + const projectId = this.route.snapshot.params.projectId; |
| 73 | + this.testRun = { project_id: projectId, debug: 0 }; |
| 74 | + this.project = { id: projectId }; |
| 75 | + |
| 76 | + this.allIssues = await this.issueService.getIssues({ |
| 77 | + project_id: projectId |
| 78 | + }); |
| 79 | + |
| 80 | + this.myIssues = this.allIssues.filter(x => x.status_id != 4 && x.status_id != 2 && x.assignee_id === this.globaldata.currentUser.id); |
| 81 | + |
| 82 | + this.suites = await this.testSuiteService.getTestSuite({ project_id: projectId }); |
| 83 | + this.project = (await this.projectService.getProjects(this.project).toPromise())[0]; |
| 84 | + |
| 85 | + if (this.globaldata.auditModule) { |
| 86 | + this.auditService.getAudits({ project: { id: this.project.id } }).subscribe(audits => { |
| 87 | + this.audits = audits; |
| 88 | + this.generateAuditNotification(); |
| 89 | + this.setAuditNotification(); |
62 | 90 | });
|
63 |
| - this.columns = [ |
64 |
| - { name: 'Build Name', property: 'build_name', type: TFColumnType.text }, |
65 |
| - { name: 'Execution Environment', property: 'execution_environment', type: TFColumnType.text }, |
66 |
| - { name: 'Start Time', property: 'start_time', type: TFColumnType.date, class: 'fit' }, |
67 |
| - { name: 'Finish Time', property: 'finish_time', type: TFColumnType.date, class: 'fit' }, |
68 |
| - { name: 'Pass Rate', property: 'failed', type: TFColumnType.percent, class: 'fit' }, |
69 |
| - ]; |
| 91 | + } |
| 92 | + |
| 93 | + this.testRuns = await this.testrunService.getTestRun(this.testRun, 5); |
| 94 | + this.testRunStats = await this.testrunService.getTestsRunStats(this.testRun); |
| 95 | + this.testRuns.forEach(testrun => { |
| 96 | + testrun['failed'] = this.getNumberOfFails(testrun.id); |
70 | 97 | });
|
| 98 | + this.quality = this.getQualityInfo(this.suites, this.testRunStats); |
| 99 | + this.chartData = this.createChartData(this.quality.current); |
| 100 | + this.issueStat = this.getIssuesStat(this.allIssues); |
| 101 | + } |
| 102 | + |
| 103 | + getIssuesStat(issues: Issue[]): { open: number, openApp: number, total: number, totalApp: number } { |
| 104 | + return { |
| 105 | + open: issues.filter(x => x.status_id != 4 && x.status_id != 2).length, |
| 106 | + openApp: issues.filter(x => x.status_id != 4 && x.status_id != 2 && x.resolution.color === 1).length, |
| 107 | + total: issues.length, |
| 108 | + totalApp: issues.filter(x => x.resolution.color === 1).length |
| 109 | + } |
71 | 110 | }
|
72 | 111 |
|
73 | 112 | getNumberOfFails(id: number) {
|
@@ -163,4 +202,88 @@ export class ProjectViewComponent implements OnInit {
|
163 | 202 | const difference_ms = Math.abs(date1_ms - date2_ms);
|
164 | 203 | return Math.round(difference_ms / ONEDAY);
|
165 | 204 | }
|
| 205 | + |
| 206 | + getQualityInfo(suites: TestSuite[], testRunStats: TestRunStat[]): { current: { quality: number, NA: number, testIssue: number, other: number, appIssue: number, passed: number, total: number }, average: number } { |
| 207 | + const testRunStatsBySuite = []; |
| 208 | + for (const suite of suites) { |
| 209 | + const teStats: TestRunStat[] = testRunStats.filter(stat => stat.test_suite_id === suite.id); |
| 210 | + testRunStatsBySuite.push({ suite: suite, stats: teStats }); |
| 211 | + } |
| 212 | + |
| 213 | + return { |
| 214 | + current: this.calculateAutomationQuality(testRunStatsBySuite), |
| 215 | + average: this.calculateAverageAutomationQuality(testRunStatsBySuite) |
| 216 | + }; |
| 217 | + } |
| 218 | + |
| 219 | + calculateAutomationQuality(testRunStatsBySuites: { suite: TestSuite, stats: TestRunStat[] }[]): { quality: number, NA: number, testIssue: number, other: number, appIssue: number, passed: number, total: number } { |
| 220 | + let stat = { quality: 0, NA: 0, testIssue: 0, other: 0, appIssue: 0, passed: 0, total: 0 } |
| 221 | + |
| 222 | + for (const suiteStat of testRunStatsBySuites) { |
| 223 | + if (suiteStat.stats[0]) { |
| 224 | + stat.NA += suiteStat.stats[0].not_assigned; |
| 225 | + stat.appIssue += suiteStat.stats[0].app_issue; |
| 226 | + stat.passed += suiteStat.stats[0].passed; |
| 227 | + stat.testIssue += suiteStat.stats[0].warning; |
| 228 | + stat.other += suiteStat.stats[0].other; |
| 229 | + stat.total += suiteStat.stats[0].total; |
| 230 | + } |
| 231 | + } |
| 232 | + |
| 233 | + stat.quality = stat.total > 0 ? (1 - ((stat.NA * 1 + stat.testIssue * 0.75 + stat.other * 0.5) / (stat.total))) * 100 : 0; |
| 234 | + if (stat.quality < 0) { |
| 235 | + stat.quality = 0; |
| 236 | + } |
| 237 | + |
| 238 | + return stat; |
| 239 | + } |
| 240 | + |
| 241 | + calculateAverageAutomationQuality(testRunStatsBySuite: { suite: TestSuite, stats: TestRunStat[] }[]): number { |
| 242 | + let ttlNA = 0; |
| 243 | + let ttltestIssue = 0; |
| 244 | + let ttlOther = 0; |
| 245 | + let ttl = 0; |
| 246 | + let averageAutomationQuality: number; |
| 247 | + |
| 248 | + for (const suiteStat of testRunStatsBySuite) { |
| 249 | + for (const stat of suiteStat.stats) { |
| 250 | + if (Math.floor(new Date().getTime() - new Date(stat.finish_time).getTime()) / (1000 * 60 * 60 * 24) < 90) { |
| 251 | + ttlNA += stat.not_assigned; |
| 252 | + ttltestIssue += stat.warning; |
| 253 | + ttlOther += stat.other; |
| 254 | + ttl += stat.total; |
| 255 | + } |
| 256 | + } |
| 257 | + } |
| 258 | + averageAutomationQuality = ttl > 0 ? (1 - ((ttlNA * 1 + ttltestIssue * 0.75 + ttlOther * 0.5) / (ttl))) * 100 : 0; |
| 259 | + if (averageAutomationQuality < 0) { |
| 260 | + averageAutomationQuality = 0; |
| 261 | + } |
| 262 | + |
| 263 | + return averageAutomationQuality; |
| 264 | + } |
| 265 | + |
| 266 | + createChartData(data: { quality: number, NA: number, testIssue: number, other: number, appIssue: number, passed: number }) { |
| 267 | + return [{ |
| 268 | + value: data.passed, |
| 269 | + color: '#28A745', |
| 270 | + label: 'Passed' |
| 271 | + }, { |
| 272 | + value: data.appIssue, |
| 273 | + color: '#DC3545', |
| 274 | + label: 'Application Issue' |
| 275 | + }, { |
| 276 | + value: data.testIssue, |
| 277 | + color: '#FFC107', |
| 278 | + label: 'Test Issue' |
| 279 | + }, { |
| 280 | + value: data.other, |
| 281 | + color: '#17A2B8', |
| 282 | + label: 'Other' |
| 283 | + }, { |
| 284 | + value: data.NA, |
| 285 | + color: '#007BFF', |
| 286 | + label: 'Not Assigned' |
| 287 | + }]; |
| 288 | + } |
166 | 289 | }
|
0 commit comments