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

Commit

Permalink
feat(bridge): Add checkbox to set the sendFinished flag (#5735) (#5989
Browse files Browse the repository at this point in the history
)

* add sendFinished to interface/model

Signed-off-by: ermin.muratovic <[email protected]>

* add checkbox to webhook settings form

Signed-off-by: ermin.muratovic <[email protected]>

* store and read sendFinished in webhook.yaml

Signed-off-by: ermin.muratovic <[email protected]>

* show info with details and link to docs

Signed-off-by: ermin.muratovic <[email protected]>

* make sendFinished by default true

Signed-off-by: ermin.muratovic <[email protected]>

* update info message

Signed-off-by: ermin.muratovic <[email protected]>

* sendFinished checkbox should only be enabled for triggered events

Signed-off-by: ermin.muratovic <[email protected]>

* set sendFinished on triggered only

Signed-off-by: ermin.muratovic <[email protected]>

* default sendFinished true

Signed-off-by: ermin.muratovic <[email protected]>

* disable checkbox via formControl

Signed-off-by: ermin.muratovic <[email protected]>

* when sendFinished does not exist in yaml fallback to false

Signed-off-by: ermin.muratovic <[email protected]>

* update already existing webhooks sendFinished

Signed-off-by: ermin.muratovic <[email protected]>

* default value is already in model defined

Signed-off-by: ermin.muratovic <[email protected]>

* add line separator

Signed-off-by: ermin.muratovic <[email protected]>

* make label of disabled checkbox lighter color

Signed-off-by: ermin.muratovic <[email protected]>

* make input radio and enabled for '*' and 'triggered'

Signed-off-by: ermin.muratovic <[email protected]>

* trigger form change on radio change

Signed-off-by: ermin.muratovic <[email protected]>

* fix tests

Signed-off-by: ermin.muratovic <[email protected]>

* fix tests

Signed-off-by: ermin.muratovic <[email protected]>

* update sendFinished control correctly

Signed-off-by: ermin.muratovic <[email protected]>

* make webhook lowercase

Signed-off-by: ermin.muratovic <[email protected]>

* fix tests

Signed-off-by: ermin.muratovic <[email protected]>
  • Loading branch information
