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

Commit

Permalink
feat(bridge): Link secret creation from secret selection (#8478)
Browse files Browse the repository at this point in the history
* Added scope query parameter to secret creation

Signed-off-by: TannerGilbert <gilberttanner.work@gmail.com>

* Added navigation to create secret page

Signed-off-by: TannerGilbert <gilberttanner.work@gmail.com>

* Updated link to secret creation to work with RouterGuard

Signed-off-by: TannerGilbert <gilberttanner.work@gmail.com>

* Added ChangeGuard. Updated routerLink

Signed-off-by: TannerGilbert <gilberttanner.work@gmail.com>

* Added changeGuard overwrite. Fixed problem with combineLatest. Added ui and unit tests

Signed-off-by: TannerGilbert <gilberttanner.work@gmail.com>

* Removed code smells. Fixed eslint errors

Signed-off-by: TannerGilbert <gilberttanner.work@gmail.com>

* Updated change guard and UI test

Signed-off-by: TannerGilbert <gilberttanner.work@gmail.com>

* v0.0.0

* Updated dialog selection

Signed-off-by: TannerGilbert <gilberttanner.work@gmail.com>

* Removed unwanted version from package.json

Signed-off-by: TannerGilbert <gilberttanner.work@gmail.com>

* Added additional ui tests

Signed-off-by: TannerGilbert <gilberttanner.work@gmail.com>
  • Loading branch information
TannerGilbert committed Aug 2, 2022
1 parent fee5edb commit ecf2b08
Show file tree
Hide file tree
Showing 18 changed files with 318 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</div>

<ng-template #empty>
<div class="container" [innerHTML]="options.emptyText"></div>
<ng-container [ngTemplateOutlet]="options.emptyTemplate"></ng-container>
</ng-template>

<ng-template #overlay>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
OnDestroy,
OnInit,
Output,
TemplateRef,
} from '@angular/core';
import { DtTreeControl, DtTreeDataSource, DtTreeFlattener } from '@dynatrace/barista-components/core';
import { FlatTreeControl } from '@angular/cdk/tree';
Expand All @@ -32,7 +33,8 @@ export class SelectTreeFlatNode implements SelectTreeNode {

export type TreeListSelectOptions = {
headerText: string;
emptyText: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
emptyTemplate: TemplateRef<any> | null;
hintText: string;
};

Expand All @@ -43,7 +45,7 @@ export class KtbTreeListSelectDirective extends KtbOverlay implements OnInit, On
private contentRef: ComponentRef<KtbTreeListSelectComponent> | undefined;

@Input() data: SelectTreeNode[] = [];
@Input() options: TreeListSelectOptions = { headerText: '', emptyText: '', hintText: '' };
@Input() options: TreeListSelectOptions = { headerText: '', emptyTemplate: null, hintText: '' };
@Output() selected: EventEmitter<string> = new EventEmitter<string>();

constructor(protected elementRef: ElementRef, protected overlayService: OverlayService) {
Expand Down Expand Up @@ -109,7 +111,7 @@ export class KtbTreeListSelectComponent {
this.dataSource.data = data;
}

@Input() options: TreeListSelectOptions = { headerText: '', emptyText: '', hintText: '' };
@Input() options: TreeListSelectOptions = { headerText: '', emptyTemplate: null, hintText: '' };

@Output() closeDialog: EventEmitter<void> = new EventEmitter<void>();
@Output() selected: EventEmitter<string> = new EventEmitter<string>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { KtbVariableSelectorModule } from './ktb-variable-selector.module';
import { KtbVariableSelectorComponent } from './ktb-variable-selector.component';
import { RouterTestingModule } from '@angular/router/testing';

describe('KtbSecretSelectorComponent', () => {
describe('KtbVariableSelectorComponent', () => {
const variablePath = '.secret.SecretA.key1';
let component: KtbVariableSelectorComponent;
let fixture: ComponentFixture<KtbVariableSelectorComponent>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Component, EventEmitter, Input, Output, TemplateRef } from '@angular/core';
import { SelectTreeNode, TreeListSelectOptions } from '../ktb-tree-list-select/ktb-tree-list-select.component';
import { AbstractControl } from '@angular/forms';
import { DtIconType } from '@dynatrace/barista-icons';
Expand All @@ -10,7 +10,7 @@ import { DtIconType } from '@dynatrace/barista-icons';
export class KtbVariableSelectorComponent {
public options: TreeListSelectOptions = {
headerText: 'Select element',
emptyText: 'No elements available.',
emptyTemplate: null,
hintText: '',
};
@Output() changed: EventEmitter<void> = new EventEmitter<void>();
Expand All @@ -27,8 +27,9 @@ export class KtbVariableSelectorComponent {
}

@Input()
set emptyText(text: string) {
this.options.emptyText = text;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
set emptyTemplate(template: TemplateRef<any>) {
this.options.emptyTemplate = template;
}

@Input()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,13 +216,29 @@ <h2>Webhook configuration</h2>
</form>

<ng-template #secretButton let-controlName="controlName" let-index="index" let-selectionStart="selectionStart">
<ng-template #secretEmpty>
<div class="container">
No secrets for the scope keptn-webhook-service are available.
<p>
Configure secrets for the scope keptn-webhook-service under the menu entry
<a
uitestid="ktb-webhook-secret-creation-link"
*ngIf="projectName$ | async as projectName"
[routerLink]="['/project', projectName, 'settings', 'uniform', 'secrets', 'add']"
[queryParams]="{ scope: 'keptn-webhook-service' }"
><span class="bold">Secrets</span></a
>
in the Uniform.
</p>
</div>
</ng-template>

<ktb-variable-selector
uitestid="ktb-webhook-secret-selector"
[data]="secretDataSource"
[title]="'Select secret'"
[label]="'Add secret'"
[emptyText]="
'No secrets for the scope keptn-webhook-service are available.<p>Configure secrets for the scope keptn-webhook-service under the menu entry <span class=&quot;bold&quot;>Secrets</span> in the Uniform.</p>'
"
[emptyTemplate]="secretEmpty"
[hintText]="'Only secrets of scope keptn-webhook-secret can be referenced.'"
[control]="getFormControl(controlName, index)"
[selectionStart]="selectionStart"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { IWebhookConfigClient, WebhookConfigMethod } from '../../../../shared/in
import { DtOverlayConfig } from '@dynatrace/barista-components/overlay';
import { SelectTreeNode } from '../ktb-tree-list-select/ktb-tree-list-select.component';
import { IClientSecret } from '../../../../shared/interfaces/secret';
import { ActivatedRoute } from '@angular/router';
import { map } from 'rxjs/operators';

type ControlType = 'method' | 'url' | 'payload' | 'proxy' | 'header' | 'sendFinished' | 'sendStarted';

Expand All @@ -30,6 +32,8 @@ export class KtbWebhookSettingsComponent implements OnInit {
pinnable: true,
originY: 'center',
};
public projectName$ = this.route.paramMap.pipe(map((params) => params.get('projectName')));

public _eventType?: string;
public eventDataSource?: SelectTreeNode[];

Expand Down Expand Up @@ -108,6 +112,7 @@ export class KtbWebhookSettingsComponent implements OnInit {

@Output() validityChanged: EventEmitter<boolean> = new EventEmitter<boolean>();
@Output() webhookChange: EventEmitter<IWebhookConfigClient> = new EventEmitter<IWebhookConfigClient>();
@Output() webhookFormDirty: EventEmitter<boolean> = new EventEmitter<boolean>();

get header(): FormArray {
return this.getFormControl('header') as FormArray;
Expand All @@ -117,7 +122,7 @@ export class KtbWebhookSettingsComponent implements OnInit {
return this.header.controls as FormGroup[];
}

constructor() {
constructor(private route: ActivatedRoute) {
this.webhookConfigForm.statusChanges.subscribe((status: 'INVALID' | 'VALID') => {
this.validityChanged.next(status === 'VALID');
});
Expand All @@ -139,6 +144,7 @@ export class KtbWebhookSettingsComponent implements OnInit {
type: this._webhook?.type ?? '',
};
this.webhookChange.emit(this._webhook);
this.webhookFormDirty.emit(this.webhookConfigForm.dirty);
}

public addHeader(name?: string, value?: string): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { DtIconModule } from '@dynatrace/barista-components/icon';
import { KtbVariableSelectorModule } from '../ktb-variable-selector/ktb-variable-selector.module';
import { DtInputModule } from '@dynatrace/barista-components/input';
import { DtButtonModule } from '@dynatrace/barista-components/button';
import { RouterModule } from '@angular/router';

@NgModule({
declarations: [KtbWebhookSettingsComponent],
Expand All @@ -26,6 +27,7 @@ import { DtButtonModule } from '@dynatrace/barista-components/button';
ReactiveFormsModule,
KtbVariableSelectorModule,
DtInputModule,
RouterModule,
],
exports: [KtbWebhookSettingsComponent],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { NotificationType } from '../../../_models/notification';
import { NotificationsService } from '../../../_services/notifications.service';
import { BehaviorSubject, of } from 'rxjs';
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators';
import { IServiceSecret, SecretKeyValuePair } from '../../../../../shared/interfaces/secret';

Expand Down Expand Up @@ -40,7 +40,15 @@ export class KtbCreateSecretFormComponent implements OnInit {
});

_scopes = new BehaviorSubject<string[]>([]);
scopes$ = this._scopes.asObservable();
scopeQueryParam$ = this.route.queryParams.pipe(map((params) => params.scope));
scopes$: Observable<string[]> = combineLatest([this.scopeQueryParam$, this._scopes.asObservable()]).pipe(
map(([scope, scopes]) => {
if (scopes.includes(scope)) {
this.scopeControl.setValue(scope);
}
return scopes;
})
);
isUpdating = false;
isLoading = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { RouterModule, Routes } from '@angular/router';
import { NgModule } from '@angular/core';
import { KtbIntegrationViewComponent } from './ktb-integration-view.component';
import { KtbModifyUniformSubscriptionComponent } from './ktb-modify-uniform-subscription/ktb-modify-uniform-subscription.component';
import { PendingChangesGuard } from '../../../_guards/pending-changes.guard';

const routes: Routes = [
{ path: '', component: KtbIntegrationViewComponent },
{ path: ':integrationId', component: KtbIntegrationViewComponent },
{
path: ':integrationId/subscriptions/add',
component: KtbModifyUniformSubscriptionComponent,
canDeactivate: [PendingChangesGuard],
},
{
path: ':integrationId/subscriptions/:subscriptionId/edit',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { KtbUniformRegistrationLogsModule } from '../../../_components/ktb-unifo
import { KtbUniformSubscriptionsModule } from '../../../_components/ktb-uniform-subscriptions/ktb-uniform-subscriptions.module';
import { KtbIntegrationViewComponent } from './ktb-integration-view.component';
import { KtbModifyUniformSubscriptionComponent } from './ktb-modify-uniform-subscription/ktb-modify-uniform-subscription.component';
import { DtConfirmationDialogModule } from '@dynatrace/barista-components/confirmation-dialog';

@NgModule({
declarations: [KtbIntegrationViewComponent, KtbModifyUniformSubscriptionComponent],
Expand All @@ -45,6 +46,7 @@ import { KtbModifyUniformSubscriptionComponent } from './ktb-modify-uniform-subs
KtbWebhookSettingsModule,
KtbLoadingModule,
KtbIntegrationViewRoutingModule,
DtConfirmationDialogModule,
],
})
export class KtbIntegrationViewModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ <h2>{{ editMode ? 'Edit' : 'Create' }} subscription</h2>
[eventPayload]="eventPayload"
[eventType]="taskSuffixControl.value"
(validityChanged)="webhookFormValidityChanged($event)"
(webhookFormDirty)="webhookFormDirty = $event"
></ktb-webhook-settings>
<div>
<button
Expand All @@ -118,4 +119,26 @@ <h2>{{ editMode ? 'Edit' : 'Create' }} subscription</h2>
<ng-template #loading>
<ktb-loading-distractor>Loading ...</ktb-loading-distractor>
</ng-template>

<dt-confirmation-dialog
id="unsavedDataDialog"
[state]="unsavedDialogState"
[aria-label]="dialogLabel"
uitestid="ktb-uniform-subscription-confirmation-dialog"
>
<dt-confirmation-dialog-state name="unsaved">
<p [textContent]="message"></p>
<dt-confirmation-dialog-actions>
<button dt-button variant="secondary" (click)="reject()">Stay</button>
<button
dt-button
variant="secondary"
(click)="reset()"
uitestid="ktb-uniform-subscription-confirmation-discard-button"
>
Discard changes and leave page
</button>
</dt-confirmation-dialog-actions>
</dt-confirmation-dialog-state>
</dt-confirmation-dialog>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,17 @@ describe('KtbModifyUniformSubscriptionComponent', () => {
});
});

it('should not allow navigation for unsaved changes', () => {
// given

// when
component.webhookFormDirty = true;
fixture.detectChanges();

// then
expect(component.canDeactivate()).not.toEqual(true);
});

function setSubscription(integrationIndex: number, subscriptionIndex?: number): UniformSubscription {
const dataService = TestBed.inject(DataService);
const uniformRegistration = UniformRegistrationsMock[integrationIndex];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, OnDestroy } from '@angular/core';
import { ChangeDetectorRef, Component, HostListener, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { DataService } from '../../../../_services/data.service';
import { combineLatest, forkJoin, Observable, of, Subject, throwError } from 'rxjs';
Expand All @@ -19,13 +19,14 @@ import { UniformRegistrationInfo } from '../../../../../../shared/interfaces/uni
import { SecretScopeDefault } from '../../../../../../shared/interfaces/secret-scope';
import { EventTypes } from '../../../../../../shared/interfaces/event-types';
import { Trace } from '../../../../_models/trace';
import { PendingChangesComponent } from '../../../../_guards/pending-changes.guard';

@Component({
selector: 'ktb-modify-uniform-subscription',
templateUrl: './ktb-modify-uniform-subscription.component.html',
styleUrls: ['./ktb-modify-uniform-subscription.component.scss'],
})
export class KtbModifyUniformSubscriptionComponent implements OnDestroy {
export class KtbModifyUniformSubscriptionComponent implements OnDestroy, PendingChangesComponent {
private readonly unsubscribe$: Subject<void> = new Subject<void>();
private taskControl = new FormControl('', [Validators.required]);
public eventPayload: Record<string, unknown> | undefined;
Expand All @@ -50,6 +51,7 @@ export class KtbModifyUniformSubscriptionComponent implements OnDestroy {
});
private _previousFilter?: PreviousWebhookConfig;
public isWebhookFormValid = true;
public webhookFormDirty = false;
public isWebhookService = false;
public suffixes: { value: string; displayValue: string }[] = [
{
Expand All @@ -71,6 +73,12 @@ export class KtbModifyUniformSubscriptionComponent implements OnDestroy {
];
public errorMessage?: string;

private pendingChangesSubject = new Subject<boolean>();
public dialogLabel = 'Pending Changes dialog';
public message = 'You have pending changes. Are you sure you want to leave this page?';
public unsavedDialogState: null | 'unsaved' = null;
private isFilterDirty = false;

constructor(
private route: ActivatedRoute,
private dataService: DataService,
Expand Down Expand Up @@ -292,6 +300,9 @@ export class KtbModifyUniformSubscriptionComponent implements OnDestroy {
() => {
this.updating = false;
this.notificationsService.addNotification(NotificationType.SUCCESS, 'Subscription successfully created!');
this.subscriptionForm.reset();
this.webhookFormDirty = false;
this.isFilterDirty = false;
this.router.navigate(['/', 'project', projectName, 'settings', 'uniform', 'integrations', integrationId]);
},
() => {
Expand All @@ -315,6 +326,7 @@ export class KtbModifyUniformSubscriptionComponent implements OnDestroy {
}

public subscriptionFilterChanged(subscription: UniformSubscription, projectName: string): void {
this.isFilterDirty = !!subscription.filter.stages?.length || !!subscription.filter.services?.length;
this.updateIsGlobalCheckbox(subscription);
this.updateEventPayload(projectName, subscription.filter.stages ?? [], subscription.filter.services ?? []);
}
Expand Down Expand Up @@ -346,6 +358,41 @@ export class KtbModifyUniformSubscriptionComponent implements OnDestroy {
this._changeDetectorRef.detectChanges();
}

// @HostListener allows us to also guard against browser refresh, close, etc.
@HostListener('window:beforeunload', ['$event'])
public canDeactivate(_$event?: BeforeUnloadEvent): Observable<boolean> {
if (this.subscriptionForm.dirty || this.webhookFormDirty || this.isFilterDirty) {
this.showNotification();
return this.pendingChangesSubject.asObservable();
}
return of(true);
}

public reject(): void {
this.pendingChangesSubject.next(false);
this.hideNotification();
}

public reset(): void {
this.pendingChangesSubject.next(true);
this.hideNotification();
}

public showNotification(): void {
this.unsavedDialogState = 'unsaved';

const dialog = document.querySelector(`div[aria-label="${this.dialogLabel}"]`);

if (!dialog) return;

dialog.classList.add('shake');
setTimeout(() => dialog.classList.remove('shake'), 500);
}

public hideNotification(): void {
this.unsavedDialogState = null;
}

public ngOnDestroy(): void {
this.unsubscribe$.next();
this.unsubscribe$.complete();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,8 @@ export class KtbProjectSettingsComponent implements OnInit, OnDestroy, PendingCh
}
this.showNotification();
return this.pendingChangesSubject.asObservable();
} else {
return of(true);
}
return of(true);
}

public showNotification(): void {
Expand Down
Loading

0 comments on commit ecf2b08

Please sign in to comment.