From 2b029dc9c6a7febb2071c0cd067cd6171f2e15eb Mon Sep 17 00:00:00 2001 From: DmitryBogatko Date: Sun, 28 Feb 2021 23:21:58 +0300 Subject: [PATCH] Added feature to integrate aq entities with 3rd party systems (#123) * added initial version of feature that provides abilites to add links to tests from others systems * added references to test, issue, testrun * added publish results form * added workflow status support * added publish service to propogate statuses to jira * added ability to add ref on the create issue dialog * added dialog to add/change reference on publish * added returning an empty array if entity id was undefined * added abilities to add/remove ref on the issue create modal * removed redundant components * updated changelog and verion rised to 1.3.0 --- CHANGELOG.md | 3 + package.json | 2 +- src/app/app.component.spec.ts | 35 -- .../main/administration.child.module.ts | 13 +- .../main/administration.child.routing.ts | 12 +- .../main/administration.component.html | 38 +- .../main/administration.component.ts | 11 +- .../main/administration.module.ts | 5 +- .../integrations/integrations.component.html | 18 + .../integrations/integrations.component.scss | 0 .../integrations/integrations.component.ts | 33 ++ .../system-view/system-view.component.html | 32 ++ .../system-view/system-view.component.scss | 0 .../system-view/system-view.component.ts | 29 ++ .../integration-systems.component.html | 58 +++ .../integration-systems.component.scss | 25 ++ .../systems/integration-systems.component.ts | 91 +++++ .../tts-status/tts-status.component.html | 62 ++++ .../tts-status/tts-status.component.scss | 25 ++ .../tts-status/tts-status.component.ts | 123 +++++++ .../workflow-statuses.component.html | 47 +++ .../workflow-statuses.component.scss | 25 ++ .../workflow-statuses.component.ts | 64 ++++ .../projects/project-setting-item.ts | 6 + .../administration.resolutions.component.ts | 8 +- .../issue-create-modal.component.html | 17 +- .../issue-create-modal.component.ts | 35 +- .../issue-view/issue-view.component.html | 21 +- .../issue/issue-view/issue-view.component.ts | 2 + src/app/pages/project/project.module.ts | 30 +- .../dialog-references.component.html | 11 + .../dialog-references.component.scss | 0 .../dialog-references.component.ts | 33 ++ .../int-system-select.component.html | 6 + .../int-system-select.component.scss | 25 ++ .../int-system-select.component.ts | 36 ++ .../references/references.component.html | 47 +++ .../references/references.component.scss | 0 .../references/references.component.ts | 114 ++++++ .../test/test-view/test-view.component.html | 3 + .../test/test-view/test-view.component.ts | 2 + .../dialog-confirm-publish.component.html | 12 + .../dialog-confirm-publish.component.scss | 0 .../dialog-confirm-publish.component.ts | 19 + .../publish-results-modal.component.html | 96 +++++ .../publish-results-modal.component.scss | 102 ++++++ .../publish-results-modal.component.ts | 334 ++++++++++++++++++ .../testrun-view/testrun-view.component.html | 212 ++++++----- .../testrun-view/testrun-view.component.ts | 41 ++- .../services/integrations/publish.service.ts | 23 ++ .../integrations/ref-status.service.ts | 19 + .../integrations/reference.service.ts | 69 ++++ .../integrations/system-type.service.ts | 14 + .../system-workflow-status-service.service.ts | 26 ++ ...em-workflow-status-type-service.service.ts | 14 + .../services/integrations/system.service.ts | 26 ++ .../integrations/tts-status.service.ts | 22 ++ .../services/integrations/tts-type.service.ts | 14 + .../error-interceptor.service.ts | 7 +- .../result-resolution.service.ts | 2 +- src/app/shared/models/final-result.ts | 8 + src/app/shared/models/i-entity-id.ts | 3 + .../integrations/final-resolution-type.ts | 12 + .../models/integrations/final-resolution.ts | 7 + .../shared/models/integrations/pub-entry.ts | 10 + .../shared/models/integrations/pub-item.ts | 13 + .../shared/models/integrations/ref-status.ts | 4 + .../models/integrations/reference-type.ts | 17 + .../shared/models/integrations/reference.ts | 7 + .../shared/models/integrations/system-type.ts | 4 + .../system-workflow-status-type.ts | 13 + .../integrations/system-workflow-status.ts | 7 + src/app/shared/models/integrations/system.ts | 11 + .../integrations/table-creation-status.ts | 3 + .../shared/models/integrations/tts-status.ts | 10 + .../shared/models/integrations/tts-type.ts | 4 + src/app/shared/models/issue.ts | 3 +- src/app/shared/models/resolution-type.ts | 27 ++ src/app/shared/models/test-result.ts | 3 +- 79 files changed, 2173 insertions(+), 192 deletions(-) delete mode 100644 src/app/app.component.spec.ts create mode 100644 src/app/pages/administration/projects/integrations/integrations/integrations.component.html create mode 100644 src/app/pages/administration/projects/integrations/integrations/integrations.component.scss create mode 100644 src/app/pages/administration/projects/integrations/integrations/integrations.component.ts create mode 100644 src/app/pages/administration/projects/integrations/system-view/system-view.component.html create mode 100644 src/app/pages/administration/projects/integrations/system-view/system-view.component.scss create mode 100644 src/app/pages/administration/projects/integrations/system-view/system-view.component.ts create mode 100644 src/app/pages/administration/projects/integrations/systems/integration-systems.component.html create mode 100644 src/app/pages/administration/projects/integrations/systems/integration-systems.component.scss create mode 100644 src/app/pages/administration/projects/integrations/systems/integration-systems.component.ts create mode 100644 src/app/pages/administration/projects/integrations/tts-status/tts-status.component.html create mode 100644 src/app/pages/administration/projects/integrations/tts-status/tts-status.component.scss create mode 100644 src/app/pages/administration/projects/integrations/tts-status/tts-status.component.ts create mode 100644 src/app/pages/administration/projects/integrations/workflow-statuses/workflow-statuses.component.html create mode 100644 src/app/pages/administration/projects/integrations/workflow-statuses/workflow-statuses.component.scss create mode 100644 src/app/pages/administration/projects/integrations/workflow-statuses/workflow-statuses.component.ts create mode 100644 src/app/pages/administration/projects/project-setting-item.ts create mode 100644 src/app/pages/project/references/dialog-references/dialog-references.component.html create mode 100644 src/app/pages/project/references/dialog-references/dialog-references.component.scss create mode 100644 src/app/pages/project/references/dialog-references/dialog-references.component.ts create mode 100644 src/app/pages/project/references/int-system-select/int-system-select.component.html create mode 100644 src/app/pages/project/references/int-system-select/int-system-select.component.scss create mode 100644 src/app/pages/project/references/int-system-select/int-system-select.component.ts create mode 100644 src/app/pages/project/references/references.component.html create mode 100644 src/app/pages/project/references/references.component.scss create mode 100644 src/app/pages/project/references/references.component.ts create mode 100644 src/app/pages/project/testrun/publish-results-modal/dialog-confirm-publish/dialog-confirm-publish.component.html create mode 100644 src/app/pages/project/testrun/publish-results-modal/dialog-confirm-publish/dialog-confirm-publish.component.scss create mode 100644 src/app/pages/project/testrun/publish-results-modal/dialog-confirm-publish/dialog-confirm-publish.component.ts create mode 100644 src/app/pages/project/testrun/publish-results-modal/publish-results-modal.component.html create mode 100644 src/app/pages/project/testrun/publish-results-modal/publish-results-modal.component.scss create mode 100644 src/app/pages/project/testrun/publish-results-modal/publish-results-modal.component.ts create mode 100644 src/app/services/integrations/publish.service.ts create mode 100644 src/app/services/integrations/ref-status.service.ts create mode 100644 src/app/services/integrations/reference.service.ts create mode 100644 src/app/services/integrations/system-type.service.ts create mode 100644 src/app/services/integrations/system-workflow-status-service.service.ts create mode 100644 src/app/services/integrations/system-workflow-status-type-service.service.ts create mode 100644 src/app/services/integrations/system.service.ts create mode 100644 src/app/services/integrations/tts-status.service.ts create mode 100644 src/app/services/integrations/tts-type.service.ts create mode 100644 src/app/shared/models/i-entity-id.ts create mode 100644 src/app/shared/models/integrations/final-resolution-type.ts create mode 100644 src/app/shared/models/integrations/final-resolution.ts create mode 100644 src/app/shared/models/integrations/pub-entry.ts create mode 100644 src/app/shared/models/integrations/pub-item.ts create mode 100644 src/app/shared/models/integrations/ref-status.ts create mode 100644 src/app/shared/models/integrations/reference-type.ts create mode 100644 src/app/shared/models/integrations/reference.ts create mode 100644 src/app/shared/models/integrations/system-type.ts create mode 100644 src/app/shared/models/integrations/system-workflow-status-type.ts create mode 100644 src/app/shared/models/integrations/system-workflow-status.ts create mode 100644 src/app/shared/models/integrations/system.ts create mode 100644 src/app/shared/models/integrations/table-creation-status.ts create mode 100644 src/app/shared/models/integrations/tts-status.ts create mode 100644 src/app/shared/models/integrations/tts-type.ts create mode 100644 src/app/shared/models/resolution-type.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 404c2526..8217aaa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # CHANGELOG +## 1.3.0 (2021-02-26) + - Added feature that allows to link aquality entities (tests, issues and test runs) with 3rd party systems like Jira, Xray, TestRail and etc. In this version only Jira and Xray support has been added. + ## 1.2.2 (2021-01-19) - Fix of an issue with switching between detailed/non-detailed view on the dashboard diff --git a/package.json b/package.json index 3cba36b1..d73dab44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aquality-tracking-ui", - "version": "1.2.2", + "version": "1.3.0", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts deleted file mode 100644 index 7e8e0f86..00000000 --- a/src/app/app.component.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { TestBed, async } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { AppComponent } from './app.component'; - -describe('AppComponent', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - RouterTestingModule - ], - declarations: [ - AppComponent - ], - }).compileComponents(); - })); - - it('should create the app', () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); - }); - - it(`should have as title 'aquality-tracking-ui'`, () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app.title).toEqual('aquality-tracking-ui'); - }); - - it('should render title', () => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.nativeElement; - expect(compiled.querySelector('.content span').textContent).toContain('aquality-tracking-ui app is running!'); - }); -}); diff --git a/src/app/pages/administration/main/administration.child.module.ts b/src/app/pages/administration/main/administration.child.module.ts index d089dae1..2b99a407 100644 --- a/src/app/pages/administration/main/administration.child.module.ts +++ b/src/app/pages/administration/main/administration.child.module.ts @@ -14,7 +14,11 @@ import { PermissionsService } from 'src/app/services/permissions/current-permiss import { UserService } from 'src/app/services/user/user.services'; import { GuardService } from 'src/app/services/guard.service'; import { AdministrationProjectManagerGuard, AdministrationProjectGuard, AdministrationGlobalGuard } from 'src/app/shared/guards/administration-guard.service'; - +import { IntegrationSystemsComponent } from '../projects/integrations/systems/integration-systems.component'; +import { IntegrationsComponent } from '../projects/integrations/integrations/integrations.component'; +import { TtsStatusComponent } from '../projects/integrations/tts-status/tts-status.component'; +import { WorkflowStatusesComponent } from '../projects/integrations/workflow-statuses/workflow-statuses.component'; +import { SystemViewComponent } from '../projects/integrations/system-view/system-view.component'; @NgModule({ imports: [ administrationChildRouting, @@ -27,7 +31,12 @@ import { AdministrationProjectManagerGuard, AdministrationProjectGuard, Administ AdministrationResolutionsComponent, ImportBodyPatternsComponent, APITokenComponent, - AdministrationProjectSettingsComponent + AdministrationProjectSettingsComponent, + IntegrationSystemsComponent, + IntegrationsComponent, + TtsStatusComponent, + WorkflowStatusesComponent, + SystemViewComponent ], providers: [ ProjectService, diff --git a/src/app/pages/administration/main/administration.child.routing.ts b/src/app/pages/administration/main/administration.child.routing.ts index 1c1d2a1f..cf90b7e8 100644 --- a/src/app/pages/administration/main/administration.child.routing.ts +++ b/src/app/pages/administration/main/administration.child.routing.ts @@ -5,6 +5,7 @@ import { APITokenComponent } from '../projects/api-token/api-token.component'; import { AdministrationPermissionsComponent } from '../projects/permissions/administration.permissions.component'; import { AdministrationResolutionsComponent } from '../projects/resolutions/administration.resolutions.component'; import { AdministrationUsersComponent } from '../global/users/administration.users.component'; +import { IntegrationsComponent } from '../projects/integrations/integrations/integrations.component' import { AdministrationProjectManagerGuard, AdministrationGlobalGuard, @@ -18,11 +19,12 @@ const administrationChildRoutes: Routes = [ { path: 'project', canActivate: [AdministrationProjectGuard], children: [ - { path: 'permissions', component: AdministrationPermissionsComponent, canActivate: [AdministrationProjectManagerGuard]}, - { path: 'resolutions', component: AdministrationResolutionsComponent, canActivate: [AdministrationProjectManagerGuard]}, - { path: 'importBodyPatterns', component: ImportBodyPatternsComponent, canActivate: [AdministrationProjectManagerGuard]}, - { path: 'apiToken', component: APITokenComponent, canActivate: [AdministrationProjectManagerGuard]}, - { path: 'projectSettings', component: AdministrationProjectSettingsComponent, canActivate: [AdministrationProjectManagerGuard]} + { path: 'permissions', component: AdministrationPermissionsComponent, canActivate: [AdministrationProjectManagerGuard] }, + { path: 'resolutions', component: AdministrationResolutionsComponent, canActivate: [AdministrationProjectManagerGuard] }, + { path: 'importBodyPatterns', component: ImportBodyPatternsComponent, canActivate: [AdministrationProjectManagerGuard] }, + { path: 'apiToken', component: APITokenComponent, canActivate: [AdministrationProjectManagerGuard] }, + { path: 'projectSettings', component: AdministrationProjectSettingsComponent, canActivate: [AdministrationProjectManagerGuard] }, + { path: 'integrations', component: IntegrationsComponent, canActivate: [AdministrationProjectManagerGuard] } ] }, { path: 'global', canActivate: [AdministrationGlobalGuard], diff --git a/src/app/pages/administration/main/administration.component.html b/src/app/pages/administration/main/administration.component.html index 48b6ba47..00ba3688 100644 --- a/src/app/pages/administration/main/administration.component.html +++ b/src/app/pages/administration/main/administration.component.html @@ -8,40 +8,28 @@ [routerLinkActiveOptions]="{ exact: false }" routerLinkActive="active"> Application Settings - + Users - + - - Settings - - - Permissions - - - Resolutions - - - Unique Body Patterns - - - API Token - + +
+ + {{item.name}} + +
- +
- + \ No newline at end of file diff --git a/src/app/pages/administration/main/administration.component.ts b/src/app/pages/administration/main/administration.component.ts index b125d6f1..e3b6c678 100644 --- a/src/app/pages/administration/main/administration.component.ts +++ b/src/app/pages/administration/main/administration.component.ts @@ -3,6 +3,7 @@ import { Project } from '../../../shared/models/project'; import { ProjectService } from 'src/app/services/project/project.service'; import { ELocalPermissions, PermissionsService, EGlobalPermissions } from 'src/app/services/permissions/current-permissions.service'; import { UserService } from 'src/app/services/user/user.services'; +import { ProjectSettingItem } from '../projects/project-setting-item'; @Component({ templateUrl: './administration.component.html', @@ -16,13 +17,21 @@ export class AdministrationComponent implements OnInit { localManager: boolean; localEditor: boolean; + projectSettingItems: ProjectSettingItem[] = [ + { id: 'projectSettings-administration', name: 'Settings', routerLink: 'project/projectSettings' }, + { id: 'permissions-administration', name: 'Permissions', routerLink: 'project/permissions' }, + { id: 'resolutions-administration', name: 'Resolutions', routerLink: 'project/resolutions' }, + { id: 'body-pattern-administration', name: 'Unique Body Patterns', routerLink: 'project/importBodyPatterns' }, + { id: 'api-token-administration', name: 'API Token', routerLink: 'project/apiToken' }, + { id: 'integrations-administration', name: 'Integrations', routerLink: 'project/integrations' } + ] + constructor( private projectService: ProjectService, public userService: UserService, private permissionsService: PermissionsService ) { } - async ngOnInit() { this.projects = await this.projectService.getProjects(this.project); this.globalEditor = await this.permissionsService.hasPermissions([EGlobalPermissions.admin, EGlobalPermissions.manager]); diff --git a/src/app/pages/administration/main/administration.module.ts b/src/app/pages/administration/main/administration.module.ts index 5bdf48d6..2de5a906 100644 --- a/src/app/pages/administration/main/administration.module.ts +++ b/src/app/pages/administration/main/administration.module.ts @@ -7,6 +7,7 @@ import { ProjectService } from 'src/app/services/project/project.service'; import { UserService } from 'src/app/services/user/user.services'; import { PermissionsService } from 'src/app/services/permissions/current-permissions.service'; import { GuardService } from 'src/app/services/guard.service'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @NgModule({ imports: [ @@ -21,7 +22,9 @@ import { GuardService } from 'src/app/services/guard.service'; UserService, PermissionsService, AdministrationGuard, - GuardService + GuardService, + FormsModule, + ReactiveFormsModule ], }) diff --git a/src/app/pages/administration/projects/integrations/integrations/integrations.component.html b/src/app/pages/administration/projects/integrations/integrations/integrations.component.html new file mode 100644 index 00000000..d3f52d4e --- /dev/null +++ b/src/app/pages/administration/projects/integrations/integrations/integrations.component.html @@ -0,0 +1,18 @@ +
+
+
+ +
+ Project: +
+ +
+ +
+
+
+ + +
\ No newline at end of file diff --git a/src/app/pages/administration/projects/integrations/integrations/integrations.component.scss b/src/app/pages/administration/projects/integrations/integrations/integrations.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/pages/administration/projects/integrations/integrations/integrations.component.ts b/src/app/pages/administration/projects/integrations/integrations/integrations.component.ts new file mode 100644 index 00000000..fe736700 --- /dev/null +++ b/src/app/pages/administration/projects/integrations/integrations/integrations.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit } from '@angular/core'; +import { ProjectService } from 'src/app/services/project/project.service'; +import { System } from 'src/app/shared/models/integrations/system'; +import { Project } from 'src/app/shared/models/project'; + +@Component({ + selector: 'app-integrations', + templateUrl: './integrations.component.html', + styleUrls: ['./integrations.component.scss'] +}) +export class IntegrationsComponent implements OnInit { + + projects: Project[] = []; + selectedProject: Project; + systems: System[] = []; + + constructor(private projectService: ProjectService) {} + + ngOnInit(): void { + this.projectService.getProjects({}).then(projects => { + this.projects = projects; + this.selectedProject = projects[0]; + }); + } + + onProjectChange($event: Project) { + this.selectedProject = $event; + } + + isProjectSelected(): boolean { + return this.selectedProject != undefined; + } +} diff --git a/src/app/pages/administration/projects/integrations/system-view/system-view.component.html b/src/app/pages/administration/projects/integrations/system-view/system-view.component.html new file mode 100644 index 00000000..1c54e80c --- /dev/null +++ b/src/app/pages/administration/projects/integrations/system-view/system-view.component.html @@ -0,0 +1,32 @@ +
+
+
+
+
+
+ {{system.name}} +
+ +
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
\ No newline at end of file diff --git a/src/app/pages/administration/projects/integrations/system-view/system-view.component.scss b/src/app/pages/administration/projects/integrations/system-view/system-view.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/pages/administration/projects/integrations/system-view/system-view.component.ts b/src/app/pages/administration/projects/integrations/system-view/system-view.component.ts new file mode 100644 index 00000000..a9370703 --- /dev/null +++ b/src/app/pages/administration/projects/integrations/system-view/system-view.component.ts @@ -0,0 +1,29 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { SystemService } from 'src/app/services/integrations/system.service'; +import { System } from 'src/app/shared/models/integrations/system'; + +@Component({ + selector: 'app-system-view', + templateUrl: './system-view.component.html', + styleUrls: ['./system-view.component.scss'] +}) +export class SystemViewComponent implements OnInit { + + @Input() projectId: number; + @Input() system: System; + @Output() onDelete = new EventEmitter(); + + constructor( + private systemService: SystemService + ) { } + + ngOnInit(): void { + } + + deleteSystem(system: System) { + this.systemService.delete(this.projectId, system.id).subscribe(() => { + this.onDelete.emit(system); + }) + } + +} diff --git a/src/app/pages/administration/projects/integrations/systems/integration-systems.component.html b/src/app/pages/administration/projects/integrations/systems/integration-systems.component.html new file mode 100644 index 00000000..4c46c960 --- /dev/null +++ b/src/app/pages/administration/projects/integrations/systems/integration-systems.component.html @@ -0,0 +1,58 @@ +
+
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/app/pages/administration/projects/integrations/systems/integration-systems.component.scss b/src/app/pages/administration/projects/integrations/systems/integration-systems.component.scss new file mode 100644 index 00000000..319087b6 --- /dev/null +++ b/src/app/pages/administration/projects/integrations/systems/integration-systems.component.scss @@ -0,0 +1,25 @@ +select { + -webkit-appearance: none; + -moz-appearance: none; + -o-appearance: none; + appearance: none; + background-color: transparent !important; + } + + select::-ms-expand { + display: none; + } + + select + i.fa { + float: right; + margin-top: -20px; + margin-right: 1px; + pointer-events: none; + background-color: transparent; + color: black !important; + padding-right: 5px; + } + + select option { + padding-right: 21px; + } \ No newline at end of file diff --git a/src/app/pages/administration/projects/integrations/systems/integration-systems.component.ts b/src/app/pages/administration/projects/integrations/systems/integration-systems.component.ts new file mode 100644 index 00000000..6f6796d4 --- /dev/null +++ b/src/app/pages/administration/projects/integrations/systems/integration-systems.component.ts @@ -0,0 +1,91 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; +import { SystemTypeService } from 'src/app/services/integrations/system-type.service'; +import { SystemService } from 'src/app/services/integrations/system.service'; +import { TtsTypeService } from 'src/app/services/integrations/tts-type.service'; +import { System } from 'src/app/shared/models/integrations/system'; +import { SystemType } from 'src/app/shared/models/integrations/system-type'; +import { TtsType } from 'src/app/shared/models/integrations/tts-type'; + +@Component({ + selector: 'app-integration-systems', + templateUrl: './integration-systems.component.html', + styleUrls: ['./integration-systems.component.scss'] +}) +export class IntegrationSystemsComponent implements OnInit { + + @Input() projectId: number; + + ngOnChanges() { + this.loadSystems(); + } + + systemTypes: SystemType[] = []; + ttsTypes: TtsType[] = []; + systems: System[] = []; + + addSystemForm: FormGroup; + + constructor( + private systemService: SystemService, + private systemTypeService: SystemTypeService, + private ttsTypeService: TtsTypeService + ) { + } + + ngOnInit(): void { + + this.addSystemForm = new FormGroup( + { + type: new FormControl(''), + ttsType: new FormControl(''), + name: new FormControl(''), + url: new FormControl(''), + username: new FormControl(''), + password: new FormControl(''), + apiToken: new FormControl('') + } + ); + + this.systemTypeService.getTypes().subscribe(types => { + this.systemTypes = types; + this.addSystemForm.controls.type.setValue(types[0]); + }); + + this.ttsTypeService.getTypes().subscribe(types => { + this.ttsTypes = types; + this.addSystemForm.controls.ttsType.setValue(types[0]); + }); + + this.loadSystems(); + } + + public loadSystems(): void { + this.systemService.getAll(this.projectId).subscribe(systems => { + this.systems = systems; + }) + } + + public addSystem() { + let system: System = new System(); + system.name = this.addSystemForm.controls.name.value; + system.url = this.addSystemForm.controls.url.value; + system.username = this.addSystemForm.controls.username.value; + system.password = this.addSystemForm.controls.password.value; + system.api_token = this.addSystemForm.controls.apiToken.value; + system.int_system_type = this.addSystemForm.controls.type.value.id; + system.int_tts_type = this.addSystemForm.controls.ttsType.value.id; + system.project_id = this.projectId; + this.systemService.create(system).subscribe(system => { + this.systems.push(system); + }) + } + + public getSystemTypeName(system: System): string { + return this.systemTypes.filter(type => type.id === system.int_system_type)[0]?.name; + } + + public deleteSystem(system: System) { + this.systems = this.systems.filter(current => (current.id !== system.id)); + } +} diff --git a/src/app/pages/administration/projects/integrations/tts-status/tts-status.component.html b/src/app/pages/administration/projects/integrations/tts-status/tts-status.component.html new file mode 100644 index 00000000..c3e1dbd9 --- /dev/null +++ b/src/app/pages/administration/projects/integrations/tts-status/tts-status.component.html @@ -0,0 +1,62 @@ +
+
+
+ +
+ {{ttsType.name}} +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + +
#TypeResolution TypeStatus NameStatus Id
{{number}}{{status.tts_type_id}}{{getResolutionName(status)}}{{status.status_name}}{{status.status_id}} + +
+
+
diff --git a/src/app/pages/administration/projects/integrations/tts-status/tts-status.component.scss b/src/app/pages/administration/projects/integrations/tts-status/tts-status.component.scss new file mode 100644 index 00000000..a5e05c4b --- /dev/null +++ b/src/app/pages/administration/projects/integrations/tts-status/tts-status.component.scss @@ -0,0 +1,25 @@ +select { + -webkit-appearance: none; + -moz-appearance: none; + -o-appearance: none; + appearance: none; + background-color: transparent !important; +} + +select::-ms-expand { + display: none; +} + +select + i.fa { + float: right; + margin-top: -20px; + margin-right: 1px; + pointer-events: none; + background-color: transparent; + color: black !important; + padding-right: 5px; +} + +select option { + padding-right: 21px; +} \ No newline at end of file diff --git a/src/app/pages/administration/projects/integrations/tts-status/tts-status.component.ts b/src/app/pages/administration/projects/integrations/tts-status/tts-status.component.ts new file mode 100644 index 00000000..443ae712 --- /dev/null +++ b/src/app/pages/administration/projects/integrations/tts-status/tts-status.component.ts @@ -0,0 +1,123 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { forkJoin } from 'rxjs'; +import { FinalResultService } from 'src/app/services/final-result/final_results.service'; +import { TtsStatusService } from 'src/app/services/integrations/tts-status.service'; +import { TtsTypeService } from 'src/app/services/integrations/tts-type.service'; +import { ResultResolutionService } from 'src/app/services/result-resolution/result-resolution.service'; +import { FinalResolution } from 'src/app/shared/models/integrations/final-resolution'; +import { FinalResolutionType, finalResolutionTypes } from 'src/app/shared/models/integrations/final-resolution-type'; +import { System } from 'src/app/shared/models/integrations/system'; +import { TtsStatus } from 'src/app/shared/models/integrations/tts-status'; +import { TtsType } from 'src/app/shared/models/integrations/tts-type'; + +@Component({ + selector: 'app-tts-status', + templateUrl: './tts-status.component.html', + styleUrls: ['./tts-status.component.scss'] +}) +export class TtsStatusComponent implements OnInit { + + @Input() projectId: number; + @Input() system: System; + + ngOnChanges() { + this.loadStatuses(); + } + + addStatusForm: FormGroup; + finalResolutions: FinalResolution[] = []; + statuses: TtsStatus[] = []; + hasStatuses: boolean; + ttsType: TtsType; + + constructor( + private resolutionService: ResultResolutionService, + private finalResultService: FinalResultService, + private ttsTypeService: TtsTypeService, + private ttsStatusService: TtsStatusService + ) { } + + ngOnInit(): void { + this.addStatusForm = new FormGroup({ + finalResolution: new FormControl(''), + name: new FormControl(''), + id: new FormControl('', Validators.pattern("^[0-9]+$")) + }); + + this.ttsTypeService.getTypes().subscribe(types => { + this.ttsType = types.find(type => type.id === this.system.int_tts_type); + }); + + + forkJoin([this.resolutionService.getResolution(this.projectId), + this.finalResultService.getFinalResult({})]).subscribe(([resolutions, finalResults]) => { + + resolutions.forEach(resolution => { + let res: FinalResolution = new FinalResolution(); + res.id = resolution.id; + res.name = resolution.name; + res.type = finalResolutionTypes.Resolution; + this.finalResolutions.push(res); + }); + + finalResults.forEach(finalResult => { + let res: FinalResolution = new FinalResolution(); + res.id = finalResult.id; + res.name = finalResult.name; + res.type = finalResolutionTypes.FinalResult; + this.finalResolutions.push(res); + }); + this.addStatusForm.controls.finalResolution.setValue(this.finalResolutions[0]); + }); + + this.loadStatuses(); + } + + loadStatuses() { + this.ttsStatusService.get(this.projectId, this.system.id).subscribe( + statuses => { + this.statuses = statuses; + this.hasStatuses = true; + } + ); + } + + getResolutionName(status: TtsStatus): string { + let type: FinalResolutionType = status.final_result_id === undefined ? + finalResolutionTypes.Resolution : finalResolutionTypes.FinalResult; + let id: number = type === finalResolutionTypes.Resolution ? status.resolution_id : status.final_result_id; + + return this.finalResolutions.find(res => (res.id === id) && (res.type.name === type.name))?.name; + } + + private isResolutionType(resolution: FinalResolution): boolean { + return resolution.type === finalResolutionTypes.Resolution; + } + + addStatus() { + let status = new TtsStatus(); + status.project_id = this.projectId; + status.int_system_id = this.system.id; + status.tts_type_id = this.system.int_tts_type; + status.status_name = this.addStatusForm.controls.name.value; + status.status_id = this.addStatusForm.controls.id.value; + + let resolution: FinalResolution = this.addStatusForm.controls.finalResolution.value; + if (this.isResolutionType(resolution)) { + status.resolution_id = this.addStatusForm.controls.finalResolution.value.id; + } else { + status.final_result_id = this.addStatusForm.controls.finalResolution.value.id; + } + + this.ttsStatusService.create(status).subscribe(status => { + this.statuses.push(status); + }); + } + + deleteStatus(status: TtsStatus) { + this.ttsStatusService.delete(this.projectId, status.id).subscribe(() => { + this.statuses = this.statuses.filter(currentStatus => currentStatus.id !== status.id); + }); + } +} diff --git a/src/app/pages/administration/projects/integrations/workflow-statuses/workflow-statuses.component.html b/src/app/pages/administration/projects/integrations/workflow-statuses/workflow-statuses.component.html new file mode 100644 index 00000000..b51af393 --- /dev/null +++ b/src/app/pages/administration/projects/integrations/workflow-statuses/workflow-statuses.component.html @@ -0,0 +1,47 @@ +
+
+
+
+ + +
+ +
+ +
+ +
+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + +
#TypeStatus Name
{{number}}{{status.wf_sts_type_id}}{{status.name}} + +
+
+
\ No newline at end of file diff --git a/src/app/pages/administration/projects/integrations/workflow-statuses/workflow-statuses.component.scss b/src/app/pages/administration/projects/integrations/workflow-statuses/workflow-statuses.component.scss new file mode 100644 index 00000000..7dbc05ee --- /dev/null +++ b/src/app/pages/administration/projects/integrations/workflow-statuses/workflow-statuses.component.scss @@ -0,0 +1,25 @@ +select { + -webkit-appearance: none; + -moz-appearance: none; + -o-appearance: none; + appearance: none; + background-color: transparent !important; + } + + select::-ms-expand { + display: none; + } + + select + i.fa { + float: right; + margin-top: -20px; + margin-right: 1px; + pointer-events: none; + background-color: transparent; + color: black !important; + padding-right: 5px; + } + + select option { + padding-right: 21px; + } \ No newline at end of file diff --git a/src/app/pages/administration/projects/integrations/workflow-statuses/workflow-statuses.component.ts b/src/app/pages/administration/projects/integrations/workflow-statuses/workflow-statuses.component.ts new file mode 100644 index 00000000..0ee34125 --- /dev/null +++ b/src/app/pages/administration/projects/integrations/workflow-statuses/workflow-statuses.component.ts @@ -0,0 +1,64 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; +import { stat } from 'fs'; +import { SystemWorkflowStatusServiceService } from 'src/app/services/integrations/system-workflow-status-service.service'; +import { SystemWorkflowStatusTypeServiceService } from 'src/app/services/integrations/system-workflow-status-type-service.service'; +import { System } from 'src/app/shared/models/integrations/system'; +import { SystemWorkflowStatus } from 'src/app/shared/models/integrations/system-workflow-status'; +import { SystemWorkflowStatusType } from 'src/app/shared/models/integrations/system-workflow-status-type'; + +@Component({ + selector: 'app-workflow-statuses', + templateUrl: './workflow-statuses.component.html', + styleUrls: ['./workflow-statuses.component.scss'] +}) +export class WorkflowStatusesComponent implements OnInit { + + @Input() projectId: number; + @Input() system: System; + types: SystemWorkflowStatusType[] = []; + addWorkflowStatusForm: FormGroup; + statuses: SystemWorkflowStatus[] = []; + + constructor( + private wflStatusTypeService: SystemWorkflowStatusTypeServiceService, + private wflStatusService: SystemWorkflowStatusServiceService + ) { } + + ngOnInit(): void { + this.addWorkflowStatusForm = new FormGroup({ + type: new FormControl(''), + name: new FormControl('') + }); + + this.wflStatusTypeService.getTypes().subscribe(types => { + this.types = types; + this.addWorkflowStatusForm.controls.type.setValue(types[0]); + }); + + this.wflStatusService.get(this.projectId, this.system.id).subscribe(statuses => { + this.statuses = statuses; + }) + } + + hasStatuses(): boolean{ + return this.statuses.length > 0; + } + + addWorkflowStatus() { + let status: SystemWorkflowStatus = new SystemWorkflowStatus(); + status.project_id = this.projectId; + status.wf_sts_type_id = this.addWorkflowStatusForm.controls.type.value.id; + status.int_system_id = this.system.id; + status.name = this.addWorkflowStatusForm.controls.name.value; + this.wflStatusService.create(status).subscribe(status => { + this.statuses.push(status); + }) + } + + deleteStatus(status: SystemWorkflowStatus){ + this.wflStatusService.delete(this.projectId, status.id).subscribe(() => { + this.statuses = this.statuses.filter(current => current.id !== status.id); + }) + } +} diff --git a/src/app/pages/administration/projects/project-setting-item.ts b/src/app/pages/administration/projects/project-setting-item.ts new file mode 100644 index 00000000..59b26635 --- /dev/null +++ b/src/app/pages/administration/projects/project-setting-item.ts @@ -0,0 +1,6 @@ +export class ProjectSettingItem { + + id: string; + name: string; + routerLink: string; +} diff --git a/src/app/pages/administration/projects/resolutions/administration.resolutions.component.ts b/src/app/pages/administration/projects/resolutions/administration.resolutions.component.ts index 33656393..e566f9e9 100644 --- a/src/app/pages/administration/projects/resolutions/administration.resolutions.component.ts +++ b/src/app/pages/administration/projects/resolutions/administration.resolutions.component.ts @@ -4,6 +4,7 @@ import { ResultResolution } from 'src/app/shared/models/result-resolution'; import { TFOrder, TFColumn, TFColumnType } from 'src/app/elements/table-filter/tfColumn'; import { ProjectService } from 'src/app/services/project/project.service'; import { ResultResolutionService } from 'src/app/services/result-resolution/result-resolution.service'; +import { resolutionTypesArray } from 'src/app/shared/models/resolution-type'; @Component({ templateUrl: './administration.resolutions.component.html' @@ -16,12 +17,7 @@ export class AdministrationResolutionsComponent implements OnInit { projects: Project[]; selectedProject: Project; resolutions: ResultResolution[]; - colors = [ - { id: 1, title: 'Danger', color: 1 }, - { id: 2, title: 'Warning', color: 2 }, - { id: 3, title: 'Primary', color: 3 }, - { id: 4, title: 'Info', color: 4 }, - { id: 5, title: 'Success', color: 5 }]; + colors = resolutionTypesArray; public sortBy = 'name'; public sortOrder = TFOrder.asc; public tbCols: TFColumn[]; diff --git a/src/app/pages/project/issue/issue-create-modal/issue-create-modal.component.html b/src/app/pages/project/issue/issue-create-modal/issue-create-modal.component.html index 1a1c3060..64eaf844 100644 --- a/src/app/pages/project/issue/issue-create-modal/issue-create-modal.component.html +++ b/src/app/pages/project/issue/issue-create-modal/issue-create-modal.component.html @@ -47,17 +47,23 @@ [disabled]="!canEdit"> +
- - + +
- @@ -98,7 +104,8 @@
+
+ +
Publish With Closed References + + Are you sure you want to make publish issues that have 'Closed' references? + Closed references: +
+ {{ref.key}} {{ref.status}} +
+
+ + + + \ No newline at end of file diff --git a/src/app/pages/project/testrun/publish-results-modal/dialog-confirm-publish/dialog-confirm-publish.component.scss b/src/app/pages/project/testrun/publish-results-modal/dialog-confirm-publish/dialog-confirm-publish.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/pages/project/testrun/publish-results-modal/dialog-confirm-publish/dialog-confirm-publish.component.ts b/src/app/pages/project/testrun/publish-results-modal/dialog-confirm-publish/dialog-confirm-publish.component.ts new file mode 100644 index 00000000..71db19db --- /dev/null +++ b/src/app/pages/project/testrun/publish-results-modal/dialog-confirm-publish/dialog-confirm-publish.component.ts @@ -0,0 +1,19 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { MatDialogRef } from '@angular/material/dialog'; +import { RefStatus } from 'src/app/shared/models/integrations/ref-status'; + +@Component({ + selector: 'app-dialog-confirm-publish', + templateUrl: './dialog-confirm-publish.component.html', + styleUrls: ['./dialog-confirm-publish.component.scss'] +}) +export class DialogConfirmPublishComponent implements OnInit { + + @Input() refs: RefStatus[]; + + constructor(public dialogRef: MatDialogRef) { } + + ngOnInit(): void { + this.dialogRef.updateSize('50%', '50%'); + } +} diff --git a/src/app/pages/project/testrun/publish-results-modal/publish-results-modal.component.html b/src/app/pages/project/testrun/publish-results-modal/publish-results-modal.component.html new file mode 100644 index 00000000..d6c9ae2c --- /dev/null +++ b/src/app/pages/project/testrun/publish-results-modal/publish-results-modal.component.html @@ -0,0 +1,96 @@ + \ No newline at end of file diff --git a/src/app/pages/project/testrun/publish-results-modal/publish-results-modal.component.scss b/src/app/pages/project/testrun/publish-results-modal/publish-results-modal.component.scss new file mode 100644 index 00000000..ef18998a --- /dev/null +++ b/src/app/pages/project/testrun/publish-results-modal/publish-results-modal.component.scss @@ -0,0 +1,102 @@ +.mt-overlay { + display: block; +} + +.show-modal { + opacity: 1; +} + +.modal-dialog { + margin: 9rem auto !important; + max-width: 600px !important; +} + +.me-droppable { + margin: 0; + min-height: 30px; + height: 100%; + border-radius: 5px; +} + +.me-drop-parent { + height: 300px; + overflow: auto; +} + +.me-draggable { + cursor: move; + cursor: grab; + cursor: -moz-grab; + cursor: -webkit-grab; + padding: 10px; + margin-left: 5px; + margin-right: 5px; + border-bottom: solid 1px #e0e0e0; +} + +.me-draggable:active { + cursor: grabbing; + cursor: -moz-grabbing; + cursor: -webkit-grabbing; +} + +.me-padding-left-3 { + padding-left: 3px; +} + +.mc-ignore-item { + color: grey; + cursor: default !important; +} + +.gu-mirror { + position: fixed !important; + margin: 0 !important; + z-index: 9999 !important; + opacity: 0.8; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; + filter: alpha(opacity=80); +} + +.gu-hide { + display: none !important; +} + +.gu-unselectable { + -webkit-user-select: none !important; + -moz-user-select: none !important; + -ms-user-select: none !important; + user-select: none !important; +} + +.gu-transit { + opacity: 0.2; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)"; + filter: alpha(opacity=20); +} + +select { + -webkit-appearance: none; + -moz-appearance: none; + -o-appearance: none; + appearance: none; + background-color: transparent !important; +} + +select::-ms-expand { + display: none; +} + +select + i.fa { + float: right; + margin-top: -20px; + margin-right: 1px; + pointer-events: none; + background-color: transparent; + color: black !important; + padding-right: 5px; +} + +select option { + padding-right: 21px; +} diff --git a/src/app/pages/project/testrun/publish-results-modal/publish-results-modal.component.ts b/src/app/pages/project/testrun/publish-results-modal/publish-results-modal.component.ts new file mode 100644 index 00000000..bb8d69aa --- /dev/null +++ b/src/app/pages/project/testrun/publish-results-modal/publish-results-modal.component.ts @@ -0,0 +1,334 @@ +import { Component, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { ModalComponent } from 'src/app/elements/modals/modal.component'; +import { ReferenceService } from 'src/app/services/integrations/reference.service'; +import { Test } from 'src/app/shared/models/test'; +import { TestResult } from 'src/app/shared/models/test-result'; +import { TestRun } from 'src/app/shared/models/testrun'; +import { Reference } from 'src/app/shared/models/integrations/reference'; +import { ReferenceType, referenceTypes } from 'src/app/shared/models/integrations/reference-type'; +import { ResolutionType, resolutionTypesArray } from 'src/app/shared/models/resolution-type'; +import { Issue } from 'src/app/shared/models/issue'; +import { faTimes } from '@fortawesome/free-solid-svg-icons'; +import { forkJoin } from 'rxjs'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatTableDataSource } from '@angular/material/table'; +import { EventEmitter } from '@angular/core'; +import { NotificationsService } from 'angular2-notifications'; +import { TtsStatusService } from 'src/app/services/integrations/tts-status.service'; +import { TtsStatus } from 'src/app/shared/models/integrations/tts-status'; +import { finalResultNames } from 'src/app/shared/models/final-result'; +import { SystemWorkflowStatusServiceService } from 'src/app/services/integrations/system-workflow-status-service.service'; +import { SystemWorkflowStatus } from 'src/app/shared/models/integrations/system-workflow-status'; +import { workflowStatusTypes } from 'src/app/shared/models/integrations/system-workflow-status-type'; +import { MatDialog } from '@angular/material/dialog'; +import { DialogReferencesComponent } from '../../references/dialog-references/dialog-references.component'; +import { IEntityId } from 'src/app/shared/models/i-entity-id'; +import { PublishService } from 'src/app/services/integrations/publish.service'; +import { PubItem } from 'src/app/shared/models/integrations/pub-item'; +import { RefStatusService } from 'src/app/services/integrations/ref-status.service'; +import { RefStatus } from 'src/app/shared/models/integrations/ref-status'; +import { DialogConfirmPublishComponent } from './dialog-confirm-publish/dialog-confirm-publish.component'; + +@Component({ + selector: 'app-publish-results-modal', + templateUrl: './publish-results-modal.component.html', + styleUrls: ['./publish-results-modal.component.scss'] +}) +export class PublishResultsModalComponent extends ModalComponent implements OnInit { + + @Input() title = 'Publish Result'; + @Input() projectId: number; + @Input() testRun: TestRun; + @Input() runReferences: Reference[]; + @Input() testResults: TestResult[]; + @Output() onCancel = new EventEmitter(); + @Output() onPublish = new EventEmitter(); + + selectedRun: Reference; + testReferences = new Map(); + issueReferences = new Map(); + referenceTypes = referenceTypes; + + icons = { faTimes } + + tableColumns = { + ResultId: new TableColumn('resultId', 'Result Id'), + TestName: new TableColumn('testName', 'Test Name'), + TestRef: new TableColumn('testRef', 'Test Ref'), + Resolution: new TableColumn('resultRes', 'Resolution'), + IssueName: new TableColumn('issueName', 'Issue Name'), + IssueRef: new TableColumn('issueRef', 'Issue Ref') + } + + displayedColumns = [ + this.tableColumns.ResultId.id, + this.tableColumns.TestName.id, + this.tableColumns.TestRef.id, + this.tableColumns.Resolution.id, + this.tableColumns.IssueName.id, + this.tableColumns.IssueRef.id + ]; + dataSource: MatTableDataSource; + data: DataEntry[] = []; + + @ViewChild(MatPaginator) paginator: MatPaginator; + + constructor( + private referenceService: ReferenceService, + private ttsStatusService: TtsStatusService, + private wflStatusService: SystemWorkflowStatusServiceService, + private notificationService: NotificationsService, + private publishService: PublishService, + private refStatusService: RefStatusService, + public dialog: MatDialog + ) { + super(); + } + + ngOnInit(): void { + this.selectedRun = this.runReferences[0]; + forkJoin([ + this.referenceService.getAll(this.projectId, referenceTypes.Test), + this.referenceService.getAll(this.projectId, referenceTypes.Issue) + ] + ).subscribe(([testReferences, issueReferences]) => { + this.testResults.forEach(result => { + let entry = new DataEntry(); + entry.result = result; + entry.test = result.test; + entry.issue = result.issue; + + let findRef = (refereces: Reference[], entityId: number) => + refereces.find(ref => ref.entity_id === entityId); + entry.testRef = findRef(testReferences, result.test.id); + entry.issueRef = findRef(issueReferences, result.issue?.id); + this.data.push(entry); + }) + this.dataSource = new MatTableDataSource(this.data); + this.dataSource.paginator = this.paginator; + this.dataSource.filterPredicate = (entry, value) => { + return entry.result.id.toString().toLowerCase().includes(value) || + entry.test?.name?.toLowerCase().includes(value) || + entry.testRef?.key?.toLowerCase().includes(value) || + entry.issue?.title?.toLowerCase().includes(value) || + entry.issueRef?.key?.toLowerCase().includes(value) + }; + }); + } + + applyFilter(filterValue: string) { + filterValue = filterValue.trim().toLowerCase(); + this.dataSource.filter = filterValue; + } + + hasTestReference(entry: DataEntry): boolean { + return entry.testRef !== undefined; + } + + hasReferencedIssue(entry: DataEntry): boolean { + return this.hasIssue(entry) && this.hasIssueReference(entry); + } + + private hasIssueReference(entry: DataEntry): boolean { + return entry.issueRef != undefined; + } + + hasIssue(entry: DataEntry): boolean { + return entry.issue !== undefined; + } + + getResolutionType(result: TestResult): ResolutionType { + return resolutionTypesArray.find(type => type.color === result.final_result.color); + } + + addTestReference(reference: Reference) { + let entry = this.dataSource.data.find(entry => reference.entity_id === entry.test?.id); + entry.testRef = reference; + entry.edit = false; + } + + addIssueReference(reference: Reference) { + this.dataSource.data.find(entry => reference.entity_id === entry.issue?.id).issueRef = reference; + } + + validateAndPublish() { + let keys: string[] = this.dataSource.data.filter(entry => entry.issueRef !== undefined).map(entry => entry.issueRef.key); + forkJoin( + [ + this.ttsStatusService.get(this.projectId, this.selectedRun.int_system), + this.wflStatusService.get(this.projectId, this.selectedRun.int_system) + ] + ).subscribe(([statuses, wflStatuses]) => { + if ( + this.hasNoInProgress() && + this.hasNoPending() && + this.hasNoMissedTestRef() && + this.hasNoFailedWithoutIssue() && + this.hasNoMissedIssueRef() && + this.hasNoMappedResolutions(statuses) && + this.hasClosedTypeWflStatus(wflStatuses) + ) { + this.refStatusService.getStatuses(this.projectId, this.selectedRun.int_system, keys) + .subscribe(refStatuses => { + let closedIssues = this.getClosedIssues(refStatuses, wflStatuses); + if (closedIssues.length == 0) { + this.publish(statuses); + } else { + const dialogRef = this.dialog.open(DialogConfirmPublishComponent); + dialogRef.componentInstance.refs = closedIssues; + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.publish(statuses); + } + }); + } + }); + } + }); + } + + private publish(statuses: TtsStatus[]) { + let items: PubItem[] = []; + this.dataSource.data.forEach(entry => { + let item: PubItem = new PubItem(); + item.result_id = entry.result.id; + item.test_ref = entry.testRef.key; + item.status = this.getTtsStatus(entry, statuses).status_id; + if (entry.issueRef != undefined) { + item.issue_ref = entry.issueRef.key; + } + items.push(item); + }); + + this.publishService.publish( + items, + this.projectId, + this.selectedRun.int_system, + this.testRun.id, + this.selectedRun.key + ).subscribe(() => { + this.notificationService.success('Success', 'Results were successfully sent'); + this.onPublish.emit(); + }); + } + + private getTtsStatus(entry: DataEntry, statuses: TtsStatus[]): TtsStatus { + if (entry.issue !== undefined) { + return statuses.find(status => status.resolution_id === entry.issue.resolution_id); + } else { + return statuses.find(status => status.final_result_id === entry.result.final_result_id); + } + } + + private getClosedIssues(refStatuses: RefStatus[], workflowStatuses: SystemWorkflowStatus[]): RefStatus[] { + let closeStatuses = workflowStatuses.map(status => status.name.toLowerCase()); + let closedIssues = refStatuses.filter(ref => closeStatuses.includes(ref.status.toLowerCase())); + return closedIssues; + } + + private hasClosedTypeWflStatus(statuses: SystemWorkflowStatus[]): boolean { + let result: boolean = statuses.find(status => status.wf_sts_type_id === workflowStatusTypes.Closed.id) !== undefined; + if (!result) { + this.notificationService.error(`Workflow status is not defined`, + `Wokflow status type ${workflowStatusTypes.Closed.name} is not defined for integration system. Please, configure it in the project -> integrations settings`); + } + return result; + } + + private hasNoMappedResolutions(statuses: TtsStatus[]): boolean { + let resolutions: number[] = statuses.map(status => status.resolution_id); + let finalResults: number[] = statuses.map(status => status.final_result_id); + return this.validateOn(entry => { + if (entry.issue !== undefined) { + return !resolutions.includes(entry.issue?.resolution_id); + } else { + return !finalResults.includes(entry.result.final_result_id); + } + }, 'mapped resolution'); + } + + private hasNoPending(): boolean { + return this.validateOnInComplete(finalResultNames.Pending); + } + + private hasNoInProgress(): boolean { + return this.validateOnInComplete(finalResultNames.InProgress); + } + + private validateOnInComplete(status: string): boolean { + return this.validateOn(entry => { + return (entry.result.final_result?.name.toLowerCase() === status); + }, `result (for ${status})`); + } + + private hasNoFailedWithoutIssue() { + return this.validateOn(entry => { + return (entry.result.final_result?.name.toLowerCase() === finalResultNames.Failed) && + entry.issue === undefined; + }, 'issue'); + } + + private hasNoMissedTestRef(): boolean { + return this.validateOnRef(entry => entry.testRef === undefined, referenceTypes.Test); + } + + private hasNoMissedIssueRef(): boolean { + return this.validateOnRef(entry => entry.issue !== undefined && entry.issueRef === undefined, referenceTypes.Issue); + } + + private validateOnRef(filter: (entry: DataEntry) => boolean, refType: ReferenceType): boolean { + return this.validateOn(filter, `${refType.name} reference`) + } + + private validateOn(filter: (entry: DataEntry) => boolean, missedEntityName: string): boolean { + let invalidEntries = this.dataSource.data.filter(filter); + let size: number = invalidEntries.length; + if (size > 0) { + let maxToDisplay: number = 5; + this.notificationService.error(`Results have missed ${missedEntityName}`, + `Please add ${missedEntityName} to results: ${invalidEntries.map(entry => entry.result.id).slice(0, maxToDisplay).join(',')}${size > maxToDisplay ? ' and others...' : ''}`); + return false; + } + return true; + } + + cancel() { + this.onCancel.emit(); + } + + openAddRefDialog(projectId: number, entity: IEntityId, referenceType: ReferenceType): void { + const dialogRef = this.dialog.open(DialogReferencesComponent); + let instance = dialogRef.componentInstance; + instance.projectId = projectId; + instance.entityId = entity.id; + instance.referenceType = referenceType; + + dialogRef.afterClosed().subscribe(references => { + let ref = references.find(ref => ref.int_system === this.selectedRun.int_system); + if (referenceType.id === referenceTypes.Test.id) { + this.dataSource.data.find(entry => entry.test.id === entity.id).testRef = ref; + } else { + this.dataSource.data.find(entry => entry.issue.id === entity.id).issueRef = ref; + } + }); + } +} + +export class TableColumn { + id: string; + name: string; + + constructor(id: string, name: string) { + this.id = id; + this.name = name; + } +} + +export class DataEntry { + result: TestResult; + test: Test; + testRef?: Reference; + issue?: Issue; + issueRef?: Reference; + edit: boolean; +} diff --git a/src/app/pages/project/testrun/testrun-view/testrun-view.component.html b/src/app/pages/project/testrun/testrun-view/testrun-view.component.html index 41553b73..a62bb68b 100644 --- a/src/app/pages/project/testrun/testrun-view/testrun-view.component.html +++ b/src/app/pages/project/testrun/testrun-view/testrun-view.component.html @@ -7,6 +7,15 @@ [buttons]="[{name:'Download', execute:true }, {name:'Cancel', execute:false}]" (execute)="execute($event)" (closed)="wasClosed()"> + + +
@@ -17,6 +26,11 @@

+ + +