Skip to content

Commit

Permalink
updated subscriptions (#731)
Browse files Browse the repository at this point in the history
* updated subscriptions

* updates on subscriptions

* made subscription configs optional

* review updates

* tiny fixes

* undo angular.json changes

Co-authored-by: horlah <emmanuel.ainaj@gmail.com>
  • Loading branch information
Oluwadaminiola and horlah committed Jun 11, 2022
1 parent 19ba730 commit 0655be8
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 40 deletions.
11 changes: 11 additions & 0 deletions web/ui/dashboard/src/app/models/subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,15 @@ export interface SUBSCRIPTION {
type: 'outgoing' | 'incoming';
uid: string;
updated_at: string;
endpoint_metadata?: {
target_url: string;
};
app_metadata?: {
name: string;
};
alert_config?: { count: number; threshold: string };
retry_config?: { type: string; retry_count: number };
filter_config?: {
event_types: string[];
};
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<form class="padding-bottom__40px position__relative" [formGroup]="subscriptonForm" (ngSubmit)="createSubscription()">
<form class="padding-bottom__40px position__relative" [formGroup]="subscriptionForm" (ngSubmit)="createSubscription()">
<convoy-loader *ngIf="isLoadingForm"></convoy-loader>

<section class="card padding-all__24px">
<div class="input">
<label for="source-name">Subscription name</label>
<input type="text" id="subscription-name" placeholder="e.g paystack-prod" formControlName="name" />
<div class="input__error input__error__danger" *ngIf="subscriptonForm.controls['name'].touched && subscriptonForm.controls['name'].invalid">
<div class="input__error input__error__danger" *ngIf="subscriptionForm.controls['name'].touched && subscriptionForm.controls['name'].invalid">
<img src="assets/img/input-error-icon.svg" alt="input error icon" />
<span>Enter new subscription name</span>
</div>
Expand All @@ -21,7 +21,7 @@
<option value="">Select source</option>
<option [value]="source.uid" *ngFor="let source of sources">{{ source.name }}</option>
</select>
<div class="input__error input__error__danger" *ngIf="subscriptonForm.get('source_id')?.touched && subscriptonForm.get('source_id')?.invalid">
<div class="input__error input__error__danger" *ngIf="subscriptionForm.get('source_id')?.touched && subscriptionForm.get('source_id')?.invalid">
<img src="assets/img/input-error-icon.svg" alt="input error icon" />
<span>Select or create a source</span>
</div>
Expand All @@ -43,7 +43,7 @@
<option value="">Select application</option>
<option [value]="app.uid" *ngFor="let app of apps">{{ app.name }}</option>
</select>
<div class="input__error input__error__danger" *ngIf="subscriptonForm.get('app_id')?.touched && subscriptonForm.get('app_id')?.invalid">
<div class="input__error input__error__danger" *ngIf="subscriptionForm.get('app_id')?.touched && subscriptionForm.get('app_id')?.invalid">
<img src="assets/img/input-error-icon.svg" alt="input error icon" />
<span>Select or create an application</span>
</div>
Expand All @@ -55,20 +55,90 @@
New App
</button>

<div class="input margin-bottom__0px" [ngClass]="{ disabled: subscriptonForm.get('app_id')?.invalid }">
<div class="input" [ngClass]="{ disabled: subscriptionForm.get('app_id')?.invalid }">
<label for="endpoint" class="position__relative">
Endpoint
<span class="bg__grey__light rounded__4px padding-x__4px position__absolute position--right__0px font__10px font__weight-400">required</span>
</label>
<select id="endpoint" formControlName="endpoint_id">
<option value="">Select endpoint</option>
<option [value]="endpoint.uid" *ngFor="let endpoint of endPoints">{{ endpoint.target_url }}</option>
<option [value]="endpoint.uid" *ngFor="let endpoint of endPoints">{{ endpoint.description }}</option>
</select>
<div class="input__error input__error__danger" *ngIf="subscriptonForm.get('endpoint_id')?.touched && subscriptonForm.get('endpoint_id')?.invalid">
<div class="input__error input__error__danger" *ngIf="subscriptionForm.get('endpoint_id')?.touched && subscriptionForm.get('endpoint_id')?.invalid">
<img src="assets/img/input-error-icon.svg" alt="input error icon" />
<span>Select an endpoint</span>
</div>
</div>

<div class="input multiple">
<label class="flex flex__justify-between" for="tagInput">
<div class="flex flex__align-items-center">Event types</div>
</label>
<div class="input--multiple" (click)="focusInput()">
<div class="tag" *ngFor="let tag of eventTags">
<div class="flex flex__align-items-center font__weight-400">{{ tag }}</div>
<button class="button button__clear button--has-icon" (click)="removeEventTag(tag)">
<img src="/assets/img/close-icon-black.svg" width="12px" class="margin-left__8px" alt="close icon" />
</button>
</div>
<input type="text" id="tagInput" (input)="addTag()" required class="font__12px" autocomplete="off" />
</div>
<div class="input__error input__error__secondary">
<span>Separate event types with comma (,)</span>
</div>
</div>

<div [hidden]="!enableMoreConfig">
<h3 class="margin-top__40px margin-bottom__16px flex flex__align-items-center">
Retry Logic
<app-tooltip [size]="'small'" position="right" class="margin-left__4px">
These are the specifications for the retry mechanism for your endpoints under this subscription. In Linear time retry, event retries are done in linear time, while in Exponential back off
retry, events are retried progressively increasing the time before the next retry attempt.
</app-tooltip>
</h3>
<div class="grid grid__col-2 grid--gap__24px" formGroupName="retry_config">
<div class="input margin-bottom__0px">
<label for="type">Mechanism</label>
<select name="type" id="type" formControlName="type">
<option *ngFor="let type of retryLogicTypes" [value]="type.id">{{ type.type }}</option>
</select>
</div>

<div class="input margin-bottom__0px">
<label for="retry-logic-duration">Duration</label>
<input type="text" id="retry-logic-duration" placeholder="e.g 3s" formControlName="interval_seconds" />
</div>

<div class="input margin-bottom__0px">
<label for="retry-logic-count">Limit</label>
<input type="number" id="retry-logic-count" placeholder="e.g 5" formControlName="retry_count" />
</div>
</div>
<h3 class="margin-top__40px margin-bottom__16px flex flex__align-items-center">
Alert Rule
<app-tooltip [size]="'small'" position="right" class="margin-left__4px">
This specifies the frequency at which notifications(emails, slack messages) would be sent when something happens to your subscirption.
</app-tooltip>
</h3>
<div class="grid grid__col-2 grid--gap__24px" formGroupName="alert_config">
<div class="input">
<label for="alert-config-count">Count</label>
<input type="number" id="alert-config-count" placeholder="e.g 10" formControlName="theshold" />
</div>

<div class="input">
<label for="alert-config-time">Time</label>
<input type="text" id="alert-config-time" placeholder="e.g 1h" formControlName="time" />
</div>
</div>
</div>
<div class="flex flex__justify-end">
<label class="toggle">
<span class="toggle-label">Add more configurations</span>
<input class="toggle-checkbox" type="checkbox" (change)="enableMoreConfig = !enableMoreConfig" [checked]="enableMoreConfig" />
<div class="toggle-switch"></div>
</label>
</div>
</section>

<div class="button-container flex flex__justify-end margin-top__32px">
Expand All @@ -83,7 +153,7 @@
</div>
</form>

<div class="_overlay" *ngIf="showCreateAppModal || showCreateSourceModal" (click)="showCreateAppModal = !showCreateAppModal; showCreateSourceModal = !showCreateSourceModal"></div>
<div class="_overlay" *ngIf="showCreateAppModal || showCreateSourceModal" (click)="showCreateAppModal = false; showCreateSourceModal = false"></div>
<div class="modal modal__right large" *ngIf="showCreateAppModal || showCreateSourceModal">
<!-- add endpoint modal -->
<div class="modal--head" *ngIf="showCreateAppModal">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.input--multiple .tag {
margin-bottom: 2px;
padding: 2px 4px;
margin-right: 4px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,37 @@ import { CreateSubscriptionService } from './create-subscription.service';
styleUrls: ['./create-subscription.component.scss']
})
export class CreateSubscriptionComponent implements OnInit {
subscriptonForm: FormGroup = this.formBuilder.group({
subscriptionForm: FormGroup = this.formBuilder.group({
name: ['', Validators.required],
type: ['', Validators.required],
app_id: ['', Validators.required],
source_id: ['', Validators.required],
endpoint_id: ['', Validators.required],
group_id: ['', Validators.required]
group_id: ['', Validators.required],
alert_config: this.formBuilder.group({
theshold: [''],
time: ['']
}),
retry_config: this.formBuilder.group({
type: [''],
retry_count: [''],
interval_seconds: ['']
}),
filter_config: this.formBuilder.group({
event_types: ['']
})
});
apps!: APP[];
sources!: SOURCE[];
endPoints?: ENDPOINT[];
eventTags: string[] = [];
showCreateAppModal = false;
showCreateSourceModal = false;
enableMoreConfig = false;
retryLogicTypes = [
{ id: 'linear', type: 'Linear time retry' },
{ id: 'exponential', type: 'Exponential time backoff' }
];
isCreatingSubscription = false;
@Output() onAction = new EventEmitter();
projectType: 'incoming' | 'outgoing' = 'incoming';
Expand Down Expand Up @@ -62,7 +80,7 @@ export class CreateSubscriptionComponent implements OnInit {
async getGetProjectDetails() {
try {
const response = await this.privateService.getProjectDetails();
this.subscriptonForm.patchValue({
this.subscriptionForm.patchValue({
group_id: response.data.uid,
type: 'incoming'
});
Expand All @@ -74,33 +92,40 @@ export class CreateSubscriptionComponent implements OnInit {
}

onUpdateAppSelection() {
const app = this.apps.find(app => app.uid === this.subscriptonForm.value.app_id);
const app = this.apps.find(app => app.uid === this.subscriptionForm.value.app_id);
this.endPoints = app?.endpoints;
}

async onCreateSource(newSource: SOURCE) {
await this.getSources();
this.subscriptonForm.patchValue({ source_id: newSource.uid });
this.subscriptionForm.patchValue({ source_id: newSource.uid });
}

async createSubscription() {
if (this.projectType === 'incoming' && this.subscriptonForm.invalid) return this.subscriptonForm.markAllAsTouched();
this.subscriptionForm.patchValue({
filter_config: { event_types: this.eventTags }
});
if (this.projectType === 'incoming' && this.subscriptionForm.invalid) return this.subscriptionForm.markAllAsTouched();
if (
this.subscriptonForm.get('name')?.invalid &&
this.subscriptonForm.get('type')?.invalid &&
this.subscriptonForm.get('app_id')?.invalid &&
this.subscriptonForm.get('endpoint_id')?.invalid &&
this.subscriptonForm.get('group_id')?.invalid
this.subscriptionForm.get('name')?.invalid &&
this.subscriptionForm.get('type')?.invalid &&
this.subscriptionForm.get('app_id')?.invalid &&
this.subscriptionForm.get('endpoint_id')?.invalid &&
this.subscriptionForm.get('group_id')?.invalid
) {
return this.subscriptonForm.markAllAsTouched();
return this.subscriptionForm.markAllAsTouched();
}

const subscription = this.subscriptonForm.value;
const subscription = this.subscriptionForm.value;
if (this.projectType === 'outgoing') delete subscription.source_id;
if (!this.enableMoreConfig) {
delete subscription.alert_config;
delete subscription.retry_config;
}
this.isCreatingSubscription = true;

try {
const response = await this.createSubscriptionService.createSubscription(this.subscriptonForm.value);
const response = await this.createSubscriptionService.createSubscription(this.subscriptionForm.value);
this.isCreatingSubscription = false;
this.onAction.emit(response.data);
} catch (error) {
Expand All @@ -110,6 +135,37 @@ export class CreateSubscriptionComponent implements OnInit {

async onCreateNewApp(newApp: APP) {
await this.getApps();
this.subscriptonForm.patchValue({ app_id: newApp.uid });
this.subscriptionForm.patchValue({ app_id: newApp.uid });
}

removeEventTag(tag: string) {
this.eventTags = this.eventTags.filter(e => e !== tag);
}

addTag() {
const addTagInput = document.getElementById('tagInput');
const addTagInputValue = document.getElementById('tagInput') as HTMLInputElement;
addTagInput?.addEventListener('keydown', e => {
const key = e.keyCode || e.charCode;
if (key == 8) {
e.stopImmediatePropagation();
if (this.eventTags.length > 0 && !addTagInputValue?.value) this.eventTags.splice(-1);
}
if (e.which === 188 || e.key == ' ') {
if (this.eventTags.includes(addTagInputValue?.value)) {
addTagInputValue.value = '';
this.eventTags = this.eventTags.filter(e => String(e).trim());
} else {
this.eventTags.push(addTagInputValue?.value);
addTagInputValue.value = '';
this.eventTags = this.eventTags.filter(e => String(e).trim());
}
e.preventDefault();
}
});
}

focusInput() {
document.getElementById('tagInput')?.focus();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { CreateSubscriptionComponent } from './create-subscription.component';
import { ReactiveFormsModule } from '@angular/forms';
import { CreateAppModule } from '../create-app/create-app.module';
import { CreateSourceModule } from '../create-source/create-source.module';
import { TooltipModule } from '../tooltip/tooltip.module';
import { LoaderModule } from '../loader/loader.module';

@NgModule({
declarations: [CreateSubscriptionComponent],
imports: [CommonModule, ReactiveFormsModule, CreateAppModule, CreateSourceModule, LoaderModule],
imports: [CommonModule, ReactiveFormsModule, CreateAppModule, CreateSourceModule, TooltipModule, LoaderModule],
exports: [CreateSubscriptionComponent]
})
export class CreateSubscriptionModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ <h2>Add Endpoint</h2>
</div>
<span class="bg__grey__light rounded__4px padding-x__4px position__absolute position--right__0px font__10px font__weight-400">required</span>
</label>
<div class="input--multiple">
<div class="input--multiple" (click)="focusInput()">
<div class="tag" *ngFor="let tag of eventTags">
{{ tag }}
<a (click)="removeEventTag(tag)">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ export class AppDetailsComponent implements OnInit {
this.screenWidth > 1010 ? (this.shouldRenderSmallSize = false) : (this.shouldRenderSmallSize = true);
}

focusInput() {
document.getElementById('tagInput')?.focus();
}

@HostListener('window:resize', ['$event'])
onWindowResize() {
this.screenWidth = window.innerWidth;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ <h1 class="font__12px font__weight-500 flex flex__align-items-center text__upper
<div>{{ app.updated_at | date }}</div>
</td>
<td>
<div>{{ app.events }}</div>
<div>{{ app.events || 0 }}</div>
</td>
<td>
<div>{{ app.endpoints.length }}</div>
<div>{{ app?.endpoints?.length || 0 }}</div>
</td>
<td>
<div>
Expand Down

0 comments on commit 0655be8

Please sign in to comment.