Skip to content
This repository has been archived by the owner on Dec 21, 2023. It is now read-only.

refactor(bridge): Remove global project polling and remove project dependency in integrations view #8412

Merged
merged 10 commits into from Jul 27, 2022
@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
import { Trace } from '../../_models/trace';
import { DataService } from '../../_services/data.service';
import { DtOverlayConfig } from '@dynatrace/barista-components/overlay';
Expand All @@ -9,6 +9,7 @@ import { KeptnService } from '../../../../shared/models/keptn-service';
selector: 'ktb-approval-item[event]',
templateUrl: './ktb-approval-item.component.html',
styleUrls: ['./ktb-approval-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class KtbApprovalItemComponent {
public _event?: Trace;
Expand Down Expand Up @@ -39,7 +40,7 @@ export class KtbApprovalItemComponent {
this.loadEvaluation(value);
}

constructor(private dataService: DataService) {}
constructor(private dataService: DataService, private changeDetectorRef_: ChangeDetectorRef) {}
renepanzar marked this conversation as resolved.
Show resolved Hide resolved

private loadEvaluation(trace: Trace): void {
this.dataService
Expand All @@ -53,6 +54,7 @@ export class KtbApprovalItemComponent {
.subscribe((evaluation) => {
this.evaluation = evaluation[0];
this.evaluationExists = !!this.evaluation;
this.changeDetectorRef_.markForCheck();
});
}

Expand All @@ -61,5 +63,6 @@ export class KtbApprovalItemComponent {
this.approvalSent.emit();
});
this.approvalResult = result;
this.changeDetectorRef_.markForCheck();
}
}
@@ -1,4 +1,4 @@
import { Component, Input, NgZone, OnDestroy, TemplateRef, ViewChild } from '@angular/core';
import { ChangeDetectorRef, Component, Input, NgZone, OnDestroy, TemplateRef, ViewChild } from '@angular/core';
import { DtOverlay, DtOverlayConfig, DtOverlayRef } from '@dynatrace/barista-components/overlay';
import { Trace } from '../../_models/trace';
import { ResultTypes } from '../../../../shared/models/result-types';
Expand Down Expand Up @@ -83,7 +83,12 @@ export class KtbEvaluationInfoComponent implements OnDestroy {
);
}

constructor(private dataService: DataService, private ngZone: NgZone, private _dtOverlay: DtOverlay) {}
constructor(
private dataService: DataService,
private ngZone: NgZone,
private _dtOverlay: DtOverlay,
private changeDetectorRef_: ChangeDetectorRef
) {}

private fetchEvaluationHistory(): void {
const evaluation = this.evaluation;
Expand Down Expand Up @@ -112,6 +117,7 @@ export class KtbEvaluationInfoComponent implements OnDestroy {
} else {
this._evaluationHistory = traces;
}
this.changeDetectorRef_.markForCheck();
});
}
}
Expand Down
@@ -1,4 +1,4 @@
<ng-container *ngIf="project && subscription as subscription">
<ng-container *ngIf="projectName && subscription as subscription">
<span class="pl-3 pr-4" [textContent]="name"></span>
<span class="bold pl-3 pr-2">subscribes to:</span>
<span class="mr-1 pr-3" [textContent]="subscription.formattedEvent"></span>
Expand Down
Expand Up @@ -48,16 +48,16 @@ describe('KtbSubscriptionItemComponent', () => {
subscription.id = 'mySubscriptionId';
});

