From 89de0e41789be8d0670e56d35bb669995ec0ac7d Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Fri, 14 Jun 2024 13:38:33 +0200 Subject: [PATCH] Refactor frontend code --- .../src/app/diagram/diagram.component.html | 7 +- .../src/app/diagram/diagram.component.spec.ts | 3 +- frontend/src/app/diagram/diagram.component.ts | 59 +++++++------- .../objective-detail.component.ts | 4 +- .../app/overview/overview.component.spec.ts | 28 ++++++- .../src/app/overview/overview.component.ts | 76 ++++++++++--------- .../app/shared/types/enums/ObjectiveState.ts | 6 ++ 7 files changed, 109 insertions(+), 74 deletions(-) create mode 100644 frontend/src/app/shared/types/enums/ObjectiveState.ts diff --git a/frontend/src/app/diagram/diagram.component.html b/frontend/src/app/diagram/diagram.component.html index e6594ae99e..2491b38045 100644 --- a/frontend/src/app/diagram/diagram.component.html +++ b/frontend/src/app/diagram/diagram.component.html @@ -1,6 +1,9 @@ -
+
-
+

Kein Alignment vorhanden

diff --git a/frontend/src/app/diagram/diagram.component.spec.ts b/frontend/src/app/diagram/diagram.component.spec.ts index 2a9d737409..4b6a6b0285 100644 --- a/frontend/src/app/diagram/diagram.component.spec.ts +++ b/frontend/src/app/diagram/diagram.component.spec.ts @@ -44,7 +44,7 @@ describe('DiagramComponent', () => { component.prepareDiagramData(alignmentLists); expect(component.generateNodes).toHaveBeenCalled(); - expect(component.noAlignmentData).toBeFalsy(); + expect(component.alignmentDataCache?.alignmentObjectDtoList.length).not.toEqual(0); }); it('should not call generateElements if alignmentData is empty', () => { @@ -57,7 +57,6 @@ describe('DiagramComponent', () => { component.prepareDiagramData(alignmentLists); expect(component.generateNodes).not.toHaveBeenCalled(); - expect(component.noAlignmentData).toBeTruthy(); }); it('should call prepareDiagramData when Subject receives new data', () => { diff --git a/frontend/src/app/diagram/diagram.component.ts b/frontend/src/app/diagram/diagram.component.ts index 1fa6bc43d7..4883cb248b 100644 --- a/frontend/src/app/diagram/diagram.component.ts +++ b/frontend/src/app/diagram/diagram.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, Input, OnDestroy } from '@angular/core'; -import { map, Observable, Subject, zip } from 'rxjs'; +import { map, Observable, of, Subject, zip } from 'rxjs'; import { AlignmentLists } from '../shared/types/model/AlignmentLists'; import cytoscape from 'cytoscape'; import { @@ -19,6 +19,8 @@ import { KeyResultOrdinal } from '../shared/types/model/KeyResultOrdinal'; import { Router } from '@angular/router'; import { AlignmentObject } from '../shared/types/model/AlignmentObject'; import { AlignmentConnection } from '../shared/types/model/AlignmentConnection'; +import { Zone } from '../shared/types/enums/Zone'; +import { ObjectiveState } from '../shared/types/enums/ObjectiveState'; @Component({ selector: 'app-diagram', @@ -29,7 +31,6 @@ export class DiagramComponent implements AfterViewInit, OnDestroy { private alignmentData$: Subject = new Subject(); cy!: cytoscape.Core; diagramData: any[] = []; - noAlignmentData: boolean = false; alignmentDataCache: AlignmentLists | null = null; constructor( @@ -51,7 +52,7 @@ export class DiagramComponent implements AfterViewInit, OnDestroy { let lastAlignmentItem: AlignmentObject = alignmentData.alignmentObjectDtoList[alignmentData.alignmentObjectDtoList.length - 1]; - let diagramReloadRequired: boolean = + const diagramReloadRequired: boolean = lastAlignmentItem?.objectTitle === 'reload' ? lastAlignmentItem?.objectType === 'true' : JSON.stringify(this.alignmentDataCache) !== JSON.stringify(alignmentData); @@ -70,6 +71,7 @@ export class DiagramComponent implements AfterViewInit, OnDestroy { ngOnDestroy(): void { this.cleanUpDiagram(); + this.alignmentData.unsubscribe(); } generateDiagram(): void { @@ -140,10 +142,7 @@ export class DiagramComponent implements AfterViewInit, OnDestroy { } prepareDiagramData(alignmentData: AlignmentLists): void { - if (alignmentData.alignmentObjectDtoList.length == 0) { - this.noAlignmentData = true; - } else { - this.noAlignmentData = false; + if (alignmentData.alignmentObjectDtoList.length != 0) { this.generateNodes(alignmentData); } } @@ -153,24 +152,20 @@ export class DiagramComponent implements AfterViewInit, OnDestroy { let diagramElements: any[] = []; alignmentData.alignmentObjectDtoList.forEach((alignmentObject: AlignmentObject) => { if (alignmentObject.objectType == 'objective') { - let observable: Observable = new Observable((observer) => { - let node = { - data: { - id: 'Ob' + alignmentObject.objectId, - }, - style: { - 'background-image': this.generateObjectiveSVG( - alignmentObject.objectTitle, - alignmentObject.objectTeamName, - alignmentObject.objectState!, - ), - }, - }; - diagramElements.push(node); - observer.next(node); - observer.complete(); - }); - observableArray.push(observable); + let node = { + data: { + id: 'Ob' + alignmentObject.objectId, + }, + style: { + 'background-image': this.generateObjectiveSVG( + alignmentObject.objectTitle, + alignmentObject.objectTeamName, + alignmentObject.objectState!, + ), + }, + }; + diagramElements.push(node); + observableArray.push(of(node)); } else { let observable: Observable = this.keyResultService.getFullKeyResult(alignmentObject.objectId).pipe( map((keyResult: KeyResult) => { @@ -248,11 +243,11 @@ export class DiagramComponent implements AfterViewInit, OnDestroy { generateObjectiveSVG(title: string, teamName: string, state: string): string { switch (state) { - case 'ONGOING': + case ObjectiveState.ONGOING: return generateObjectiveSVG(title, teamName, getOnGoingIcon); - case 'SUCCESSFUL': + case ObjectiveState.SUCCESSFUL: return generateObjectiveSVG(title, teamName, getSuccessfulIcon); - case 'NOTSUCCESSFUL': + case ObjectiveState.NOTSUCCESSFUL: return generateObjectiveSVG(title, teamName, getNotSuccessfulIcon); default: return generateObjectiveSVG(title, teamName, getDraftIcon); @@ -261,13 +256,13 @@ export class DiagramComponent implements AfterViewInit, OnDestroy { generateKeyResultSVG(title: string, teamName: string, state: string | undefined): string { switch (state) { - case 'FAIL': + case Zone.FAIL: return generateKeyResultSVG(title, teamName, '#BA3838', 'white'); - case 'COMMIT': + case Zone.COMMIT: return generateKeyResultSVG(title, teamName, '#FFD600', 'black'); - case 'TARGET': + case Zone.TARGET: return generateKeyResultSVG(title, teamName, '#1E8A29', 'black'); - case 'STRETCH': + case Zone.STRETCH: return generateKeyResultSVG(title, teamName, '#1E5A96', 'white'); default: return generateNeutralKeyResultSVG(title, teamName); diff --git a/frontend/src/app/objective-detail/objective-detail.component.ts b/frontend/src/app/objective-detail/objective-detail.component.ts index 03bd8735e3..747bd70ea0 100644 --- a/frontend/src/app/objective-detail/objective-detail.component.ts +++ b/frontend/src/app/objective-detail/objective-detail.component.ts @@ -65,11 +65,9 @@ export class ObjectiveDetailComponent { .subscribe((result) => { if (result?.openNew) { this.openAddKeyResultDialog(); - } else if (result == '' || result == undefined) { return; - } else { - this.refreshDataService.markDataRefresh(); } + this.refreshDataService.markDataRefresh(); }); } diff --git a/frontend/src/app/overview/overview.component.spec.ts b/frontend/src/app/overview/overview.component.spec.ts index e38f09d652..c047057d24 100644 --- a/frontend/src/app/overview/overview.component.spec.ts +++ b/frontend/src/app/overview/overview.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { OverviewComponent } from './overview.component'; import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { overViewEntity1 } from '../shared/testData'; +import { alignmentLists, overViewEntity1 } from '../shared/testData'; import { BehaviorSubject, of, Subject } from 'rxjs'; import { OverviewService } from '../shared/services/overview.service'; import { AppRoutingModule } from '../app-routing.module'; @@ -16,11 +16,16 @@ import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { AlignmentService } from '../shared/services/alignment.service'; const overviewService = { getOverview: jest.fn(), }; +const alignmentService = { + getAlignmentByFilter: jest.fn(), +}; + const authGuardMock = () => { return Promise.resolve(true); }; @@ -53,6 +58,10 @@ describe('OverviewComponent', () => { provide: OverviewService, useValue: overviewService, }, + { + provide: AlignmentService, + useValue: alignmentService, + }, { provide: authGuard, useValue: authGuardMock, @@ -132,6 +141,23 @@ describe('OverviewComponent', () => { expect(component.loadOverview).toHaveBeenLastCalledWith(); }); + it('should call overviewService on overview', async () => { + jest.spyOn(overviewService, 'getOverview'); + component.isOverview = true; + + component.loadOverview(3, [5, 6], '', null); + expect(overviewService.getOverview).toHaveBeenCalled(); + }); + + it('should call alignmentService on diagram', async () => { + jest.spyOn(alignmentService, 'getAlignmentByFilter').mockReturnValue(of(alignmentLists)); + component.isOverview = false; + fixture.detectChanges(); + + component.loadOverview(3, [5, 6], '', null); + expect(alignmentService.getAlignmentByFilter).toHaveBeenCalled(); + }); + function markFiltersAsReady() { refreshDataServiceMock.quarterFilterReady.next(null); refreshDataServiceMock.teamFilterReady.next(null); diff --git a/frontend/src/app/overview/overview.component.ts b/frontend/src/app/overview/overview.component.ts index 25ac21ff80..cc97822235 100644 --- a/frontend/src/app/overview/overview.component.ts +++ b/frontend/src/app/overview/overview.component.ts @@ -69,45 +69,53 @@ export class OverviewComponent implements OnInit, OnDestroy { this.loadOverview(quarterId, teamIds, objectiveQueryString, reload); } - loadOverview(quarterId?: number, teamIds?: number[], objectiveQuery?: string, reload?: boolean | null) { + loadOverview(quarterId?: number, teamIds?: number[], objectiveQuery?: string, reload?: boolean | null): void { if (this.isOverview) { - this.overviewService - .getOverview(quarterId, teamIds, objectiveQuery) - .pipe( - catchError(() => { - this.loadOverview(); - return EMPTY; - }), - ) - .subscribe((dashboard) => { - this.hasAdminAccess.next(dashboard.adminAccess); - this.overviewEntities$.next(dashboard.overviews); - }); + this.loadOverviewData(quarterId, teamIds, objectiveQuery); } else { - this.alignmentService - .getAlignmentByFilter(quarterId, teamIds, objectiveQuery) - .pipe( - catchError(() => { - this.loadOverview(); - return EMPTY; - }), - ) - .subscribe((alignmentLists: AlignmentLists) => { - if (reload != null) { - let alignmentObjectReload: AlignmentObject = { - objectId: 0, - objectTitle: 'reload', - objectType: reload.toString(), - objectTeamName: '', - objectState: null, - }; - alignmentLists.alignmentObjectDtoList.push(alignmentObjectReload); - } - this.alignmentLists$.next(alignmentLists); - }); + this.loadAlignmentData(quarterId, teamIds, objectiveQuery, reload); } } + loadOverviewData(quarterId?: number, teamIds?: number[], objectiveQuery?: string): void { + this.overviewService + .getOverview(quarterId, teamIds, objectiveQuery) + .pipe( + catchError(() => { + this.loadOverview(); + return EMPTY; + }), + ) + .subscribe((dashboard) => { + this.hasAdminAccess.next(dashboard.adminAccess); + this.overviewEntities$.next(dashboard.overviews); + }); + } + + loadAlignmentData(quarterId?: number, teamIds?: number[], objectiveQuery?: string, reload?: boolean | null): void { + this.alignmentService + .getAlignmentByFilter(quarterId, teamIds, objectiveQuery) + .pipe( + catchError(() => { + this.loadOverview(); + return EMPTY; + }), + ) + .subscribe((alignmentLists: AlignmentLists) => { + if (reload != null) { + let alignmentObjectReload: AlignmentObject = { + objectId: 0, + objectTitle: 'reload', + objectType: reload.toString(), + objectTeamName: '', + objectState: null, + }; + alignmentLists.alignmentObjectDtoList.push(alignmentObjectReload); + } + this.alignmentLists$.next(alignmentLists); + }); + } + ngOnDestroy(): void { this.destroyed$.next(true); this.destroyed$.complete(); diff --git a/frontend/src/app/shared/types/enums/ObjectiveState.ts b/frontend/src/app/shared/types/enums/ObjectiveState.ts new file mode 100644 index 0000000000..342fa5a49d --- /dev/null +++ b/frontend/src/app/shared/types/enums/ObjectiveState.ts @@ -0,0 +1,6 @@ +export enum ObjectiveState { + DRAFT = 'DRAFT', + ONGOING = 'ONGOING', + SUCCESSFUL = 'SUCCESSFUL', + NOTSUCCESSFUL = 'NOTSUCCESSFUL', +}