ermin-muratovic authored Nov 17, 2021
1 parent 5e779eb commit 89598f8
Show file tree
Hide file tree
Showing 13 changed files with 185 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ <h2>{{ editMode ? 'Edit' : 'Create' }} subscription</h2>
*ngIf="isWebhookService"
[(webhook)]="data.webhook"
[secrets]="data.webhookSecrets"
[eventType]="taskSuffixControl.value"
(validityChanged)="webhookFormValidityChanged($event)"
></ktb-webhook-settings>
<div fxLayout="row">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { SecretScope } from '../../../../shared/interfaces/secret-scope';
export class KtbModifyUniformSubscriptionComponent implements OnDestroy {
private readonly unsubscribe$: Subject<void> = new Subject<void>();
private taskControl = new FormControl('', [Validators.required]);
private taskSuffixControl = new FormControl('', [Validators.required]);
public taskSuffixControl = new FormControl('', [Validators.required]);
private isGlobalControl = new FormControl();
public data$: Observable<{
taskNames: string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,27 @@ <h2>Webhook configuration</h2>
<dt-error *ngIf="getFormControl('proxy').errors?.url">URL must start with http(s)://</dt-error>
</dt-form-field>
</div>
<div class="mb-1" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="15px">
<dt-form-field uitestid="edit-webhook-field-sendFinished" class="send-finished-input">
<dt-label>Send finished event</dt-label>
<dt-radio-group name="sendFinished" formControlName="sendFinished" (change)="onWebhookFormChange()">
<dt-radio-button value="true" class="mr-2"> automatically </dt-radio-button>
<dt-radio-button value="false"> by webhook receiver </dt-radio-button>
</dt-radio-group>
</dt-form-field>
<div [dtOverlay]="sendFinishedOverlay" style="cursor: pointer" [dtOverlayConfig]="sendFinishedOverlayConfig">
<dt-icon name="information" class="info mr-1"></dt-icon>
</div>
<ng-template #sendFinishedOverlay>
<p>
If you subscribe your webhook to a task event of type triggered, the corresponding started and finished events
are sent automatically.
<br /><br />
To move the responsibility of sending the finished event to the webhook receiver, select the option 'by webhook
receiver'. This is especially required for webhooks that trigger a long-running task.
</p>
</ng-template>
</div>
</form>

<ng-template #secretButton let-controlName="controlName" let-index="index" let-selectionStart="selectionStart">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
@import '../../../variables';
@import '~@dynatrace/barista-components/core/src/style/variables';

#webhook-config-form {
> div:first-of-type {
min-width: calc(100% - 170px);
Expand All @@ -17,6 +20,16 @@
font-family: monospace;
}

.dt-icon.info {
width: 16px;
height: 16px;
fill: $primary-color;
}

.send-finished-input + .dt-overlay-trigger {
margin-top: 2.5em;
}

::ng-deep {
.dt-form-field.text-area {
.dt-form-field-suffix {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,11 +345,99 @@ describe('KtbWebhookSettingsComponent', () => {
method: 'GET',
payload: 'payload',
proxy: 'https://proxy.com',
sendFinished: true,
type: '',
url: 'https://example.com',
});
});

it('sendFinished should be enabled for triggered events and true by default', () => {
// given
const checkbox = fixture.nativeElement.querySelector('[uitestid=edit-webhook-field-sendFinished] input');
component.eventType = 'triggered';
fixture.detectChanges();

// when

// then
expect(checkbox.disabled).toEqual(false);
expect(component.getFormControl('sendFinished').value).toEqual('true');
});

it('sendFinished should be disabled for started events and null', () => {
// given
const checkbox = fixture.nativeElement.querySelector('[uitestid=edit-webhook-field-sendFinished] input');
component.eventType = 'started';
fixture.detectChanges();

// when

// then
expect(checkbox.disabled).toEqual(true);
expect(component.getFormControl('sendFinished').value).toEqual(null);
});

it('sendFinished should be disabled for finished events and null', () => {
// given
const checkbox = fixture.nativeElement.querySelector('[uitestid=edit-webhook-field-sendFinished] input');
component.eventType = 'finished';
fixture.detectChanges();

// when

// then
expect(checkbox.disabled).toEqual(true);
expect(component.getFormControl('sendFinished').value).toEqual(null);
});

it('sendFinished should be set to true', () => {
// given
component.eventType = 'triggered';
component.webhook = {
header: [{ name: 'x-token', value: 'token-value' }],
method: 'GET',
payload: 'payload',
proxy: 'https://proxy.com',
sendFinished: true,
filter: {
projects: null,
services: null,
stages: null,
},
type: '',
url: 'https://example.com',
};

// when

// then
expect(component.getFormControl('sendFinished').value).toEqual('true');
});

it('sendFinished should be set to false', () => {
// given
component.eventType = 'triggered';
component.webhook = {
header: [{ name: 'x-token', value: 'token-value' }],
method: 'GET',
payload: 'payload',
proxy: 'https://proxy.com',
sendFinished: false,
filter: {
projects: null,
services: null,
stages: null,
},
type: '',
url: 'https://example.com',
};

// when

// then
expect(component.getFormControl('sendFinished').value).toEqual('false');
});

function getAddHeaderButton(): HTMLElement {
return fixture.nativeElement.querySelector('[uitestid="ktb-webhook-settings-add-header-button"]');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { WebhookConfigMethod } from '../../../../shared/interfaces/webhook-confi
import { WebhookConfig } from '../../../../shared/models/webhook-config';
import { Secret } from '../../_models/secret';
import { SelectTreeNode, TreeListSelectOptions } from '../ktb-tree-list-select/ktb-tree-list-select.component';
import { DtOverlayConfig } from '@dynatrace/barista-components/overlay';

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

@Component({
selector: 'ktb-webhook-settings',
Expand All @@ -21,6 +22,7 @@ export class KtbWebhookSettingsComponent implements OnInit {
payload: new FormControl('', []),
header: new FormArray([]),
proxy: new FormControl('', [FormUtils.isUrlValidator]),
sendFinished: new FormControl('true', []),
});
public webhookMethods: WebhookConfigMethod[] = ['GET', 'POST', 'PUT'];
public secretDataSource: SelectTreeNode[] = [];
Expand All @@ -29,6 +31,19 @@ export class KtbWebhookSettingsComponent implements OnInit {
emptyText:
'No secrets can be found.<p>Secrets can be configured under the menu entry "Secrets" in the Uniform.</p>',
};
public sendFinishedOverlayConfig: DtOverlayConfig = {
pinnable: true,
originY: 'center',
};
public _eventType: string | undefined;

@Input()
set eventType(eventType: string | undefined) {
if (this._eventType != eventType) {
this._eventType = eventType;
this.setSendFinishedControl();
}
}

@Input()
set webhook(webhookConfig: WebhookConfig | undefined) {
Expand All @@ -38,6 +53,7 @@ export class KtbWebhookSettingsComponent implements OnInit {
this.getFormControl('url').setValue(webhookConfig.url);
this.getFormControl('payload').setValue(webhookConfig.payload);
this.getFormControl('proxy').setValue(webhookConfig.proxy);
this.setSendFinishedControl();

for (const header of webhookConfig.header || []) {
this.addHeader(header.name, header.value);
Expand Down Expand Up @@ -83,6 +99,7 @@ export class KtbWebhookSettingsComponent implements OnInit {
this._webhook.payload = this.getFormControl('payload').value;
this._webhook.proxy = this.getFormControl('proxy').value;
this._webhook.header = this.getFormControl('header').value;
this._webhook.sendFinished = this.getFormControl('sendFinished').value === 'true';
this.webhookChange.emit(this._webhook);
}

Expand Down Expand Up @@ -122,6 +139,16 @@ export class KtbWebhookSettingsComponent implements OnInit {
this.onWebhookFormChange();
}

private setSendFinishedControl(): void {
if (this._eventType !== 'triggered' && this._eventType !== '>') {
this.getFormControl('sendFinished').setValue(null);
this.getFormControl('sendFinished').disable();
} else {
this.getFormControl('sendFinished').setValue(this._webhook.sendFinished.toString());
this.getFormControl('sendFinished').enable();
}
}

private mapSecret(secret: Secret): SelectTreeNode {
const scrt: SelectTreeNode = { name: secret.name };
if (secret.keys) {
Expand Down
2 changes: 2 additions & 0 deletions bridge/client/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ import {
import { OverlayModule } from '@angular/cdk/overlay';
import { KtbSequenceStateInfoComponent } from './_components/ktb-sequence-state-info/ktb-sequence-state-info.component';
import { KtbPayloadViewerComponent } from './_components/ktb-payload-viewer/ktb-payload-viewer.component';
import { DtRadioModule } from '@dynatrace/barista-components/radio';
import { NotFoundComponent } from './not-found/not-found.component';

registerLocaleData(localeEn, 'en');
Expand Down Expand Up @@ -272,6 +273,7 @@ export function init_app(appLoadService: AppInitService): () => Promise<unknown>
DtCopyToClipboardModule,
DtToggleButtonGroupModule,
DtQuickFilterModule,
DtRadioModule,
MatDialogModule,
DtIconModule.forRoot({
svgIconLocation: `assets/icons/{{name}}.svg`,
Expand Down
4 changes: 4 additions & 0 deletions bridge/client/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ button.dt-button-secondary.error-button:not([disabled]) {
background-color: $gray-100;
}

.dt-checkbox.dt-checkbox-disabled .dt-checkbox-content {
color: #ccc;
}

.m-0 {
margin: 0 !important;
}
Expand Down
1 change: 1 addition & 0 deletions bridge/server/interfaces/webhook-config-yaml-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { WebhookSecret } from '../../shared/models/webhook-config';

export type Webhook = {
subscriptionID: string;
sendFinished?: boolean;
type: string; // type === event
requests: string[];
envFrom?: WebhookSecret[];
Expand Down
27 changes: 16 additions & 11 deletions bridge/server/interfaces/webhook-config-yaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,27 @@ export class WebhookConfigYaml implements WebhookConfigYamlResult {
* @params eventType
* @params curl
*/
public addWebhook(eventType: string, curl: string, subscriptionId: string, secrets: WebhookSecret[]): void {
public addWebhook(
eventType: string,
curl: string,
subscriptionId: string,
secrets: WebhookSecret[],
sendFinished: boolean
): void {
const webhook = this.getWebhook(subscriptionId);
if (!webhook) {
if (secrets.length) {
this.spec.webhooks.push({
type: eventType,
requests: [curl],
envFrom: secrets,
subscriptionID: subscriptionId,
});
} else {
this.spec.webhooks.push({ type: eventType, requests: [curl], subscriptionID: subscriptionId });
}
this.spec.webhooks.push({
type: eventType,
requests: [curl],
...(secrets.length && { envFrom: secrets }),
subscriptionID: subscriptionId,
sendFinished: sendFinished,
});
} else {
// overwrite
webhook.type = eventType;
webhook.requests[0] = curl;
webhook.sendFinished = sendFinished;
if (secrets.length) {
webhook.envFrom = secrets;
} else {
Expand Down Expand Up @@ -105,6 +109,7 @@ export class WebhookConfigYaml implements WebhookConfigYamlResult {
const parsedConfig = curl ? this.parseConfig(curl) : undefined;
if (parsedConfig) {
parsedConfig.secrets = secrets;
parsedConfig.sendFinished = webhook?.sendFinished ?? false;
}
return parsedConfig;
}
Expand Down
8 changes: 7 additions & 1 deletion bridge/server/services/data-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,13 @@ export class DataService {
stage,
service
);
previousWebhookConfig.addWebhook(webhookConfig.type, curl, subscriptionId, secrets);
previousWebhookConfig.addWebhook(
webhookConfig.type,
curl,
subscriptionId,
secrets,
webhookConfig.sendFinished
);
await this.apiService.saveWebhookConfig(previousWebhookConfig.toYAML(), project, stage, service);
}
}
Expand Down
1 change: 1 addition & 0 deletions bridge/shared/interfaces/webhook-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export interface WebhookConfig {
payload: string;
header: { name: string; value: string }[];
proxy?: string;
sendFinished: boolean;
}
2 changes: 2 additions & 0 deletions bridge/shared/models/webhook-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ export class WebhookConfig implements wc {
public header: WebhookHeader[];
public proxy?: string;
public secrets?: WebhookSecret[];
public sendFinished: boolean;

constructor() {
this.type = '';
this.method = 'POST';
this.url = '';
this.payload = '';
this.header = [];
this.sendFinished = true;
}
}

0 comments on commit 89598f8

Please sign in to comment.