Skip to content

Commit

Permalink
app endpoint edit/update feature (#737)
Browse files Browse the repository at this point in the history
* app endpoint edit/update feature

* updated apps and app-details

* added empty state img for projects

* tiny update

* review updates

* took out commented code

* fixed oboarding flow

* took out console.log

Co-authored-by: horlah <emmanuel.ainaj@gmail.com>
  • Loading branch information
Oluwadaminiola and horlah committed Jun 12, 2022
1 parent 43d21e4 commit e825421
Show file tree
Hide file tree
Showing 32 changed files with 605 additions and 355 deletions.
3 changes: 3 additions & 0 deletions web/ui/dashboard/src/app/models/app.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ export interface ENDPOINT {
secret: string;
target_url: string;
updated_at: Date;
rate_limit?: number;
rate_limit_duration?: string;
http_timeout?: string;
}
2 changes: 1 addition & 1 deletion web/ui/dashboard/src/app/models/event.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface EVENT {
app_metadata: {
group_id: string;
support_email: string;
title: string;
name: string;
uid: string;
};
}
Expand Down
1 change: 1 addition & 0 deletions web/ui/dashboard/src/app/models/http.model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface HTTP_RESPONSE {
data: any;
message: string;
error?: any;
status: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
</div>
</div>
<div class="grid grid__col-2 grid--gap__20px">
<div class="input">
<div class="input margin-bottom__0px">
<label class="flex flex__align-items-center" for="app-email">
Support Email
<app-tooltip [position]="'right'" [size]="'small'" class="margin-left__4px">We use this to send an email notifcation for when a failure happens</app-tooltip>
</label>
<input type="email" id="app-email" formControlName="support_email" />
</div>
<div class="input">
<div class="input margin-bottom__0px">
<label class="flex flex__align-items-center" for="app-slack-url">
Slack webhook url
<app-tooltip [size]="'small'" class="margin-left__4px">We use this to send notifications to your slack channel for when a failure happens</app-tooltip>
Expand All @@ -35,7 +35,7 @@
</div>

<div class="margin-top__32px" *ngIf="!editAppMode" formArrayName="endpoints">
<div *ngFor="let endpoint of endpoints.controls; let i = index">
<div class="margin-top__40px" *ngFor="let endpoint of endpoints.controls; let i = index">
<div class="flex flex__align-items-center flex__justify-between margin-bottom__12px">
<h3>Endpoint {{ i > 0 ? i + 1 : '' }}</h3>
<div class="flex flex__justify-end margin-bottom__16px">
Expand All @@ -45,45 +45,49 @@ <h3>Endpoint {{ i > 0 ? i + 1 : '' }}</h3>
</button>
</div>
</div>
<div class="grid grid__col-2 grid--gap__20px" [formGroupName]="i">
<div class="input">
<label class="flex__justify-between" [for]="'description' + i">
Description
<span class="bg__grey__light rounded__4px padding-x__4px position__absolute position--right__0px font__10px font__weight-400">required</span>
</label>
<input [id]="'description' + i" type="text" formControlName="description" required />
<div class="input__error input__error__danger" *ngIf="getSingleEndpoint(i).description.touched && getSingleEndpoint(i).description.invalid">
<img src="assets/img/input-error-icon.svg" alt="input error icon" />
<span>Please provide a description</span>
<div [formGroupName]="i">
<div class="grid grid__col-2 grid--gap__20px">
<div class="input">
<label class="flex__justify-between" [for]="'description' + i">
Description
<span class="bg__grey__light rounded__4px padding-x__4px position__absolute position--right__0px font__10px font__weight-400">required</span>
</label>
<input [id]="'description' + i" type="text" formControlName="description" required />
<div class="input__error input__error__danger" *ngIf="getSingleEndpoint(i).description.touched && getSingleEndpoint(i).description.invalid">
<img src="assets/img/input-error-icon.svg" alt="input error icon" />
<span>Please provide a description</span>
</div>
</div>
</div>

<div class="input">
<label class="flex flex__justify-between" [for]="'endpoint-url' + i">
Enter Endpoint URL
<span class="bg__grey__light rounded__4px padding-x__4px position__absolute position--right__0px font__10px font__weight-400">required</span>
</label>
<input [id]="'endpoint-url' + i" type="text" formControlName="url" required />
<div class="input__error input__error__danger" *ngIf="getSingleEndpoint(i).url.touched && getSingleEndpoint(i).url.invalid">
<img src="assets/img/input-error-icon.svg" alt="input error icon" />
<span>Please provide enter endpoint URL</span>
<div class="input">
<label class="flex flex__justify-between" [for]="'endpoint-url' + i">
Enter Endpoint URL
<span class="bg__grey__light rounded__4px padding-x__4px position__absolute position--right__0px font__10px font__weight-400">required</span>
</label>
<input [id]="'endpoint-url' + i" type="text" formControlName="url" required />
<div class="input__error input__error__danger" *ngIf="getSingleEndpoint(i).url.touched && getSingleEndpoint(i).url.invalid">
<img src="assets/img/input-error-icon.svg" alt="input error icon" />
<span>Please provide enter endpoint URL</span>
</div>
</div>

<div class="input">
<label for="rate-limit-count">Rate Limit</label>
<input type="number" id="rate-limit-count" placeholder="e.g 5000" formControlName="rate_limit" />
</div>

<div class="input">
<label for="rate-limit-duration">Rate Limit Duration</label>
<input type="text" id="rate-limit-duration" placeholder="e.g 1m" formControlName="rate_limit_duration" />
</div>
</div>

<div class="input multiple">
<label class="flex flex__align-items-center" [for]="'tagInput' + i">
Events
<app-tooltip [size]="'small'" position="right" class="margin-left__4px">Add multiple events by separating them with commas.</app-tooltip>
</div>
<div class="input">
<label class="flex flex__align-items-center" for="http-timeout">
HTTP Timeout
<app-tooltip [size]="'small'" position="right" class="margin-left__4px">This specifies how long the socket stays open in the absense of a response.</app-tooltip>
</label>
<div class="input--multiple" (click)="focusInput(i)">
<div class="tag" *ngFor="let tag of getSingleEndpoint(i)?.events?.value">
{{ tag }}
<a (click)="removeEventTag(tag, i)">
<img src="/assets/img/close-icon-black.svg" alt="close icon" />
</a>
</div>
<input type="text" [id]="'tagInput' + i" (input)="addTag(i)" />
</div>
<input id="http-timeout" type="text" formControlName="http_timeout" placeholder="e.g. 10s" />
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export class CreateAppComponent implements OnInit {

@Output() discardApp = new EventEmitter<any>();
@Output() createApp = new EventEmitter<any>();
eventTags: any[] = [];
appUid!: string;
isSavingApp: boolean = false;
addNewAppForm: FormGroup = this.formBuilder.group({
Expand Down Expand Up @@ -46,8 +45,10 @@ export class CreateAppComponent implements OnInit {
newEndpoint(): FormGroup {
return this.formBuilder.group({
url: ['', Validators.required],
events: [''],
description: ['', Validators.required]
description: ['', Validators.required],
http_timeout: [''],
rate_limit: [''],
rate_limit_duration: ['']
});
}

Expand All @@ -59,33 +60,6 @@ export class CreateAppComponent implements OnInit {
this.endpoints.removeAt(i);
}

removeEventTag(tag: string, i: number) {
this.eventTags[i] = this.eventTags[i].filter((e: string) => e !== tag);
}

addTag(i: number) {
this.eventTags[i] ? (this.eventTags[i] = this.eventTags[i]) : (this.eventTags[i] = []);

const addTagInput = document.getElementById('tagInput' + i);
const addTagInputValue = document.getElementById('tagInput' + i) as HTMLInputElement;

addTagInput?.addEventListener('keydown', e => {
if (e.which === 188) {
if (this.eventTags[i].includes(addTagInputValue?.value)) {
addTagInputValue.value = '';
this.eventTags[i] = this.eventTags[i].filter((e: string) => String(e).trim());
} else {
this.eventTags[i].push(addTagInputValue?.value);
this.eventTags[i] = this.eventTags[i].filter((e: string) => String(e).trim());

((this.addNewAppForm.get('endpoints') as FormArray)?.at(i) as FormGroup)?.get('events')?.patchValue(this.eventTags[i]);
addTagInputValue.value = '';
}
e.preventDefault();
}
});
}

updateForm() {
this.addNewAppForm.patchValue({
name: this.appsDetailsItem?.name,
Expand All @@ -102,10 +76,6 @@ export class CreateAppComponent implements OnInit {
return;
}

this.addNewAppForm.value.endpoints.forEach((item: any) => {
if (item.events === '') item.events = ['*'];
});

this.isSavingApp = true;
let requests: any[] = [];

Expand Down Expand Up @@ -147,16 +117,11 @@ export class CreateAppComponent implements OnInit {
}
}

async saveNewEndpoints(requests: any[]) {
const response = await Promise.all(requests);
console.log(response);
saveNewEndpoints(requests: any[]) {
Promise.all(requests);
}

closeAppInstance() {
this.discardApp.emit();
}

focusInput(i: number) {
document.getElementById('tagInput' + i)?.focus();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,20 @@ <h3 class="margin-top__24px margin-bottom__16px">Disable Failing Endpoints</h3>
<button class="button button__primary padding-y__12px padding-x__28px rounded__8px" [disabled]="isCreatingProject" *ngIf="action === 'update'">Update Project</button>
</div>
</form>
<div class="_overlay" *ngIf="showApiKey"></div>
<div class="modal modal__right" *ngIf="showApiKey">
<div class="modal--body delete no-padding flex flex__column flex__align-items-center">
<img src="/assets/img/success.gif" class="img" alt="success" />
<h2 class="font__weight-600 color__black margin-top__16px">Project Created Successfully</h2>
<p class="font__14px color__grey font-weight-400 text--center margin-bottom__16px margin-top__8px">Your API Key has also been created. Please copy this key and save it somewhere safe.</p>
<p class="font__14px color__danger margin-bottom__40px text--center">For security reasons, we cannot show it to you again</p>
<div class="key margin-bottom__32px">
<span>{{ apiKey }}</span>
<button (click)="copyKey(apiKey, 'secret')">
<img src="/assets/img/copy.svg" alt="copy" />
<small *ngIf="showSecretCopyText">Copied!</small>
</button>
</div>
<button class="button button__primary" (click)="onAction.emit({ action: 'createProject', data: projectDetails })">Done</button>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export class CreateProjectComponent implements OnInit {
type: ['', Validators.required]
});
isCreatingProject = false;
showApiKey = false;
showPublicCopyText = false;
showSecretCopyText = false;
apiKey!: string;
hashAlgorithms = ['SHA256', 'SHA512', 'MD5', 'SHA1', 'SHA224', 'SHA384', 'SHA3_224', 'SHA3_256', 'SHA3_384', 'SHA3_512', 'SHA512_256', 'SHA512_224'];
retryLogicTypes = [
{ id: 'linear', type: 'Linear time retry' },
Expand Down Expand Up @@ -64,15 +68,22 @@ export class CreateProjectComponent implements OnInit {
if (this.projectForm.invalid) return this.projectForm.markAllAsTouched();

this.isCreatingProject = true;
const [digits, word] = this.projectForm.value.config.strategy.duration.match(/\D+|\d+/g);
word === 's' ? (this.projectForm.value.config.strategy.duration = parseInt(digits) * 1000) : (this.projectForm.value.config.strategy.duration = parseInt(digits) * 1000000);

let duration = this.projectForm.value.config.strategy.duration;
const [digits, word] = duration.match(/\D+|\d+/g);
word === 's' ? (duration = parseInt(digits) * 1000) : (duration = parseInt(digits) * 1000000);
this.projectForm.value.config.strategy.duration = duration;
try {
const response = await this.createProjectService.createProject(this.projectForm.value);
this.privateService.activeProjectDetails = response.data;
this.isCreatingProject = false;
this.generalService.showNotification({ message: 'Project created successfully!', style: 'success' });
this.onAction.emit(response.data);
if (response.status === true) {
this.privateService.activeProjectDetails = response.data.group;
this.generalService.showNotification({ message: 'Project created successfully!', style: 'success' });
this.apiKey = response.data.api_key.key;
this.projectDetails = response.data.group;
this.showApiKey = true;
} else {
this.generalService.showNotification({ message: response?.error?.message, style: 'error' });
}
} catch (error: any) {
this.isCreatingProject = false;
this.generalService.showNotification({ message: error.message, style: 'error' });
Expand All @@ -96,4 +107,18 @@ export class CreateProjectComponent implements OnInit {
this.isCreatingProject = false;
}
}

copyKey(key: string, type: 'public' | 'secret') {
const text = key;
const el = document.createElement('textarea');
el.value = text;
document.body.appendChild(el);
el.select();
document.execCommand('copy');
type === 'public' ? (this.showPublicCopyText = true) : (this.showSecretCopyText = true);
setTimeout(() => {
type === 'public' ? (this.showPublicCopyText = false) : (this.showSecretCopyText = false);
}, 3000);
document.body.removeChild(el);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { HTTP_RESPONSE } from 'convoy-app/lib/models/http.model';
import { HTTP_RESPONSE } from 'src/app/models/http.model';
import { HttpService } from 'src/app/services/http/http.service';
import { PrivateService } from '../../private.service';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { HTTP_RESPONSE } from 'convoy-app/lib/models/http.model';
import { SOURCE } from 'src/app/models/group.model';
import { HTTP_RESPONSE } from 'src/app/models/http.model';
import { HttpService } from 'src/app/services/http/http.service';
import { PrivateService } from '../../private.service';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { HTTP_RESPONSE } from 'convoy-app/lib/models/http.model';
import { HTTP_RESPONSE } from 'src/app/models/http.model';
import { HttpService } from 'src/app/services/http/http.service';
import { PrivateService } from '../../private.service';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,13 @@ <h2 class="font__16px font__weight-600">Create Project</h2>

<div class="card padding-all__24px margin-bottom__24px" *ngIf="projectStage !== 'createProject'">
<ul class="setup-steps flex flex__align-items-center border__bottom padding-bottom__24px">
<li class="setup-step--step" [ngClass]="{ done: projectStage != 'createApplication', current: projectStage == 'createApplication' }">
<div class="line bg__primary"></div>
<div class="margin-top__4px flex flex__align-items-center">
<div class="checkbox margin-all__0px width__16px height__16px border__all rounded__50px"></div>
<div class="text margin-left__8px font__14px font__weight-500">Create Application</div>
</div>
</li>

<li class="setup-step--step" *ngIf="projectType === 'incoming'" [ngClass]="{ done: projectStage == 'createSubscription', current: projectStage == 'createSource' }">
<div class="line"></div>
<div class="margin-top__4px flex flex__align-items-center">
<div class="checkbox margin-all__0px width__16px height__16px border__all rounded__50px"></div>
<div class="text margin-left__8px font__14px font__weight-500">Create Source</div>
</div>
</li>

<li class="setup-step--step" [ngClass]="{ current: projectStage == 'createSubscription' }">
<div class="line bg__primary"></div>
<div class="margin-top__4px flex flex__align-items-center">
<div class="checkbox margin-all__0px width__16px height__16px border__all rounded__50px"></div>
<div class="text margin-left__8px font__14px font__weight-500">Create Subscription</div>
<li [class]="'setup-step--step ' + stage.currentStage" *ngFor="let stage of projectStages">
<div>
<div class="line"></div>
<div class="margin-top__4px flex flex__align-items-center">
<div class="checkbox margin-all__0px width__16px height__16px border__all rounded__50px"></div>
<div class="text margin-left__8px font__14px font__weight-500">{{ stage.projectStage }}</div>
</div>
</div>
</li>
</ul>
Expand All @@ -61,13 +47,15 @@ <h3 class="margin-top__24px font__weight-600 font__18px">Create your first subsc

<app-create-source
*ngIf="projectStage === 'createSource' && privateService.activeProjectDetails.uid"
(onAction)="$event?.action == 'cancel' ? cancel() : (projectStage = 'createSubscription')"
(onAction)="$event?.action == 'cancel' ? cancel() : toggleActiveStage({ project: 'createSubscription', prevStage: 'createSource' })"
></app-create-source>

<app-create-app
*ngIf="projectStage === 'createApplication' && privateService.activeProjectDetails.uid"
(discardApp)="cancel()"
(createApp)="projectType == 'incoming' ? (projectStage = 'createSource') : (projectStage = 'createSubscription')"
(createApp)="
projectType == 'incoming' ? toggleActiveStage({ project: 'createSource', prevStage: 'createApplication' }) : toggleActiveStage({ project: 'createSubscription', prevStage: 'createApplication' })
"
></app-create-app>

<app-create-subscription *ngIf="projectStage === 'createSubscription' && privateService.activeProjectDetails.uid" (onAction)="onProjectOnboardingComplete()"></app-create-subscription>
Expand Down
Loading

0 comments on commit e825421

Please sign in to comment.