it('should create and have given project set', () => {
it('should create', () => {
expect(component).toBeTruthy();
expect(component.project?.projectName).toEqual('sockshop');
});

it('should navigate to subscription to edit', () => {
// given
const router = TestBed.inject(Router);
const routerSpy = jest.spyOn(router, 'navigate');
component.integrationId = 'myIntegrationId';
component.projectName = 'sockshop';

// when
component.editSubscription(subscription);
Expand Down
@@ -1,38 +1,25 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
} from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
import { UniformSubscription } from '../../_models/uniform-subscription';
import { filter, map, switchMap, takeUntil } from 'rxjs/operators';
import { DataService } from '../../_services/data.service';
import { ActivatedRoute, Router } from '@angular/router';
import { Project } from '../../_models/project';
import { Subject } from 'rxjs';
import { DeleteDialogState } from '../_dialogs/ktb-delete-confirmation/ktb-delete-confirmation.component';

@Component({
selector: 'ktb-subscription-item[subscription][integrationId][isWebhookService]',
selector: 'ktb-subscription-item[subscription][integrationId][isWebhookService][projectName]',
templateUrl: './ktb-subscription-item.component.html',
styleUrls: ['./ktb-subscription-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class KtbSubscriptionItemComponent implements OnInit, OnDestroy {
export class KtbSubscriptionItemComponent {
private _subscription?: UniformSubscription;
public project?: Project;
private readonly unsubscribe$ = new Subject<void>();
private currentSubscription?: UniformSubscription;
public deleteState: DeleteDialogState = null;

@Output() subscriptionDeleted: EventEmitter<UniformSubscription> = new EventEmitter<UniformSubscription>();
@Input() name?: string;
@Input() integrationId?: string;
@Input() isWebhookService = false;
@Input() projectName = '';

@Input()
get subscription(): UniformSubscription | undefined {
Expand All @@ -53,25 +40,11 @@ export class KtbSubscriptionItemComponent implements OnInit, OnDestroy {
private router: Router
) {}

ngOnInit(): void {
this.route.paramMap
.pipe(
map((params) => params.get('projectName')),
filter((projectName: string | null): projectName is string => !!projectName),
switchMap((projectName) => this.dataService.getProject(projectName)),
filter((project: Project | undefined): project is Project => !!project),
takeUntil(this.unsubscribe$)
)
.subscribe((project) => {
this.project = project;
});
}

public editSubscription(subscription: UniformSubscription): void {
this.router.navigate([
'/',
'project',
this.project?.projectName,
this.projectName,
'settings',
'uniform',
'integrations',
Expand All @@ -97,9 +70,4 @@ export class KtbSubscriptionItemComponent implements OnInit, OnDestroy {
});
}
}

ngOnDestroy(): void {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
}
Expand Up @@ -36,6 +36,7 @@
[subscription]="subscription"
[integrationId]="uniformRegistration.id"
[isWebhookService]="uniformRegistration.isWebhookService"
[projectName]="projectName"
name="Subscription {{ index + 1 }}"
(subscriptionDeleted)="deleteSubscription($event)"
></ktb-subscription-item>
Expand Down
28 changes: 0 additions & 28 deletions bridge/client/app/_models/project.mock.ts

This file was deleted.

21 changes: 21 additions & 0 deletions bridge/client/app/_services/data.service.spec.ts
Expand Up @@ -149,6 +149,27 @@ describe('DataService', () => {
expect(loadLogSpy).not.toHaveBeenCalled();
});

it('should correctly set isTriggerSequenceOpen', async () => {
// given, when
let value = await firstValueFrom(dataService.isTriggerSequenceOpen);
// then
expect(value).toBe(false);

// when
dataService.setIsTriggerSequenceOpen(true);

// then
value = await firstValueFrom(dataService.isTriggerSequenceOpen);
expect(value).toBe(true);

// when
dataService.setIsTriggerSequenceOpen(false);
value = await firstValueFrom(dataService.isTriggerSequenceOpen);

// then
expect(value).toBe(false);
});

function setGetTracesResponse(traces: Trace[]): void {
const response = new HttpResponse({ body: { events: traces, totalCount: 0, pageSize: 1, nextPageKey: 1 } });
jest.spyOn(apiService, 'getTraces').mockReturnValue(of(response));
Expand Down
11 changes: 9 additions & 2 deletions bridge/client/app/_services/data.service.ts
Expand Up @@ -57,8 +57,7 @@ export class DataService {
private readonly DEFAULT_NEXT_SEQUENCE_PAGE_SIZE = 10;
private _isQualityGatesOnly = new BehaviorSubject<boolean>(false);
private _evaluationResults = new Subject<EvaluationHistory>();

public isTriggerSequenceOpen = false;
private _isTriggerSequenceOpen = new BehaviorSubject<boolean>(false);

constructor(private apiService: ApiService) {}

Expand Down Expand Up @@ -86,6 +85,14 @@ export class DataService {
return this._isQualityGatesOnly.asObservable();
}

get isTriggerSequenceOpen(): Observable<boolean> {
return this._isTriggerSequenceOpen.asObservable();
}

public setIsTriggerSequenceOpen(status: boolean): void {
this._isTriggerSequenceOpen.next(status);
}

get projectName(): Observable<string> {
return this._projectName.asObservable();
}
Expand Down
Expand Up @@ -2,10 +2,20 @@
<ng-container *ngIf="project$ | async as project; else loading">
<ktb-stage-overview
fxFlex="70"
(selectedStageChange)="stageDetails.selectStage($event)"
[project]="project"
[(selectedStageInfo)]="selectedStageInfo"
Kirdock marked this conversation as resolved.
Show resolved Hide resolved
(selectedStageInfoChange)="setLocation(project.projectName, $event)"
renepanzar marked this conversation as resolved.
Show resolved Hide resolved
(filteredServicesChange)="stageDetails.filteredServices = $event"
[isTriggerSequenceOpen]="!!(isTriggerSequenceOpen$ | async)"
></ktb-stage-overview>
<ktb-stage-details fxFlex="30" #stageDetails [project]="project"></ktb-stage-details>
<ktb-stage-details
fxFlex="30"
#stageDetails
[isQualityGatesOnly]="!!(isQualityGatesOnly$ | async)"
[project]="project"
[(selectedStageInfo)]="selectedStageInfo"
(selectedStageInfoChange)="setLocation(project.projectName, $event)"
></ktb-stage-details>
</ng-container>
</div>
<ng-template #loading>
Expand Down
Expand Up @@ -3,22 +3,104 @@ import { KtbEnvironmentViewComponent } from './ktb-environment-view.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { KtbEnvironmentViewModule } from './ktb-environment-view.module';
import { RouterTestingModule } from '@angular/router/testing';
import { POLLING_INTERVAL_MILLIS } from '../../_utils/app.utils';
import { Location } from '@angular/common';
import { Stage } from '../../_models/stage';
import { ActivatedRoute, convertToParamMap, ParamMap } from '@angular/router';
import { BehaviorSubject } from 'rxjs';
import { DataService } from '../../_services/data.service';
import { ApiService } from '../../_services/api.service';
import { ApiServiceMock } from '../../_services/api.service.mock';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

describe('KtbEnvironmentViewComponent', () => {
let component: KtbEnvironmentViewComponent;
let fixture: ComponentFixture<KtbEnvironmentViewComponent>;
let queryParamMap: BehaviorSubject<ParamMap>;
let paramMap: BehaviorSubject<ParamMap>;
let dataService: DataService;

beforeEach(async () => {
queryParamMap = new BehaviorSubject<ParamMap>(convertToParamMap({}));
paramMap = new BehaviorSubject<ParamMap>(convertToParamMap({}));
await TestBed.configureTestingModule({
imports: [KtbEnvironmentViewModule, RouterTestingModule, HttpClientTestingModule],
imports: [KtbEnvironmentViewModule, RouterTestingModule, BrowserAnimationsModule, HttpClientTestingModule],
providers: [
{
provide: ActivatedRoute,
useValue: {
queryParamMap: queryParamMap.asObservable(),
paramMap: paramMap.asObservable(),
},
},
{
provide: ApiService,
useClass: ApiServiceMock,
},
{ provide: POLLING_INTERVAL_MILLIS, useValue: 0 },
],
}).compileComponents();

fixture = TestBed.createComponent(KtbEnvironmentViewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
dataService = TestBed.inject(DataService);
});

it('should create', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
});

it('should set the right location', () => {
// given
const locationSpy = jest.spyOn(TestBed.inject(Location), 'go');

// when
component.setLocation('sockshop', { stage: { stageName: 'myStage' } as Stage, filterType: 'approval' });

// then
expect(locationSpy).toHaveBeenCalledWith('/project/sockshop/environment/stage/myStage?filterType=approval');
});

it('should set the right location without filterType', () => {
// given
const locationSpy = jest.spyOn(TestBed.inject(Location), 'go');

// when
component.setLocation('sockshop', { stage: { stageName: 'myStage' } as Stage, filterType: undefined });

// then
expect(locationSpy).toHaveBeenCalledWith('/project/sockshop/environment/stage/myStage');
});

it('should load the project', () => {
// given
const loadSpy = jest.spyOn(dataService, 'loadProject');

// when
paramMap.next(convertToParamMap({ projectName: 'sockshop' }));
fixture.detectChanges();

// then
expect(loadSpy).toHaveBeenCalledWith('sockshop');
});

it('should set the selected stage through params', () => {
// given, when
paramMap.next(convertToParamMap({ projectName: 'sockshop', stageName: 'dev' }));

// then
expect(component.selectedStageInfo?.stage.stageName).toBe('dev');
expect(component.selectedStageInfo?.filterType).toBeUndefined();
});

it('should set the selected stage with filterType through params', () => {
// given, when
queryParamMap.next(convertToParamMap({ filterType: 'approval' }));
paramMap.next(convertToParamMap({ projectName: 'sockshop', stageName: 'dev' }));

// then
expect(component.selectedStageInfo?.stage.stageName).toBe('dev');
expect(component.selectedStageInfo?.filterType).toBe('approval');
});
});