mirror of
https://github.com/optim-enterprises-bv/siembol.git
synced 2025-10-30 01:52:52 +00:00
Config-editor-ui: add delete config / testcase functionality and fix tab change bug (#51)
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -68,3 +68,9 @@ testem.log
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# UI config files
|
||||
config-editor/config-editor-ui/src/config/*
|
||||
|
||||
# REST config files
|
||||
config-editor/config-editor-rest/src/main/resources/application.properties
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-member-accessibility": ["error", { "accessibility": "no-public" }],
|
||||
"@angular-eslint/component-selector": [
|
||||
"error",
|
||||
{ "type": "element", "prefix": ["re", "formly"], "style": "kebab-case" }
|
||||
@@ -113,7 +114,7 @@
|
||||
{
|
||||
"selector": "enumMember",
|
||||
"format": [
|
||||
"PascalCase"
|
||||
"UPPER_CASE"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "rule-editor.ui",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1-dev",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
@@ -46,7 +46,6 @@
|
||||
"rxjs": "^6.6.6",
|
||||
"rxjs-compat": "^6.6.7",
|
||||
"tslib": "^2.2.0",
|
||||
"typescript": "^4.1.5",
|
||||
"zone.js": "^0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -83,7 +82,7 @@
|
||||
"protractor": "^7.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "4.1.5",
|
||||
"typescript": "^4.1.5",
|
||||
"webpack": "^4.41.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -12,96 +12,98 @@ import { takeUntil, take, skip } from 'rxjs/operators';
|
||||
import { Router } from '@angular/router';
|
||||
import { SubmitDialogComponent } from '../submit-dialog/submit-dialog.component';
|
||||
import { BlockUI, NgBlockUI } from 'ng-block-ui';
|
||||
import { AppConfigService } from '@app/services/app-config.service';
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 're-admin-editor',
|
||||
styleUrls: ['./admin.component.scss'],
|
||||
templateUrl: './admin.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 're-admin-editor',
|
||||
styleUrls: ['./admin.component.scss'],
|
||||
templateUrl: './admin.component.html',
|
||||
})
|
||||
export class AdminComponent implements OnInit, OnDestroy {
|
||||
public ngUnsubscribe = new Subject();
|
||||
public configData: ConfigData = {};
|
||||
public options: FormlyFormOptions = {};
|
||||
public form: FormGroup = new FormGroup({});
|
||||
public adminConfig$: Observable<AdminConfig>;
|
||||
public config: AdminConfig;
|
||||
public serviceName: string;
|
||||
public adminPullRequestPending$: Observable<PullRequestInfo>;
|
||||
private readonly PR_OPEN_MESSAGE = 'A pull request is already open';
|
||||
private readonly BLOCKING_TIMEOUT = 30000;
|
||||
@Input() fields: FormlyFieldConfig[];
|
||||
@BlockUI() blockUI: NgBlockUI;
|
||||
ngUnsubscribe = new Subject();
|
||||
configData: ConfigData = {};
|
||||
options: FormlyFormOptions = {};
|
||||
form: FormGroup = new FormGroup({});
|
||||
adminConfig$: Observable<AdminConfig>;
|
||||
config: AdminConfig;
|
||||
serviceName: string;
|
||||
adminPullRequestPending$: Observable<PullRequestInfo>;
|
||||
private readonly PR_OPEN_MESSAGE = 'A pull request is already open';
|
||||
constructor(
|
||||
public dialog: MatDialog,
|
||||
public snackbar: PopupService,
|
||||
private editorService: EditorService,
|
||||
private router: Router,
|
||||
private configService: AppConfigService
|
||||
) {
|
||||
this.adminConfig$ = editorService.configStore.adminConfig$;
|
||||
this.adminPullRequestPending$ = this.editorService.configStore.adminPullRequestPending$;
|
||||
this.serviceName = editorService.serviceName;
|
||||
}
|
||||
|
||||
@Input() fields: FormlyFieldConfig[];
|
||||
@BlockUI() blockUI: NgBlockUI;
|
||||
constructor(public dialog: MatDialog, public snackbar: PopupService,
|
||||
private editorService: EditorService, private router: Router) {
|
||||
this.adminConfig$ = editorService.configStore.adminConfig$;
|
||||
this.adminPullRequestPending$ = this.editorService.configStore.adminPullRequestPending$;
|
||||
this.serviceName = editorService.serviceName;
|
||||
}
|
||||
ngOnInit() {
|
||||
this.adminConfig$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(config => {
|
||||
this.config = config;
|
||||
this.configData = this.editorService.adminSchema.wrapConfig(config.configData);
|
||||
this.editorService.adminSchema.wrapAdminConfig(this.configData);
|
||||
this.options.formState = {
|
||||
mainModel: this.configData,
|
||||
rawObjects: {},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.adminConfig$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(config => {
|
||||
this.config = config;
|
||||
this.configData = this.editorService.adminSchema.wrapConfig(config.configData);
|
||||
this.editorService.adminSchema.wrapAdminConfig(this.configData);
|
||||
this.options.formState = {
|
||||
mainModel: this.configData,
|
||||
rawObjects: {},
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.ngUnsubscribe.next();
|
||||
this.ngUnsubscribe.complete();
|
||||
}
|
||||
|
||||
updateConfigInStore() {
|
||||
const configToClean = cloneDeep(this.config) as AdminConfig;
|
||||
configToClean.configData = cloneDeep(this.form.value);
|
||||
configToClean.configData = this.editorService.adminSchema.cleanRawObjects(
|
||||
configToClean.configData,
|
||||
this.options.formState.rawObjects
|
||||
);
|
||||
const configToUpdate = this.editorService.adminSchema.unwrapAdminConfig(configToClean);
|
||||
|
||||
this.editorService.configStore.updateAdmin(configToUpdate);
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
this.updateConfigInStore();
|
||||
this.adminPullRequestPending$.pipe(skip(1), take(1)).subscribe(a => {
|
||||
if (!a.pull_request_pending) {
|
||||
const dialogRef = this.dialog.open(SubmitDialogComponent, {
|
||||
data: {
|
||||
type: Type.ADMIN_TYPE,
|
||||
validate: () => this.editorService.configStore.validateAdminConfig(),
|
||||
submit: () => this.editorService.configStore.submitAdminConfig(),
|
||||
},
|
||||
disableClose: true,
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.ngUnsubscribe.next();
|
||||
this.ngUnsubscribe.complete();
|
||||
}
|
||||
|
||||
updateConfigInStore() {
|
||||
const configToClean = cloneDeep(this.config) as AdminConfig;
|
||||
configToClean.configData = cloneDeep(this.form.value);
|
||||
configToClean.configData = this.editorService.adminSchema.cleanRawObjects(configToClean.configData, this.options.formState.rawObjects);
|
||||
let configToUpdate = this.editorService.adminSchema.unwrapAdminConfig(configToClean);
|
||||
|
||||
this.editorService.configStore.updateAdmin(configToUpdate);
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
this.updateConfigInStore();
|
||||
this.adminPullRequestPending$.pipe(skip(1), take(1)).subscribe(a => {
|
||||
if (!a.pull_request_pending) {
|
||||
const dialogRef = this.dialog.open(SubmitDialogComponent,
|
||||
{
|
||||
data: {
|
||||
type: Type.ADMIN_TYPE,
|
||||
validate: () => this.editorService.configStore.validateAdminConfig(),
|
||||
submit: () => this.editorService.configStore.submitAdminConfig()
|
||||
},
|
||||
disableClose: true
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(
|
||||
success => {
|
||||
if (success) {
|
||||
this.router.navigate(
|
||||
[this.editorService.serviceName, 'admin']
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.snackbar.openNotification(this.PR_OPEN_MESSAGE);
|
||||
}
|
||||
dialogRef.afterClosed().subscribe(success => {
|
||||
if (success) {
|
||||
this.router.navigate([this.editorService.serviceName, 'admin']);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.snackbar.openNotification(this.PR_OPEN_MESSAGE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public onSyncWithGit() {
|
||||
this.blockUI.start("loading admin config");
|
||||
this.editorService.configStore.reloadAdminConfig().subscribe(() => {
|
||||
this.blockUI.stop();
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.blockUI.stop();
|
||||
}, this.BLOCKING_TIMEOUT);
|
||||
}
|
||||
onSyncWithGit() {
|
||||
this.blockUI.start('loading admin config');
|
||||
this.editorService.configStore.reloadAdminConfig().subscribe(() => {
|
||||
this.blockUI.stop();
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.blockUI.stop();
|
||||
}, this.configService.blockingTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<div @list cdkDropList id="store-list" [cdkDropListData]="allConfigs$ | async" [cdkDropListConnectedTo]="['deployment-list']" [cdkDropListEnterPredicate]="noReturnPredicate"
|
||||
(cdkDropListDropped)="drop($event)" class="rule-list">
|
||||
<re-config-tile *ngFor="let config of (filteredConfigs$ | async); index as i; trackBy: trackConfigByName"
|
||||
cdkDrag [cdkDragData]="config" [config]="config" [hideAddDeployment]="i>=filteredDeployment.configs.length" (onEdit)="onEdit(i)" (onView)="onView(i)" (onClone)="onClone(i)" (onAddToDeployment)="addToDeployment(i)">
|
||||
cdkDrag [cdkDragData]="config" [config]="config" [notDeployed]="i>=filteredDeployment.configs.length" (edit)="onEdit(i)" (view)="onView(i)" (clone)="onClone(i)" (addToDeployment)="addToDeployment(i)" (deleteFromStore)="deleteConfigFromStore(i)">
|
||||
</re-config-tile>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,7 +44,7 @@
|
||||
</popper-content>
|
||||
<mat-icon>history</mat-icon>
|
||||
</div>
|
||||
<span *ngIf="!(pullRequestPending$ | async).pull_request_pending && !(releaseSubmitInFlight$ | async); else prMessage">
|
||||
<span *ngIf="(pullRequestPending$ | async).pull_request_pending === false && (releaseSubmitInFlight$ | async) === false; else prMessage">
|
||||
<button class="button" mat-button color="accent" title="Deploy Configs" (click)="onDeploy()">DEPLOY</button>
|
||||
</span>
|
||||
<ng-template #prMessage>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { animate, query, stagger, style, transition, trigger } from '@angular/animations';
|
||||
import {
|
||||
CdkDrag,
|
||||
CdkDragDrop,
|
||||
CdkDropList,
|
||||
} from '@angular/cdk/drag-drop';
|
||||
import { CdkDrag, CdkDragDrop, CdkDropList } from '@angular/cdk/drag-drop';
|
||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { EditorService } from '@services/editor.service';
|
||||
@@ -18,205 +14,218 @@ import { FileHistory } from '../../model';
|
||||
import { ConfigStoreService } from '../../services/store/config-store.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { BlockUI, NgBlockUI } from 'ng-block-ui';
|
||||
import { AppConfigService } from '@app/services/app-config.service';
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 're-config-manager',
|
||||
styleUrls: ['./config-manager.component.scss'],
|
||||
templateUrl: './config-manager.component.html',
|
||||
animations: [
|
||||
trigger('list', [
|
||||
transition(':enter', [
|
||||
transition('* => *', []),
|
||||
query(':enter', [
|
||||
style({ opacity: 0 }),
|
||||
stagger(10, [
|
||||
style({ transform: 'scale(0.8)', opacity: 0 }),
|
||||
animate('.6s cubic-bezier(.8,-0.6,0.7,1.5)',
|
||||
style({ transform: 'scale(1)', opacity: 1 })),
|
||||
]),
|
||||
], { optional: true }),
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 're-config-manager',
|
||||
styleUrls: ['./config-manager.component.scss'],
|
||||
templateUrl: './config-manager.component.html',
|
||||
animations: [
|
||||
trigger('list', [
|
||||
transition(':enter', [
|
||||
transition('* => *', []),
|
||||
query(
|
||||
':enter',
|
||||
[
|
||||
style({ opacity: 0 }),
|
||||
stagger(10, [
|
||||
style({ transform: 'scale(0.8)', opacity: 0 }),
|
||||
animate('.6s cubic-bezier(.8,-0.6,0.7,1.5)', style({ transform: 'scale(1)', opacity: 1 })),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
],
|
||||
{ optional: true }
|
||||
),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class ConfigManagerComponent implements OnInit, OnDestroy {
|
||||
private ngUnsubscribe = new Subject();
|
||||
private configStore: ConfigStoreService;
|
||||
public allConfigs$: Observable<Config[]>;
|
||||
public filteredConfigs$: Observable<Config[]>;
|
||||
public deployment$: Observable<Deployment>;
|
||||
public deployment: Deployment;
|
||||
public configs: Config[];
|
||||
public selectedConfig$: Observable<number>;
|
||||
public selectedConfig: number;
|
||||
public pullRequestPending$: Observable<PullRequestInfo>;
|
||||
public releaseSubmitInFlight$: Observable<boolean>;
|
||||
public searchTerm$: Observable<string>;
|
||||
public filteredDeployment: Deployment;
|
||||
public filteredDeployment$: Observable<Deployment>;
|
||||
private filteredConfigs: Config[];
|
||||
public filterMyConfigs$: Observable<boolean>;
|
||||
public filterUndeployed$: Observable<boolean>;
|
||||
public filterUpgradable$: Observable<boolean>;
|
||||
public deploymentHistory$: Observable<FileHistory[]>;
|
||||
public deploymentHistory;
|
||||
@BlockUI() blockUI: NgBlockUI;
|
||||
allConfigs$: Observable<Config[]>;
|
||||
filteredConfigs$: Observable<Config[]>;
|
||||
deployment$: Observable<Deployment>;
|
||||
deployment: Deployment;
|
||||
configs: Config[];
|
||||
selectedConfig$: Observable<number>;
|
||||
selectedConfig: number;
|
||||
pullRequestPending$: Observable<PullRequestInfo>;
|
||||
releaseSubmitInFlight$: Observable<boolean>;
|
||||
searchTerm$: Observable<string>;
|
||||
filteredDeployment: Deployment;
|
||||
filteredDeployment$: Observable<Deployment>;
|
||||
filterMyConfigs$: Observable<boolean>;
|
||||
filterUndeployed$: Observable<boolean>;
|
||||
filterUpgradable$: Observable<boolean>;
|
||||
deploymentHistory$: Observable<FileHistory[]>;
|
||||
deploymentHistory;
|
||||
|
||||
private readonly BLOCKING_TIMEOUT = 30000;
|
||||
private readonly PR_OPEN_MESSAGE = 'A pull request is already open';
|
||||
@BlockUI() blockUI: NgBlockUI;
|
||||
constructor(public dialog: MatDialog, private snackbar: PopupService,
|
||||
private editorService: EditorService, private router: Router) {
|
||||
this.configStore = editorService.configStore;
|
||||
this.allConfigs$ = this.configStore.allConfigs$;
|
||||
private ngUnsubscribe = new Subject();
|
||||
private filteredConfigs: Config[];
|
||||
private configStore: ConfigStoreService;
|
||||
private readonly PR_OPEN_MESSAGE = 'A pull request is already open';
|
||||
constructor(
|
||||
public dialog: MatDialog,
|
||||
private snackbar: PopupService,
|
||||
private editorService: EditorService,
|
||||
private router: Router,
|
||||
private configService: AppConfigService
|
||||
) {
|
||||
this.configStore = editorService.configStore;
|
||||
this.allConfigs$ = this.configStore.allConfigs$;
|
||||
|
||||
this.filteredConfigs$ = this.configStore.filteredConfigs$;
|
||||
this.filteredConfigs$ = this.configStore.filteredConfigs$;
|
||||
|
||||
this.pullRequestPending$ = this.configStore.pullRequestPending$;
|
||||
this.releaseSubmitInFlight$ = this.configStore.releaseSubmitInFlight$;
|
||||
this.deployment$ = this.configStore.deployment$;
|
||||
this.pullRequestPending$ = this.configStore.pullRequestPending$;
|
||||
this.releaseSubmitInFlight$ = this.configStore.releaseSubmitInFlight$;
|
||||
this.deployment$ = this.configStore.deployment$;
|
||||
|
||||
this.searchTerm$ = this.configStore.searchTerm$;
|
||||
this.filteredDeployment$ = this.configStore.filteredDeployment$;
|
||||
this.filterMyConfigs$ = this.configStore.filterMyConfigs$;
|
||||
this.searchTerm$ = this.configStore.searchTerm$;
|
||||
this.filteredDeployment$ = this.configStore.filteredDeployment$;
|
||||
this.filterMyConfigs$ = this.configStore.filterMyConfigs$;
|
||||
|
||||
this.filterUndeployed$ = this.configStore.filterUndeployed$;
|
||||
this.filterUpgradable$ = this.configStore.filterUpgradable$;
|
||||
this.filterUndeployed$ = this.configStore.filterUndeployed$;
|
||||
this.filterUpgradable$ = this.configStore.filterUpgradable$;
|
||||
|
||||
this.deploymentHistory$ = this.configStore.deploymentHistory$;
|
||||
this.deploymentHistory$ = this.configStore.deploymentHistory$;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.deployment$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(s => {
|
||||
this.deployment = cloneDeep(s);
|
||||
});
|
||||
this.allConfigs$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(r => {
|
||||
this.configs = cloneDeep(r);
|
||||
});
|
||||
|
||||
this.filteredConfigs$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(s => (this.filteredConfigs = s));
|
||||
|
||||
this.filteredDeployment$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(s => {
|
||||
this.filteredDeployment = cloneDeep(s);
|
||||
});
|
||||
|
||||
this.deploymentHistory$
|
||||
.pipe(takeUntil(this.ngUnsubscribe))
|
||||
.subscribe(h => (this.deploymentHistory = { fileHistory: h }));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.ngUnsubscribe.next();
|
||||
this.ngUnsubscribe.complete();
|
||||
}
|
||||
|
||||
onSearch(searchTerm: string) {
|
||||
this.configStore.updateSearchTerm(searchTerm);
|
||||
}
|
||||
|
||||
upgrade(index: number) {
|
||||
this.configStore.upgradeConfigInDeployment(index);
|
||||
}
|
||||
|
||||
drop(event: CdkDragDrop<Config[]>) {
|
||||
if (event.container.id === 'deployment-list') {
|
||||
if (event.previousContainer.id === 'store-list') {
|
||||
this.configStore.addConfigToDeploymentInPosition(event.previousIndex, event.currentIndex);
|
||||
} else if (event.previousContainer.id === 'deployment-list' && event.currentIndex !== event.previousIndex) {
|
||||
this.configStore.moveConfigInDeployment(event.previousIndex, event.currentIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.deployment$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(s => {
|
||||
this.deployment = cloneDeep(s);
|
||||
onView(id: number, releaseId: number = undefined) {
|
||||
this.dialog.open(JsonViewerComponent, {
|
||||
data: {
|
||||
config1: releaseId === undefined ? undefined : this.filteredDeployment.configs[releaseId].configData,
|
||||
config2: this.filteredConfigs[id].configData,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onEdit(id: number) {
|
||||
this.router.navigate([this.editorService.serviceName, 'edit'], {
|
||||
queryParams: { configName: this.filteredConfigs[id].name },
|
||||
});
|
||||
}
|
||||
|
||||
addToDeployment(id: number) {
|
||||
this.configStore.addConfigToDeployment(id);
|
||||
}
|
||||
|
||||
onClone(id: number) {
|
||||
this.router.navigate([this.editorService.serviceName, 'edit'], {
|
||||
queryParams: { newConfig: true, cloneConfig: this.filteredConfigs[id].name },
|
||||
});
|
||||
}
|
||||
|
||||
onRemove(id: number) {
|
||||
this.configStore.removeConfigFromDeployment(id);
|
||||
}
|
||||
|
||||
onClickCreate() {
|
||||
this.router.navigate([this.editorService.serviceName, 'edit'], { queryParams: { newConfig: true } });
|
||||
}
|
||||
|
||||
onDeploy() {
|
||||
this.configStore.loadPullRequestStatus();
|
||||
this.pullRequestPending$.pipe(skip(1), take(1)).subscribe(a => {
|
||||
if (!a.pull_request_pending) {
|
||||
const dialogRef = this.dialog.open(DeployDialogComponent, {
|
||||
data: cloneDeep(this.deployment),
|
||||
});
|
||||
this.allConfigs$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(r => {
|
||||
this.configs = cloneDeep(r);
|
||||
});
|
||||
|
||||
this.filteredConfigs$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(s => this.filteredConfigs = s);
|
||||
|
||||
this.filteredDeployment$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(s => {
|
||||
this.filteredDeployment = cloneDeep(s);
|
||||
});
|
||||
|
||||
this.deploymentHistory$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(h => this.deploymentHistory = { fileHistory: h });
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.ngUnsubscribe.next();
|
||||
this.ngUnsubscribe.complete();
|
||||
}
|
||||
|
||||
public onSearch(searchTerm: string) {
|
||||
this.configStore.updateSearchTerm(searchTerm);
|
||||
}
|
||||
|
||||
public upgrade(index: number) {
|
||||
this.configStore.upgradeConfigInDeployment(index);
|
||||
}
|
||||
|
||||
public drop(event: CdkDragDrop<Config[]>) {
|
||||
if (event.container.id === 'deployment-list') {
|
||||
if (event.previousContainer.id === 'store-list') {
|
||||
this.configStore.addConfigToDeploymentInPosition(event.previousIndex, event.currentIndex);
|
||||
} else if (event.previousContainer.id === 'deployment-list' && event.currentIndex !== event.previousIndex) {
|
||||
this.configStore.moveConfigInDeployment(event.previousIndex, event.currentIndex);
|
||||
dialogRef.afterClosed().subscribe((results: Deployment) => {
|
||||
if (results && results.configs.length > 0) {
|
||||
if (results.deploymentVersion >= 0) {
|
||||
this.configStore.submitRelease(results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public onView(id: number, releaseId: number = undefined) {
|
||||
this.dialog.open(JsonViewerComponent, {
|
||||
data: {
|
||||
config1: releaseId === undefined
|
||||
? undefined
|
||||
: this.filteredDeployment.configs[releaseId].configData,
|
||||
config2: this.filteredConfigs[id].configData,
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.snackbar.openNotification(this.PR_OPEN_MESSAGE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public onEdit(id: number) {
|
||||
this.router.navigate(
|
||||
[this.editorService.serviceName, 'edit'],
|
||||
{ queryParams: { configName: this.filteredConfigs[id].name } }
|
||||
);
|
||||
}
|
||||
onFilterMine($event: boolean) {
|
||||
this.configStore.updateFilterMyConfigs($event);
|
||||
}
|
||||
|
||||
public addToDeployment(id: number) {
|
||||
this.configStore.addConfigToDeployment(id);
|
||||
}
|
||||
onSyncWithGit() {
|
||||
this.blockUI.start('loading store and deployments');
|
||||
this.configStore.reloadStoreAndDeployment().subscribe(() => {
|
||||
this.blockUI.stop();
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.blockUI.stop();
|
||||
}, this.configService.blockingTimeout);
|
||||
}
|
||||
|
||||
public onClone(id: number) {
|
||||
this.router.navigate(
|
||||
[this.editorService.serviceName, 'edit'],
|
||||
{ queryParams: { newConfig: true, cloneConfig: this.filteredConfigs[id].name } }
|
||||
);
|
||||
}
|
||||
duplicateItemCheck(item: CdkDrag<Config>, deployment: CdkDropList<Config[]>) {
|
||||
return deployment.data.find(d => d.name === item.data.name) === undefined ? true : false;
|
||||
}
|
||||
|
||||
public onRemove(id: number) {
|
||||
this.configStore.removeConfigFromDeployment(id);
|
||||
}
|
||||
noReturnPredicate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public onClickCreate() {
|
||||
this.router.navigate(
|
||||
[this.editorService.serviceName, 'edit'],
|
||||
{ queryParams: { newConfig: true } });
|
||||
}
|
||||
onFilterUpgradable($event: boolean) {
|
||||
this.configStore.updateFilterUpgradable($event);
|
||||
}
|
||||
|
||||
public onDeploy() {
|
||||
this.configStore.loadPullRequestStatus();
|
||||
this.pullRequestPending$.pipe(skip(1), take(1)).subscribe(a => {
|
||||
if (!a.pull_request_pending) {
|
||||
const dialogRef = this.dialog.open(DeployDialogComponent, {
|
||||
data: cloneDeep(this.deployment),
|
||||
});
|
||||
dialogRef.afterClosed().subscribe((results: Deployment) => {
|
||||
if (results && results.configs.length > 0) {
|
||||
if (results.deploymentVersion >= 0) {
|
||||
this.configStore.submitRelease(results);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.snackbar.openNotification(this.PR_OPEN_MESSAGE);
|
||||
}
|
||||
});
|
||||
}
|
||||
onFilterUndeployed($event: boolean) {
|
||||
this.configStore.updateFilterUndeployed($event);
|
||||
}
|
||||
|
||||
public onFilterMine($event: boolean) {
|
||||
this.configStore.updateFilterMyConfigs($event);
|
||||
}
|
||||
trackConfigByName(index: number, item: Config) {
|
||||
return item.name;
|
||||
}
|
||||
|
||||
public onSyncWithGit() {
|
||||
this.blockUI.start("loading store and deployments");
|
||||
this.configStore.reloadStoreAndDeployment().subscribe(() => {
|
||||
this.blockUI.stop();
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.blockUI.stop();
|
||||
}, this.BLOCKING_TIMEOUT);
|
||||
}
|
||||
|
||||
public duplicateItemCheck(item: CdkDrag<Config>, deployment: CdkDropList<Config[]>) {
|
||||
return deployment.data.find(d => d.name === item.data.name) === undefined
|
||||
? true : false;
|
||||
}
|
||||
|
||||
public noReturnPredicate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public onFilterUpgradable($event: boolean) {
|
||||
this.configStore.updateFilterUpgradable($event);
|
||||
}
|
||||
|
||||
public onFilterUndeployed($event: boolean) {
|
||||
this.configStore.updateFilterUndeployed($event);
|
||||
}
|
||||
|
||||
public trackConfigByName(index: number, item: Config) {
|
||||
return item.name;
|
||||
}
|
||||
deleteConfigFromStore(index: number) {
|
||||
this.blockUI.start('deleting config');
|
||||
this.configStore.deleteConfig(this.filteredConfigs[index].name).subscribe(() => {
|
||||
this.blockUI.stop();
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.blockUI.stop();
|
||||
}, this.configService.blockingTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { CONFIG_TAB, TEST_CASE_TAB } from '@app/model/test-case';
|
||||
import { EditorService } from '@app/services/editor.service';
|
||||
import { FormlyJsonschema } from '@ngx-formly/core/json-schema';
|
||||
import { of } from 'rxjs';
|
||||
import { EditorComponent } from '../editor/editor.component';
|
||||
import { EditorViewComponent } from './editor-view.component';
|
||||
|
||||
const MockEditorService = {
|
||||
serviceName: 'test',
|
||||
configSchema: { schema: {} },
|
||||
configStore: {
|
||||
editedConfig$: of({}),
|
||||
editingTestCase$: of(true),
|
||||
},
|
||||
metaDataMap: {
|
||||
testing: {
|
||||
perConfigTesting: true,
|
||||
testCaseEnabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 're-generic-editor',
|
||||
template: '',
|
||||
providers: [
|
||||
{
|
||||
provide: EditorComponent,
|
||||
useClass: EditorStubComponent,
|
||||
},
|
||||
],
|
||||
})
|
||||
class EditorStubComponent {
|
||||
form = { valid: true };
|
||||
}
|
||||
|
||||
describe('EditorViewComponent', () => {
|
||||
const routerSpy = { navigate: jasmine.createSpy('navigate') };
|
||||
const formlySpy = { toFieldConfig: jasmine.createSpy('navigate') };
|
||||
let component: EditorViewComponent;
|
||||
let fixture: ComponentFixture<EditorViewComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [EditorViewComponent, EditorStubComponent],
|
||||
providers: [
|
||||
{ provide: EditorService, useValue: MockEditorService },
|
||||
{ provide: Router, useValue: routerSpy },
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
snapshot: { params: { configName: 'test' } },
|
||||
},
|
||||
},
|
||||
{ provide: ChangeDetectorRef, useValue: {} },
|
||||
{ provide: FormlyJsonschema, useValue: formlySpy },
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EditorViewComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.editorComponent = TestBed.createComponent(EditorStubComponent).componentInstance as EditorComponent;
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should navigate to testCase`, fakeAsync(() => {
|
||||
expect(component.selectedTab).toBe(CONFIG_TAB.index);
|
||||
component.ngOnInit();
|
||||
tick();
|
||||
expect(component.selectedTab).toBe(TEST_CASE_TAB.index);
|
||||
}));
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Config } from '@app/model';
|
||||
import { CONFIG_TAB, TESTING_TAB, TEST_CASE_TAB } from '@app/model/test-case';
|
||||
@@ -18,73 +17,66 @@ import { SchemaService } from '@app/services/schema/schema.service';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 're-editor-view',
|
||||
styleUrls: ['./editor-view.component.scss'],
|
||||
templateUrl: './editor-view.component.html'
|
||||
templateUrl: './editor-view.component.html',
|
||||
})
|
||||
export class EditorViewComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(EditorComponent, { static: false }) editorComponent: EditorComponent;
|
||||
export class EditorViewComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
@ViewChild(EditorComponent) editorComponent: EditorComponent;
|
||||
|
||||
readonly TEST_CASE_TAB = TEST_CASE_TAB;
|
||||
readonly TESTING_TAB = TESTING_TAB;
|
||||
readonly CONFIG_TAB = CONFIG_TAB;
|
||||
readonly NO_TAB = -1;
|
||||
ngUnsubscribe = new Subject();
|
||||
|
||||
testCaseEnabled: () => boolean = () => false;
|
||||
testingEnabled: () => boolean = () => false;
|
||||
configData: any;
|
||||
serviceName: string;
|
||||
schema: JSONSchema7;
|
||||
selectedTab = this.NO_TAB;
|
||||
selectedTab = this.CONFIG_TAB.index;
|
||||
previousTab = this.NO_TAB;
|
||||
testingType = TestingType.CONFIG_TESTING;
|
||||
|
||||
fields: FormlyFieldConfig[] = [];
|
||||
|
||||
editedConfig$: Observable<Config>;
|
||||
|
||||
constructor(
|
||||
private formlyJsonschema: FormlyJsonschema,
|
||||
private editorService: EditorService,
|
||||
private router: Router,
|
||||
private activeRoute: ActivatedRoute,
|
||||
private cd: ChangeDetectorRef
|
||||
private activeRoute: ActivatedRoute
|
||||
) {
|
||||
this.serviceName = editorService.serviceName;
|
||||
this.schema = editorService.configSchema.schema;
|
||||
this.editedConfig$ = editorService.configStore.editedConfig$;
|
||||
this.fields = [
|
||||
this.formlyJsonschema.toFieldConfig(cloneDeep(this.schema), {map: SchemaService.renameDescription}),
|
||||
this.formlyJsonschema.toFieldConfig(cloneDeep(this.schema), { map: SchemaService.renameDescription }),
|
||||
];
|
||||
}
|
||||
|
||||
testCaseEnabled: () => boolean = () => false;
|
||||
testingEnabled: () => boolean = () => false;
|
||||
|
||||
ngOnInit() {
|
||||
this.editorService.configStore.editingTestCase$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(e => {
|
||||
if (e) {
|
||||
this.selectedTab = TEST_CASE_TAB.index;
|
||||
if (this.previousTab === this.NO_TAB) {
|
||||
this.previousTab = this.selectedTab;
|
||||
}
|
||||
}
|
||||
if (this.previousTab === this.NO_TAB) {
|
||||
this.previousTab = this.selectedTab;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ngAfterViewInit() {
|
||||
ngAfterViewInit() {
|
||||
this.editedConfig$.pipe(takeUntil(this.ngUnsubscribe)).subscribe((config: Config) => {
|
||||
this.fields = [
|
||||
this.formlyJsonschema.toFieldConfig(cloneDeep(this.schema), {map: SchemaService.renameDescription}),
|
||||
this.formlyJsonschema.toFieldConfig(cloneDeep(this.schema), { map: SchemaService.renameDescription }),
|
||||
];
|
||||
|
||||
this.testingEnabled = () => this.editorService.metaDataMap.testing.perConfigTestEnabled
|
||||
&& this.editorComponent.form.valid;
|
||||
this.testingEnabled = () =>
|
||||
this.editorService.metaDataMap.testing.perConfigTestEnabled && this.editorComponent.form.valid;
|
||||
|
||||
|
||||
this.testCaseEnabled = () => this.editorService.metaDataMap.testing.testCaseEnabled
|
||||
&& this.editorComponent.form.valid
|
||||
&& !config.isNew;
|
||||
this.testCaseEnabled = () =>
|
||||
this.editorService.metaDataMap.testing.testCaseEnabled && this.editorComponent.form.valid && !config.isNew;
|
||||
|
||||
this.configData = config.configData;
|
||||
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -95,15 +87,13 @@ export class EditorViewComponent implements OnInit, OnDestroy {
|
||||
|
||||
onTabChange() {
|
||||
if (this.previousTab === TEST_CASE_TAB.index) {
|
||||
this.router.navigate(
|
||||
[],
|
||||
{
|
||||
relativeTo: this.activeRoute,
|
||||
queryParams: { testCaseName: null },
|
||||
queryParamsHandling: 'merge',
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.activeRoute,
|
||||
queryParams: { testCaseName: null },
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
} else if (this.previousTab === CONFIG_TAB.index) {
|
||||
this.editorComponent.updateConfigInStore()
|
||||
this.editorComponent.updateConfigInStore();
|
||||
}
|
||||
this.previousTab = this.selectedTab;
|
||||
}
|
||||
@@ -111,5 +101,4 @@ export class EditorViewComponent implements OnInit, OnDestroy {
|
||||
changeRoute() {
|
||||
this.router.navigate([this.serviceName]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,95 +1,101 @@
|
||||
<mat-card>
|
||||
<mat-card-title *ngIf="!(editingTestCase$ | async)">
|
||||
<h2>Test Suite</h2>
|
||||
<div class="button-group-end">
|
||||
<button mat-icon-button color="primary" title="Add New Test Case" (click)="onAddTestCase()">
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
<button mat-raised-button color="accent" title="Run Test Suite" (click)="onRunTestSuite()"
|
||||
[disabled]="testCases?.length === 0">Run Test Suite</button>
|
||||
</div>
|
||||
</mat-card-title>
|
||||
<mat-card-title *ngIf="(editingTestCase$ | async)">
|
||||
<button mat-raised-button title="Test Suite" (click)="onCancelEditing()"><</button>
|
||||
<h2>{{ testCase.testCase.test_case_name }} v{{ testCase.testCase.version }}</h2>
|
||||
<div class="test-status-badge" *ngIf="testCase?.testCaseResult"
|
||||
[ngClass]="getTestBadge(testCase.testCaseResult)"
|
||||
[popper]="testCaseResult"
|
||||
[popperTrigger]="'hover'"
|
||||
[popperPlacement]="'left-start'"
|
||||
[popperHideOnScroll]="true"
|
||||
[popperDisableStyle]="true">
|
||||
<popper-content #testCaseResult>
|
||||
<re-test-results [testResult]="testCase.testCaseResult"></re-test-results>
|
||||
</popper-content>
|
||||
{{testCase.testCaseResult?.evaluationResult?.number_matched_assertions}}<mat-icon
|
||||
class="test-icon">done</mat-icon>{{testCase.testCaseResult?.evaluationResult?.number_failed_assertions}}<mat-icon
|
||||
class="test-icon">clear</mat-icon>
|
||||
{{testCase.testCaseResult?.evaluationResult?.number_skipped_assertions}}<strong>?</strong>
|
||||
</div>
|
||||
</mat-card-title>
|
||||
<div *ngIf="!(editingTestCase$ | async); else editTestCaseTemplate">
|
||||
<div class="box" *ngFor="let testCase of testCases; index as i">
|
||||
<div class="inline">
|
||||
<div class="column-fixed"
|
||||
[popper]="fileHistory"
|
||||
[popperTrigger]="'hover'"
|
||||
[popperPlacement]="'left-start'"
|
||||
[popperHideOnScroll]="true"
|
||||
[popperDisableStyle]="true">
|
||||
<popper-content #fileHistory>
|
||||
<re-change-history [history]="testCase.fileHistory"></re-change-history>
|
||||
</popper-content>
|
||||
<span class="chip-bag">
|
||||
<span #versionChip class="chip">v{{testCase?.testCase?.version || '0' }}</span>
|
||||
</span>
|
||||
<h4 class="author">{{testCase?.testCase?.author}}</h4>
|
||||
</div>
|
||||
<mat-divider [vertical]="true"></mat-divider>
|
||||
<div class="column">
|
||||
<h3 class="rule-title">{{ testCase?.testCase?.test_case_name }}</h3>
|
||||
<div class="subtitle">
|
||||
{{ testCase?.testCase?.description }} Assertions: {{ testCase?.testCase?.assertions?.length }}
|
||||
|
||||
<block-ui>
|
||||
<mat-card>
|
||||
<mat-card-title *ngIf="(editingTestCase$ | async) === false">
|
||||
<h2>Test Suite</h2>
|
||||
<div class="button-group-end">
|
||||
<button mat-icon-button color="primary" title="Add New Test Case" (click)="onAddTestCase()">
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
<button mat-raised-button color="accent" title="Run Test Suite" (click)="onRunTestSuite()"
|
||||
[disabled]="testCases?.length === 0">Run Test Suite</button>
|
||||
</div>
|
||||
</mat-card-title>
|
||||
<mat-card-title *ngIf="(editingTestCase$ | async)">
|
||||
<button mat-raised-button title="Test Suite" (click)="onCancelEditing()"><</button>
|
||||
<h2>{{ testCase.testCase.test_case_name }} v{{ testCase.testCase.version }}</h2>
|
||||
<div class="test-status-badge" *ngIf="testCase?.testCaseResult"
|
||||
[ngClass]="getTestBadge(testCase.testCaseResult)"
|
||||
[popper]="testCaseResult"
|
||||
[popperTrigger]="'hover'"
|
||||
[popperPlacement]="'left-start'"
|
||||
[popperHideOnScroll]="true"
|
||||
[popperDisableStyle]="true">
|
||||
<popper-content #testCaseResult>
|
||||
<re-test-results [testResult]="testCase.testCaseResult"></re-test-results>
|
||||
</popper-content>
|
||||
{{testCase.testCaseResult?.evaluationResult?.number_matched_assertions}}<mat-icon
|
||||
class="test-icon">done</mat-icon>{{testCase.testCaseResult?.evaluationResult?.number_failed_assertions}}<mat-icon
|
||||
class="test-icon">clear</mat-icon>
|
||||
{{testCase.testCaseResult?.evaluationResult?.number_skipped_assertions}}<strong>?</strong>
|
||||
</div>
|
||||
</mat-card-title>
|
||||
<div *ngIf="(editingTestCase$ | async) === false; else editTestCaseTemplate">
|
||||
<div class="box" *ngFor="let testCase of testCases; index as i">
|
||||
<div class="inline">
|
||||
<div class="column-fixed"
|
||||
[popper]="fileHistory"
|
||||
[popperTrigger]="'hover'"
|
||||
[popperPlacement]="'left-start'"
|
||||
[popperHideOnScroll]="true"
|
||||
[popperDisableStyle]="true">
|
||||
<popper-content #fileHistory>
|
||||
<re-change-history [history]="testCase.fileHistory"></re-change-history>
|
||||
</popper-content>
|
||||
<span class="chip-bag">
|
||||
<span #versionChip class="chip">v{{testCase?.testCase?.version || '0' }}</span>
|
||||
</span>
|
||||
<h4 class="author">{{testCase?.testCase?.author}}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-block">
|
||||
<div class="button-group-end">
|
||||
<span class="buttons">
|
||||
<a (click)="onEditTestCase(i)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</a>
|
||||
<a (click)="onCloneTestCase(i)">
|
||||
<mat-icon>content_copy</mat-icon>
|
||||
</a>
|
||||
</span>
|
||||
<span>
|
||||
<div *ngIf="testCase?.testCaseResult?.evaluationResult"
|
||||
class="test-status-badge"
|
||||
[ngClass]="getTestBadge(testCase.testCaseResult)"
|
||||
[popper]="testCaseResult"
|
||||
[popperTrigger]="'hover'"
|
||||
[popperPlacement]="'left-start'"
|
||||
[popperHideOnScroll]="true"
|
||||
[popperDisableStyle]="true"
|
||||
[popperAppendTo]="'body'"
|
||||
[popperModifiers]="{preventOverflow: {boundariesElement: 'viewport'}}"
|
||||
>
|
||||
<popper-content #testCaseResult>
|
||||
<re-test-results [testResult]="testCase.testCaseResult"></re-test-results>
|
||||
</popper-content>
|
||||
{{testCase?.testCaseResult?.evaluationResult?.number_matched_assertions}}<mat-icon
|
||||
class="test-icon">done</mat-icon>
|
||||
{{testCase?.testCaseResult?.evaluationResult?.number_failed_assertions}}<mat-icon
|
||||
class="test-icon">clear</mat-icon>
|
||||
{{testCase?.testCaseResult?.evaluationResult?.number_skipped_assertions}}<strong>?</strong>
|
||||
</div>
|
||||
</span>
|
||||
<mat-divider [vertical]="true"></mat-divider>
|
||||
<div class="column">
|
||||
<h3 class="rule-title">{{ testCase?.testCase?.test_case_name }}</h3>
|
||||
<div class="subtitle">
|
||||
{{ testCase?.testCase?.description }} Assertions: {{ testCase?.testCase?.assertions?.length }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-block">
|
||||
<div class="button-group-end">
|
||||
<span class="buttons">
|
||||
<a (click)="onEditTestCase(i)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</a>
|
||||
<a (click)="onCloneTestCase(i)">
|
||||
<mat-icon>content_copy</mat-icon>
|
||||
</a>
|
||||
<a (click)="onDeleteTestCase(i)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</a>
|
||||
</span>
|
||||
<span>
|
||||
<div *ngIf="testCase?.testCaseResult?.evaluationResult"
|
||||
class="test-status-badge"
|
||||
[ngClass]="getTestBadge(testCase.testCaseResult)"
|
||||
[popper]="testCaseResult"
|
||||
[popperTrigger]="'hover'"
|
||||
[popperPlacement]="'left-start'"
|
||||
[popperHideOnScroll]="true"
|
||||
[popperDisableStyle]="true"
|
||||
[popperAppendTo]="'body'"
|
||||
[popperModifiers]="{preventOverflow: {boundariesElement: 'viewport'}}"
|
||||
>
|
||||
<popper-content #testCaseResult>
|
||||
<re-test-results [testResult]="testCase.testCaseResult"></re-test-results>
|
||||
</popper-content>
|
||||
{{testCase?.testCaseResult?.evaluationResult?.number_matched_assertions}}<mat-icon
|
||||
class="test-icon">done</mat-icon>
|
||||
{{testCase?.testCaseResult?.evaluationResult?.number_failed_assertions}}<mat-icon
|
||||
class="test-icon">clear</mat-icon>
|
||||
{{testCase?.testCaseResult?.evaluationResult?.number_skipped_assertions}}<strong>?</strong>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #editTestCaseTemplate>
|
||||
<re-test-case-editor></re-test-case-editor>
|
||||
</ng-template>
|
||||
</mat-card>
|
||||
<ng-template #editTestCaseTemplate>
|
||||
<re-test-case-editor></re-test-case-editor>
|
||||
</ng-template>
|
||||
</mat-card>
|
||||
</block-ui>
|
||||
@@ -7,108 +7,119 @@ import { TestCaseWrapper } from '@model/test-case';
|
||||
import { TestStoreService } from '../../../services/store/test-store.service';
|
||||
import { TestCaseResult } from '../../../model/test-case';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { BlockUI, NgBlockUI } from 'ng-block-ui';
|
||||
import { AppConfigService } from '@app/services/app-config.service';
|
||||
|
||||
@Component({
|
||||
selector: 're-test-centre',
|
||||
templateUrl: './test-centre.component.html',
|
||||
styleUrls: ['./test-centre.component.scss'],
|
||||
selector: 're-test-centre',
|
||||
templateUrl: './test-centre.component.html',
|
||||
styleUrls: ['./test-centre.component.scss'],
|
||||
})
|
||||
export class TestCentreComponent implements OnInit, OnDestroy {
|
||||
private testStoreService: TestStoreService;
|
||||
private ngUnsubscribe = new Subject();
|
||||
@BlockUI() blockUI: NgBlockUI;
|
||||
testCases$: Observable<TestCaseWrapper[]>;
|
||||
editingTestCase$: Observable<boolean>;
|
||||
editedTestCase$: Observable<TestCaseWrapper>;
|
||||
|
||||
public testCases$: Observable<TestCaseWrapper[]>;
|
||||
public editingTestCase$: Observable<boolean>;
|
||||
public editedTestCase$: Observable<TestCaseWrapper>;
|
||||
testCases: TestCaseWrapper[];
|
||||
testCase: TestCaseWrapper;
|
||||
|
||||
public testCases: TestCaseWrapper[];
|
||||
public testCase: TestCaseWrapper;
|
||||
private testStoreService: TestStoreService;
|
||||
private ngUnsubscribe = new Subject();
|
||||
constructor(
|
||||
private editorService: EditorService,
|
||||
public snackbar: PopupService,
|
||||
private router: Router,
|
||||
private activeRoute: ActivatedRoute,
|
||||
private configService: AppConfigService
|
||||
) {
|
||||
this.testCases$ = this.editorService.configStore.editedConfigTestCases$;
|
||||
this.editingTestCase$ = this.editorService.configStore.editingTestCase$;
|
||||
this.testStoreService = this.editorService.configStore.testService;
|
||||
this.editedTestCase$ = this.editorService.configStore.editedTestCase$;
|
||||
}
|
||||
|
||||
constructor(private editorService: EditorService,
|
||||
public snackbar: PopupService,
|
||||
private router: Router,
|
||||
private activeRoute: ActivatedRoute) {
|
||||
this.testCases$ = this.editorService.configStore.editedConfigTestCases$;
|
||||
this.editingTestCase$ = this.editorService.configStore.editingTestCase$;
|
||||
this.testStoreService = this.editorService.configStore.testService;
|
||||
this.editedTestCase$ = this.editorService.configStore.editedTestCase$;
|
||||
ngOnInit() {
|
||||
if (this.editorService.metaDataMap.testing.testCaseEnabled) {
|
||||
this.testCases$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(testCases => {
|
||||
this.testCases = testCases;
|
||||
});
|
||||
this.editedTestCase$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(testCaseWrapper => {
|
||||
this.testCase = testCaseWrapper;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.ngUnsubscribe.next();
|
||||
this.ngUnsubscribe.complete();
|
||||
}
|
||||
|
||||
onAddTestCase() {
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.activeRoute,
|
||||
queryParams: { newTestCase: true },
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
|
||||
onEditTestCase(index: number) {
|
||||
const name = this.testCases[index].testCase.test_case_name;
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.activeRoute,
|
||||
queryParams: { testCaseName: name },
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
|
||||
onCloneTestCase(index: number) {
|
||||
const name = this.testCases[index].testCase.test_case_name;
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.activeRoute,
|
||||
queryParams: { newTestCase: true, cloneTestCase: name },
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
|
||||
onRunTestSuite() {
|
||||
this.testStoreService.runEditedConfigTestSuite();
|
||||
}
|
||||
|
||||
onCancelEditing() {
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.activeRoute,
|
||||
queryParams: { testCaseName: null, newTestCase: null, cloneTestCase: null },
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
|
||||
getTestBadge(testCaseResult: TestCaseResult): string {
|
||||
if (!testCaseResult) {
|
||||
return 'test-default';
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.editorService.metaDataMap.testing.testCaseEnabled) {
|
||||
this.testCases$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(testCases => {
|
||||
this.testCases = testCases;
|
||||
});
|
||||
this.editedTestCase$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(testCaseWrapper => {
|
||||
this.testCase = testCaseWrapper;
|
||||
});
|
||||
}
|
||||
if (testCaseResult.isRunning) {
|
||||
return 'test-running';
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.ngUnsubscribe.next();
|
||||
this.ngUnsubscribe.complete();
|
||||
}
|
||||
return !testCaseResult.evaluationResult
|
||||
? 'test-skipped'
|
||||
: testCaseResult.evaluationResult.number_failed_assertions > 0
|
||||
? 'test-fail'
|
||||
: testCaseResult.evaluationResult.number_skipped_assertions > 0
|
||||
? 'test-skipped'
|
||||
: 'test-success';
|
||||
}
|
||||
|
||||
onAddTestCase() {
|
||||
this.router.navigate(
|
||||
[],
|
||||
{
|
||||
relativeTo: this.activeRoute,
|
||||
queryParams: { newTestCase: true },
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
|
||||
onEditTestCase(index: number) {
|
||||
const name = this.testCases[index].testCase.test_case_name;
|
||||
this.router.navigate(
|
||||
[],
|
||||
{
|
||||
relativeTo: this.activeRoute,
|
||||
queryParams: { testCaseName: name },
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
|
||||
onCloneTestCase(index: number) {
|
||||
const name = this.testCases[index].testCase.test_case_name;
|
||||
this.router.navigate(
|
||||
[],
|
||||
{
|
||||
relativeTo: this.activeRoute,
|
||||
queryParams: { newTestCase: true, cloneTestCase: name },
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
|
||||
onRunTestSuite() {
|
||||
this.testStoreService.runEditedConfigTestSuite();
|
||||
}
|
||||
|
||||
onCancelEditing() {
|
||||
this.router.navigate(
|
||||
[],
|
||||
{
|
||||
relativeTo: this.activeRoute,
|
||||
queryParams: { testCaseName: null, newTestCase: null, cloneTestCase: null},
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
|
||||
getTestBadge(testCaseResult: TestCaseResult): string {
|
||||
if (!testCaseResult) {
|
||||
return 'test-default';
|
||||
}
|
||||
|
||||
if (testCaseResult.isRunning) {
|
||||
return 'test-running'
|
||||
}
|
||||
|
||||
return !testCaseResult.evaluationResult
|
||||
? 'test-skipped' : testCaseResult.evaluationResult.number_failed_assertions > 0
|
||||
? 'test-fail' : testCaseResult.evaluationResult.number_skipped_assertions > 0
|
||||
? 'test-skipped' : 'test-success';
|
||||
|
||||
}
|
||||
onDeleteTestCase(index: number) {
|
||||
this.blockUI.start('deleting test case');
|
||||
this.editorService.configStore
|
||||
.deleteTestCase(this.testCases[index].testCase.config_name, this.testCases[index].testCase.test_case_name)
|
||||
.subscribe(() => {
|
||||
this.blockUI.stop();
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.blockUI.stop();
|
||||
}, this.configService.blockingTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,10 @@
|
||||
<a (click)="cloneConfig()" [title]="'Clone Config'">
|
||||
<mat-icon>content_copy</mat-icon>
|
||||
</a>
|
||||
<a *ngIf="hideAddDeployment" (click)="addToDeployment()" [title]="'Add to Deployment'">
|
||||
<a *ngIf="notDeployed" (click)="deleteConfigFromStore()" [title]="'Delete Config From Store'">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</a>
|
||||
<a *ngIf="notDeployed" (click)="addConfigToDeployment()" [title]="'Add to Deployment'">
|
||||
<mat-icon>arrow_forward</mat-icon>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
@@ -1,38 +1,39 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { ConfigData, Config } from '../../model/config-model';
|
||||
import { Config } from '../../model/config-model';
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 're-config-tile',
|
||||
styleUrls: ['./config-tile.component.scss'],
|
||||
templateUrl: './config-tile.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 're-config-tile',
|
||||
styleUrls: ['./config-tile.component.scss'],
|
||||
templateUrl: './config-tile.component.html',
|
||||
})
|
||||
export class ConfigTileComponent {
|
||||
@Input() config: Config;
|
||||
@Input() notDeployed: boolean;
|
||||
|
||||
@Input() config: Config;
|
||||
@Input() hideAddDeployment: boolean;
|
||||
@Output() readonly edit = new EventEmitter<number>();
|
||||
@Output() readonly view = new EventEmitter<number>();
|
||||
@Output() readonly addToDeployment = new EventEmitter<number>();
|
||||
@Output() readonly clone = new EventEmitter<number>();
|
||||
@Output() readonly deleteFromStore = new EventEmitter<number>();
|
||||
|
||||
@Output() onEdit = new EventEmitter<number>();
|
||||
@Output() onView = new EventEmitter<number>();
|
||||
@Output() onAddToDeployment = new EventEmitter<number>();
|
||||
@Output() onClone = new EventEmitter<number>();
|
||||
editConfig() {
|
||||
this.edit.emit();
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
viewConfig() {
|
||||
this.view.emit();
|
||||
}
|
||||
|
||||
editConfig() {
|
||||
this.onEdit.emit();
|
||||
}
|
||||
addConfigToDeployment() {
|
||||
this.addToDeployment.emit();
|
||||
}
|
||||
|
||||
viewConfig() {
|
||||
this.onView.emit();
|
||||
}
|
||||
|
||||
addToDeployment() {
|
||||
this.onAddToDeployment.emit();
|
||||
}
|
||||
|
||||
cloneConfig() {
|
||||
this.onClone.emit();
|
||||
}
|
||||
cloneConfig() {
|
||||
this.clone.emit();
|
||||
}
|
||||
|
||||
deleteConfigFromStore() {
|
||||
this.deleteFromStore.emit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
import { UserSettings } from "oidc-client";
|
||||
import { UserSettings } from 'oidc-client';
|
||||
|
||||
export interface BuildInfo {
|
||||
appName: string;
|
||||
appVersion: number;
|
||||
buildDate: Date;
|
||||
angularVersion: string;
|
||||
appName: string;
|
||||
appVersion: number;
|
||||
buildDate: Date;
|
||||
angularVersion: string;
|
||||
}
|
||||
|
||||
export enum AuthenticationType {
|
||||
Disabled = "disabled",
|
||||
Kerberos = "kerberos",
|
||||
Oauth2 = "oauth2",
|
||||
Disabled = 'disabled',
|
||||
Kerberos = 'kerberos',
|
||||
Oauth2 = 'oauth2',
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
environment: string;
|
||||
serviceRoot: string;
|
||||
aboutApp: BuildInfo;
|
||||
authType: AuthenticationType,
|
||||
authAttributes: Oauth2Attributes | any
|
||||
homeHelpLinks?: HomeHelpLink[];
|
||||
historyMaxSize?: number;
|
||||
environment: string;
|
||||
serviceRoot: string;
|
||||
aboutApp: BuildInfo;
|
||||
authType: AuthenticationType;
|
||||
authAttributes: Oauth2Attributes | any;
|
||||
homeHelpLinks?: HomeHelpLink[];
|
||||
historyMaxSize?: number;
|
||||
blockingTimeout?: number;
|
||||
}
|
||||
|
||||
export interface HomeHelpLink {
|
||||
title: string;
|
||||
icon: string;
|
||||
link: string;
|
||||
title: string;
|
||||
icon: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export interface Oauth2Attributes {
|
||||
callbackPath: string;
|
||||
expiresIntervalMinimum: number;
|
||||
oidcSettings: UserSettings;
|
||||
}
|
||||
callbackPath: string;
|
||||
expiresIntervalMinimum: number;
|
||||
oidcSettings: UserSettings;
|
||||
}
|
||||
|
||||
@@ -1,168 +1,176 @@
|
||||
import { TestCase, TestCaseWrapper, TestCaseEvaluationResult } from './test-case';
|
||||
import { TestCase, TestCaseWrapper, TestCaseEvaluationResult, TestCaseMap } from './test-case';
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export const NAME_REGEX = "^[a-zA-Z0-9_\\-]+$";
|
||||
export const NAME_REGEX = '^[a-zA-Z0-9_\\-]+$';
|
||||
|
||||
export const repoNames = {
|
||||
store_directory_name: "Config Store Folder",
|
||||
release_directory_name: "Config Deployment Folder",
|
||||
testcase_store_directory_name: "Config Testcase Folder",
|
||||
admin_config_store_directory_name: "Admin Config Folder"
|
||||
}
|
||||
export const repoNames = {
|
||||
store_directory_name: 'Config Store Folder',
|
||||
release_directory_name: 'Config Deployment Folder',
|
||||
testcase_store_directory_name: 'Config Testcase Folder',
|
||||
admin_config_store_directory_name: 'Admin Config Folder',
|
||||
};
|
||||
|
||||
export enum TestingType {
|
||||
DEPLOYMENT_TESTING = 'deployment_testing',
|
||||
CONFIG_TESTING = 'config_testing'
|
||||
DEPLOYMENT_TESTING = 'deployment_testing',
|
||||
CONFIG_TESTING = 'config_testing',
|
||||
}
|
||||
|
||||
export enum Type {
|
||||
CONFIG_TYPE = 'Config',
|
||||
TESTCASE_TYPE = 'TestCase',
|
||||
ADMIN_TYPE = 'Admin'
|
||||
CONFIG_TYPE = 'Config',
|
||||
TESTCASE_TYPE = 'TestCase',
|
||||
ADMIN_TYPE = 'Admin',
|
||||
}
|
||||
|
||||
export enum UserRole {
|
||||
SERVICE_USER = 'service_user',
|
||||
SERVICE_ADMIN = 'service_admin'
|
||||
SERVICE_USER = 'service_user',
|
||||
SERVICE_ADMIN = 'service_admin',
|
||||
}
|
||||
|
||||
export interface SubmitDialogData {
|
||||
name: string,
|
||||
type: string,
|
||||
validate: () => Observable<any>;
|
||||
submit: () => Observable<boolean>;
|
||||
name: string;
|
||||
type: string;
|
||||
validate: () => Observable<any>;
|
||||
submit: () => Observable<boolean>;
|
||||
}
|
||||
|
||||
export interface GitFiles<T> {
|
||||
files: T[];
|
||||
files: T[];
|
||||
}
|
||||
|
||||
export interface GitFilesDelete<T> {
|
||||
configs_files: T[];
|
||||
test_cases_files?: T[];
|
||||
}
|
||||
|
||||
export interface AdminConfigGitFiles<T> extends GitFiles<T> {
|
||||
config_version: number;
|
||||
config_version: number;
|
||||
}
|
||||
|
||||
export interface DeploymentGitFiles<T> extends GitFiles<T> {
|
||||
rules_version: number;
|
||||
rules_version: number;
|
||||
}
|
||||
|
||||
export interface TestCaseEvaluation {
|
||||
files: Content<TestCase>[];
|
||||
test_result_raw_output: string;
|
||||
files: Content<TestCase>[];
|
||||
test_result_raw_output: string;
|
||||
}
|
||||
|
||||
export interface GeneralRule {
|
||||
file_name?: string;
|
||||
file_name?: string;
|
||||
}
|
||||
|
||||
export interface Content<T> extends GeneralRule {
|
||||
content: T;
|
||||
content: T;
|
||||
}
|
||||
|
||||
export interface ServiceInfo {
|
||||
name: string;
|
||||
type: string;
|
||||
user_roles: UserRole[];
|
||||
name: string;
|
||||
type: string;
|
||||
user_roles: UserRole[];
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
user_name: string;
|
||||
services: ServiceInfo[];
|
||||
|
||||
user_name: string;
|
||||
services: ServiceInfo[];
|
||||
}
|
||||
|
||||
export interface RepositoryLinksWrapper {
|
||||
rules_repositories: RepositoryLinks;
|
||||
rules_repositories: RepositoryLinks;
|
||||
}
|
||||
|
||||
export interface RepositoryLinks {
|
||||
rule_store_directory_url: string;
|
||||
rules_release_directory_url: string;
|
||||
test_case_store_directory_url: string;
|
||||
admin_config_directory_url: string;
|
||||
service_name: string;
|
||||
rule_store_directory_url: string;
|
||||
rules_release_directory_url: string;
|
||||
test_case_store_directory_url: string;
|
||||
admin_config_directory_url: string;
|
||||
service_name: string;
|
||||
}
|
||||
|
||||
export interface SchemaInfo {
|
||||
rules_schema: JSONSchema7;
|
||||
rules_schema: JSONSchema7;
|
||||
}
|
||||
|
||||
export interface AdminSchemaInfo {
|
||||
admin_config_schema: JSONSchema7;
|
||||
admin_config_schema: JSONSchema7;
|
||||
}
|
||||
|
||||
export interface TestSchemaInfo {
|
||||
test_schema: JSONSchema7;
|
||||
test_schema: JSONSchema7;
|
||||
}
|
||||
|
||||
export interface PullRequestInfo {
|
||||
pull_request_pending: boolean;
|
||||
pull_request_url: string;
|
||||
pull_request_pending: boolean;
|
||||
pull_request_url: string;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
versionFlag?: number;
|
||||
isDeployed?: boolean;
|
||||
isNew: boolean;
|
||||
configData: ConfigData;
|
||||
savedInBackend: boolean;
|
||||
name: string;
|
||||
author: string;
|
||||
version: number;
|
||||
description: string;
|
||||
tags?: string[];
|
||||
fileHistory?: FileHistory[];
|
||||
testCases?: TestCaseWrapper[];
|
||||
versionFlag?: number;
|
||||
isDeployed?: boolean;
|
||||
isNew: boolean;
|
||||
configData: ConfigData;
|
||||
savedInBackend: boolean;
|
||||
name: string;
|
||||
author: string;
|
||||
version: number;
|
||||
description: string;
|
||||
tags?: string[];
|
||||
fileHistory?: FileHistory[];
|
||||
testCases?: TestCaseWrapper[];
|
||||
}
|
||||
|
||||
export interface AdminConfig{
|
||||
configData: ConfigData;
|
||||
version: number;
|
||||
fileHistory?: FileHistory[];
|
||||
export interface AdminConfig {
|
||||
configData: ConfigData;
|
||||
version: number;
|
||||
fileHistory?: FileHistory[];
|
||||
}
|
||||
|
||||
export interface FileHistory {
|
||||
author: string;
|
||||
date: string;
|
||||
removed: number;
|
||||
added: number;
|
||||
author: string;
|
||||
date: string;
|
||||
removed: number;
|
||||
added: number;
|
||||
}
|
||||
|
||||
export interface ConfigTestDto {
|
||||
files: Content<ConfigData>[],
|
||||
test_specification: string,
|
||||
files: Content<ConfigData>[];
|
||||
test_specification: string;
|
||||
}
|
||||
|
||||
export type ConfigData = any;
|
||||
|
||||
export interface Deployment {
|
||||
configs: Config[];
|
||||
deploymentVersion: number;
|
||||
configs: Config[];
|
||||
deploymentVersion: number;
|
||||
}
|
||||
|
||||
export interface ConfigTestResult {
|
||||
exception?: string;
|
||||
message?: string;
|
||||
test_result_output?: string;
|
||||
test_result_complete?: boolean;
|
||||
test_result_raw_output?: object;
|
||||
exception?: string;
|
||||
message?: string;
|
||||
test_result_output?: string;
|
||||
test_result_complete?: boolean;
|
||||
test_result_raw_output?: object;
|
||||
}
|
||||
|
||||
export interface DeploymentWrapper {
|
||||
storedDeployment: Deployment;
|
||||
deploymentHistory: FileHistory[];
|
||||
storedDeployment: Deployment;
|
||||
deploymentHistory: FileHistory[];
|
||||
}
|
||||
|
||||
export interface TestCaseResultAttributes {
|
||||
exception?: string;
|
||||
message?: string;
|
||||
test_case_result?: TestCaseEvaluationResult;
|
||||
exception?: string;
|
||||
message?: string;
|
||||
test_case_result?: TestCaseEvaluationResult;
|
||||
}
|
||||
|
||||
export interface UrlInfo {
|
||||
service?: string,
|
||||
mode?: string,
|
||||
configName?: string,
|
||||
testCaseName?: string
|
||||
service?: string;
|
||||
mode?: string;
|
||||
configName?: string;
|
||||
testCaseName?: string;
|
||||
}
|
||||
|
||||
export interface ConfigAndTestCases {
|
||||
configs: Config[];
|
||||
testCases?: TestCaseMap;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
export {
|
||||
GitFiles, SchemaInfo, Content,
|
||||
PullRequestInfo, Config, ConfigData, Deployment,
|
||||
RepositoryLinks, RepositoryLinksWrapper, FileHistory, NAME_REGEX
|
||||
GitFiles,
|
||||
SchemaInfo,
|
||||
Content,
|
||||
PullRequestInfo,
|
||||
Config,
|
||||
ConfigData,
|
||||
Deployment,
|
||||
RepositoryLinks,
|
||||
RepositoryLinksWrapper,
|
||||
FileHistory,
|
||||
NAME_REGEX,
|
||||
ConfigAndTestCases,
|
||||
} from './config-model';
|
||||
export { BuildInfo, AuthenticationType, AppConfig } from './app-config';
|
||||
export { StatusCode } from './status-code';
|
||||
|
||||
@@ -64,7 +64,7 @@ import { FieldArrayType } from '@ngx-formly/core';
|
||||
],
|
||||
})
|
||||
export class TabArrayTypeComponent extends FieldArrayType {
|
||||
public selectedIndex = 0;
|
||||
selectedIndex = 0;
|
||||
|
||||
getUnionType(model): string {
|
||||
const keys = Object.keys(model);
|
||||
@@ -72,7 +72,8 @@ export class TabArrayTypeComponent extends FieldArrayType {
|
||||
}
|
||||
|
||||
add(i: number) {
|
||||
super.add(this.model.length);
|
||||
const modelLength = this.model ? this.model.length : 0;
|
||||
super.add(modelLength);
|
||||
this.selectedIndex = i;
|
||||
for (let j = this.model.length - 1; j >= i; j--) {
|
||||
this.moveDown(j);
|
||||
|
||||
@@ -26,13 +26,13 @@ export class AppConfigService {
|
||||
this._authenticationService = new DefaultAuthenticationService();
|
||||
}
|
||||
|
||||
public loadConfigAndMetadata(): Promise<any> {
|
||||
loadConfigAndMetadata(): Promise<any> {
|
||||
return this.loadConfig()
|
||||
.then(() => this.loadUiMetadata())
|
||||
.then(() => this.createAuthenticationService());
|
||||
}
|
||||
|
||||
public loadBuildInfo(): Promise<any> {
|
||||
loadBuildInfo(): Promise<any> {
|
||||
return this.http
|
||||
.get('assets/build-info.json')
|
||||
.toPromise()
|
||||
@@ -43,49 +43,53 @@ export class AppConfigService {
|
||||
.catch(err => console.info(`could not load build info: ${err}`));
|
||||
}
|
||||
|
||||
public isHomePath(path: string): boolean {
|
||||
isHomePath(path: string): boolean {
|
||||
if (path === '/home' || path === '/') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public get adminPath(): string {
|
||||
get adminPath(): string {
|
||||
return '/admin';
|
||||
}
|
||||
|
||||
public get config(): AppConfig {
|
||||
get config(): AppConfig {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
public get buildInfo(): BuildInfo {
|
||||
get buildInfo(): BuildInfo {
|
||||
return this._buildInfo;
|
||||
}
|
||||
|
||||
public get environment(): string {
|
||||
get environment(): string {
|
||||
return this._config.environment;
|
||||
}
|
||||
|
||||
public get serviceRoot(): string {
|
||||
get serviceRoot(): string {
|
||||
return this._config.serviceRoot;
|
||||
}
|
||||
|
||||
public get uiMetadata(): UiMetadataMap {
|
||||
get uiMetadata(): UiMetadataMap {
|
||||
return this._uiMetadata;
|
||||
}
|
||||
|
||||
public get authenticationService(): IAuthenticationService {
|
||||
get authenticationService(): IAuthenticationService {
|
||||
return this._authenticationService;
|
||||
}
|
||||
|
||||
public get homeHelpLinks(): HomeHelpLink[] {
|
||||
get homeHelpLinks(): HomeHelpLink[] {
|
||||
return this._config.homeHelpLinks;
|
||||
}
|
||||
|
||||
public get historyMaxSize(): number {
|
||||
get historyMaxSize(): number {
|
||||
return this._config.historyMaxSize ? this._config.historyMaxSize : 5;
|
||||
}
|
||||
|
||||
get blockingTimeout(): number {
|
||||
return this._config.blockingTimeout ? this._config.blockingTimeout : 30000;
|
||||
}
|
||||
|
||||
private loadConfig(): Promise<any> {
|
||||
return this.http
|
||||
.get('config/ui-config.json')
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('ConfigLoaderService', () => {
|
||||
});
|
||||
|
||||
it('should convert testcase files to map', () => {
|
||||
expect(service['testCaseFilesToMap'](mockTestCaseFiles)).toEqual(mockTestCaseMap);
|
||||
expect(service['testCaseFilesToMap'](mockTestCaseFiles.files)).toEqual(mockTestCaseMap);
|
||||
});
|
||||
|
||||
it('should submit config', () => {
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
AdminConfig,
|
||||
AdminConfigGitFiles,
|
||||
DeploymentGitFiles,
|
||||
ConfigAndTestCases,
|
||||
GitFilesDelete,
|
||||
} from '@model/config-model';
|
||||
import { TestCase, TestCaseMap, TestCaseResult, TestCaseWrapper } from '@model/test-case';
|
||||
import { ADMIN_VERSION_FIELD_NAME, UiMetadata } from '@model/ui-metadata-map';
|
||||
@@ -45,7 +47,7 @@ export class ConfigLoaderService {
|
||||
}
|
||||
}
|
||||
|
||||
public getConfigFromFile(file: any): Config {
|
||||
getConfigFromFile(file: any): Config {
|
||||
return {
|
||||
author: file.content[this.uiMetadata.author],
|
||||
configData: file.content,
|
||||
@@ -62,7 +64,7 @@ export class ConfigLoaderService {
|
||||
};
|
||||
}
|
||||
|
||||
public getConfigs(): Observable<Config[]> {
|
||||
getConfigs(): Observable<Config[]> {
|
||||
return this.http
|
||||
.get<GitFiles<any>>(`${this.config.serviceRoot}api/v1/${this.serviceName}/configstore/configs`)
|
||||
.map(result => {
|
||||
@@ -74,13 +76,13 @@ export class ConfigLoaderService {
|
||||
});
|
||||
}
|
||||
|
||||
public getTestSpecificationSchema(): Observable<JSONSchema7> {
|
||||
getTestSpecificationSchema(): Observable<JSONSchema7> {
|
||||
return this.http
|
||||
.get<TestSchemaInfo>(`${this.config.serviceRoot}api/v1/${this.serviceName}/configs/testschema`)
|
||||
.pipe(map(x => x.test_schema));
|
||||
}
|
||||
|
||||
public getSchema(): Observable<JSONSchema7> {
|
||||
getSchema(): Observable<JSONSchema7> {
|
||||
return this.http.get<SchemaInfo>(`${this.config.serviceRoot}api/v1/${this.serviceName}/configs/schema`).map(x => {
|
||||
try {
|
||||
return x.rules_schema;
|
||||
@@ -90,7 +92,7 @@ export class ConfigLoaderService {
|
||||
});
|
||||
}
|
||||
|
||||
public getAdminSchema(): Observable<JSONSchema7> {
|
||||
getAdminSchema(): Observable<JSONSchema7> {
|
||||
return this.http
|
||||
.get<AdminSchemaInfo>(`${this.config.serviceRoot}api/v1/${this.serviceName}/adminconfig/schema`)
|
||||
.map(x => {
|
||||
@@ -102,19 +104,19 @@ export class ConfigLoaderService {
|
||||
});
|
||||
}
|
||||
|
||||
public getPullRequestStatus(): Observable<PullRequestInfo> {
|
||||
getPullRequestStatus(): Observable<PullRequestInfo> {
|
||||
return this.http.get<PullRequestInfo>(
|
||||
`${this.config.serviceRoot}api/v1/${this.serviceName}/configstore/release/status`
|
||||
);
|
||||
}
|
||||
|
||||
public getAdminPullRequestStatus(): Observable<PullRequestInfo> {
|
||||
getAdminPullRequestStatus(): Observable<PullRequestInfo> {
|
||||
return this.http.get<PullRequestInfo>(
|
||||
`${this.config.serviceRoot}api/v1/${this.serviceName}/configstore/adminconfig/status`
|
||||
);
|
||||
}
|
||||
|
||||
public getRelease(): Observable<DeploymentWrapper> {
|
||||
getRelease(): Observable<DeploymentWrapper> {
|
||||
return this.http
|
||||
.get<DeploymentGitFiles<any>>(`${this.config.serviceRoot}api/v1/${this.serviceName}/configstore/release`)
|
||||
.pipe(
|
||||
@@ -160,7 +162,7 @@ export class ConfigLoaderService {
|
||||
);
|
||||
}
|
||||
|
||||
public getAdminConfig(): Observable<AdminConfig> {
|
||||
getAdminConfig(): Observable<AdminConfig> {
|
||||
return this.http
|
||||
.get<AdminConfigGitFiles<any>>(`${this.config.serviceRoot}api/v1/${this.serviceName}/configstore/adminconfig`)
|
||||
.pipe(
|
||||
@@ -185,13 +187,13 @@ export class ConfigLoaderService {
|
||||
);
|
||||
}
|
||||
|
||||
public getTestCases(): Observable<TestCaseMap> {
|
||||
getTestCases(): Observable<TestCaseMap> {
|
||||
return this.http
|
||||
.get<GitFiles<Content<any>>>(`${this.config.serviceRoot}api/v1/${this.serviceName}/configstore/testcases`)
|
||||
.pipe(map(result => this.testCaseFilesToMap(result)));
|
||||
.pipe(map(result => this.testCaseFilesToMap(result.files)));
|
||||
}
|
||||
|
||||
public validateConfig(config: Config): Observable<any> {
|
||||
validateConfig(config: Config): Observable<any> {
|
||||
const json = JSON.stringify(config.configData, null, 2);
|
||||
|
||||
return this.http.post<any>(
|
||||
@@ -200,27 +202,27 @@ export class ConfigLoaderService {
|
||||
);
|
||||
}
|
||||
|
||||
public validateRelease(deployment: Deployment): Observable<any> {
|
||||
validateRelease(deployment: Deployment): Observable<any> {
|
||||
const validationFormat = this.marshalDeploymentFormat(deployment);
|
||||
const json = JSON.stringify(validationFormat, null, 2);
|
||||
|
||||
return this.http.post<any>(`${this.config.serviceRoot}api/v1/${this.serviceName}/configs/validate`, json);
|
||||
}
|
||||
|
||||
public validateAdminConfig(config: AdminConfig): Observable<any> {
|
||||
validateAdminConfig(config: AdminConfig): Observable<any> {
|
||||
const json = JSON.stringify(config.configData, null, 2);
|
||||
|
||||
return this.http.post<any>(`${this.config.serviceRoot}api/v1/${this.serviceName}/adminconfig/validate`, json);
|
||||
}
|
||||
|
||||
public submitRelease(deployment: Deployment): Observable<any> {
|
||||
submitRelease(deployment: Deployment): Observable<any> {
|
||||
const releaseFormat = this.marshalDeploymentFormat(deployment);
|
||||
const json = JSON.stringify(releaseFormat, null, 2);
|
||||
|
||||
return this.http.post<any>(`${this.config.serviceRoot}api/v1/${this.serviceName}/configstore/release`, json);
|
||||
}
|
||||
|
||||
public submitConfig(config: Config): Observable<Config[]> {
|
||||
submitConfig(config: Config): Observable<Config[]> {
|
||||
const fun = config.isNew ? this.submitNewConfig(config) : this.submitConfigEdit(config);
|
||||
return fun.map(result => {
|
||||
if (result.files && result.files.length > 0) {
|
||||
@@ -231,7 +233,7 @@ export class ConfigLoaderService {
|
||||
});
|
||||
}
|
||||
|
||||
public submitConfigEdit(config: Config): Observable<GitFiles<any>> {
|
||||
submitConfigEdit(config: Config): Observable<GitFiles<any>> {
|
||||
const json = JSON.stringify(config.configData, null, 2);
|
||||
|
||||
return this.http.put<GitFiles<any>>(
|
||||
@@ -240,7 +242,7 @@ export class ConfigLoaderService {
|
||||
);
|
||||
}
|
||||
|
||||
public submitNewConfig(config: Config): Observable<GitFiles<any>> {
|
||||
submitNewConfig(config: Config): Observable<GitFiles<any>> {
|
||||
const json = JSON.stringify(config.configData, null, 2);
|
||||
|
||||
return this.http.post<GitFiles<any>>(
|
||||
@@ -249,7 +251,7 @@ export class ConfigLoaderService {
|
||||
);
|
||||
}
|
||||
|
||||
public submitAdminConfig(config: AdminConfig): Observable<GitFiles<any>> {
|
||||
submitAdminConfig(config: AdminConfig): Observable<GitFiles<any>> {
|
||||
const json = JSON.stringify(config.configData, null, 2);
|
||||
|
||||
return this.http.post<GitFiles<any>>(
|
||||
@@ -258,7 +260,7 @@ export class ConfigLoaderService {
|
||||
);
|
||||
}
|
||||
|
||||
public testDeploymentConfig(deployment: Deployment, testSpecification: any): Observable<ConfigTestResult> {
|
||||
testDeploymentConfig(deployment: Deployment, testSpecification: any): Observable<ConfigTestResult> {
|
||||
const testDto: ConfigTestDto = {
|
||||
files: [
|
||||
{
|
||||
@@ -276,7 +278,7 @@ export class ConfigLoaderService {
|
||||
);
|
||||
}
|
||||
|
||||
public testSingleConfig(configData: any, testSpecification: any): Observable<ConfigTestResult> {
|
||||
testSingleConfig(configData: any, testSpecification: any): Observable<ConfigTestResult> {
|
||||
const testDto: ConfigTestDto = {
|
||||
files: [
|
||||
{
|
||||
@@ -292,27 +294,27 @@ export class ConfigLoaderService {
|
||||
);
|
||||
}
|
||||
|
||||
public submitTestCase(testCase: TestCaseWrapper): Observable<TestCaseMap> {
|
||||
submitTestCase(testCase: TestCaseWrapper): Observable<TestCaseMap> {
|
||||
return isNewTestCase(testCase) ? this.submitNewTestCase(testCase) : this.submitTestCaseEdit(testCase);
|
||||
}
|
||||
|
||||
public submitTestCaseEdit(testCase: TestCaseWrapper): Observable<TestCaseMap> {
|
||||
submitTestCaseEdit(testCase: TestCaseWrapper): Observable<TestCaseMap> {
|
||||
const json = JSON.stringify(cloneDeep(testCase.testCase), null, 2);
|
||||
|
||||
return this.http
|
||||
.put<GitFiles<TestCase>>(`${this.config.serviceRoot}api/v1/${this.serviceName}/configstore/testcases`, json)
|
||||
.pipe(map(result => this.testCaseFilesToMap(result)));
|
||||
.pipe(map(result => this.testCaseFilesToMap(result.files)));
|
||||
}
|
||||
|
||||
public submitNewTestCase(testCase: TestCaseWrapper): Observable<TestCaseMap> {
|
||||
submitNewTestCase(testCase: TestCaseWrapper): Observable<TestCaseMap> {
|
||||
const json = JSON.stringify(cloneDeep(testCase.testCase), null, 2);
|
||||
|
||||
return this.http
|
||||
.post<GitFiles<TestCase>>(`${this.config.serviceRoot}api/v1/${this.serviceName}/configstore/testcases`, json)
|
||||
.pipe(map(result => this.testCaseFilesToMap(result)));
|
||||
.pipe(map(result => this.testCaseFilesToMap(result.files)));
|
||||
}
|
||||
|
||||
public validateTestCase(testcase: TestCase): Observable<any> {
|
||||
validateTestCase(testcase: TestCase): Observable<any> {
|
||||
const outObj = {
|
||||
files: [
|
||||
{
|
||||
@@ -325,7 +327,7 @@ export class ConfigLoaderService {
|
||||
return this.http.post<any>(`${this.config.serviceRoot}api/v1/testcases/validate`, json);
|
||||
}
|
||||
|
||||
public evaluateTestCase(configData: any, testCaseWrapper: TestCaseWrapper): Observable<TestCaseResult> {
|
||||
evaluateTestCase(configData: any, testCaseWrapper: TestCaseWrapper): Observable<TestCaseResult> {
|
||||
const ret = {} as TestCaseResult;
|
||||
|
||||
return this.testSingleConfig(configData, testCaseWrapper.testCase.test_specification)
|
||||
@@ -345,7 +347,7 @@ export class ConfigLoaderService {
|
||||
);
|
||||
}
|
||||
|
||||
public evaluateTestCaseFromResult(testcase: TestCase, testResult: any): Observable<TestCaseEvaluationResult> {
|
||||
evaluateTestCaseFromResult(testcase: TestCase, testResult: any): Observable<TestCaseEvaluationResult> {
|
||||
const outObj: TestCaseEvaluation = {
|
||||
files: [
|
||||
{
|
||||
@@ -362,10 +364,40 @@ export class ConfigLoaderService {
|
||||
.pipe(map(x => x.test_case_result));
|
||||
}
|
||||
|
||||
private testCaseFilesToMap(result: GitFiles<any>): TestCaseMap {
|
||||
deleteConfig(configName: string): Observable<ConfigAndTestCases> {
|
||||
return this.http
|
||||
.post<GitFilesDelete<any>>(
|
||||
`${this.config.serviceRoot}api/v1/${this.serviceName}/configstore/configs/delete?configName=${configName}`,
|
||||
null
|
||||
)
|
||||
.map(result => {
|
||||
if (!result.configs_files || (!result.test_cases_files && this.uiMetadata.testing.testCaseEnabled)) {
|
||||
throw new DOMException('bad format response when deleting config');
|
||||
}
|
||||
let configAndTestCases = {
|
||||
configs: result.configs_files.map(file => this.getConfigFromFile(file)),
|
||||
testCases: {},
|
||||
};
|
||||
if (result.test_cases_files) {
|
||||
configAndTestCases['testCases'] = this.testCaseFilesToMap(result.test_cases_files);
|
||||
}
|
||||
return configAndTestCases;
|
||||
});
|
||||
}
|
||||
|
||||
deleteTestCase(configName: string, testCaseName: string): Observable<TestCaseMap> {
|
||||
return this.http
|
||||
.post<GitFiles<Content<any>>>(
|
||||
`${this.config.serviceRoot}api/v1/${this.serviceName}/configstore/testcases/delete?configName=${configName}&testCaseName=${testCaseName}`,
|
||||
null
|
||||
)
|
||||
.pipe(map(result => this.testCaseFilesToMap(result.files)));
|
||||
}
|
||||
|
||||
private testCaseFilesToMap(files: any[]): TestCaseMap {
|
||||
const testCaseMap: TestCaseMap = {};
|
||||
if (result.files && result.files.length > 0) {
|
||||
result.files.forEach(file => {
|
||||
if (files && files.length > 0) {
|
||||
files.forEach(file => {
|
||||
if (!testCaseMap.hasOwnProperty(file.content.config_name)) {
|
||||
testCaseMap[file.content.config_name] = [];
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ export class ConfigStoreService {
|
||||
private metaDataMap: UiMetadata;
|
||||
private testStoreService: TestStoreService;
|
||||
|
||||
public get testService(): TestStoreService {
|
||||
get testService(): TestStoreService {
|
||||
return this.testStoreService;
|
||||
}
|
||||
|
||||
@@ -369,6 +369,34 @@ export class ConfigStoreService {
|
||||
this.store.next(newState);
|
||||
}
|
||||
|
||||
deleteConfig(configName: string): Observable<any> {
|
||||
return this.configLoaderService.deleteConfig(configName).map(data => {
|
||||
const newState = new ConfigStoreStateBuilder(this.store.getValue())
|
||||
.testCaseMap(data.testCases)
|
||||
.configs(data.configs)
|
||||
.updateTestCasesInConfigs()
|
||||
.detectOutdatedConfigs()
|
||||
.reorderConfigsByDeployment()
|
||||
.computeFiltered(this.user)
|
||||
.build();
|
||||
|
||||
this.store.next(newState);
|
||||
});
|
||||
}
|
||||
|
||||
deleteTestCase(configName: string, testCaseName: string): Observable<any> {
|
||||
return this.configLoaderService.deleteTestCase(configName, testCaseName).map(testCaseMap => {
|
||||
const newState = new ConfigStoreStateBuilder(this.store.getValue())
|
||||
.testCaseMap(testCaseMap)
|
||||
.updateTestCasesInConfigs()
|
||||
.editedConfigByName(configName)
|
||||
.computeFiltered(this.user)
|
||||
.build();
|
||||
|
||||
this.store.next(newState);
|
||||
});
|
||||
}
|
||||
|
||||
private updateReleaseSubmitInFlight(releaseSubmitInFlight: boolean) {
|
||||
const newState = new ConfigStoreStateBuilder(this.store.getValue())
|
||||
.releaseSubmitInFlight(releaseSubmitInFlight)
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"serviceRoot": "https://config-editor/",
|
||||
"uiBootstrapPath": "./ui-bootstrap.json",
|
||||
"authType": "disabled",
|
||||
"historyMaxSize": 5,
|
||||
"blockingTimeout": "30000",
|
||||
"homeLinks": [
|
||||
{
|
||||
"icon": "library_books",
|
||||
|
||||
@@ -11,7 +11,8 @@ On the home page all services are listed alphabetically by name on the left side
|
||||
</p>
|
||||
|
||||
### Recently visited
|
||||
Your recently visited pages are saved in your browser and can be accessed with only one click from the home page.
|
||||
Your recently visited pages are saved in your browser and can be accessed with only one click from the home page. The default number of pages shown is 5 but can be configured in the `ui-config.json` file using the "historyMaxSize" key.
|
||||
|
||||
### Explore Siembol
|
||||
The 'Explore Siembol' section of the home page is for quick access to useful resources such as documentation, ticket tracking systems etc... By default there is a link to the documentation and to the issues page on the git repo. This can be customised from the `ui-config.json` config file.
|
||||
|
||||
@@ -23,6 +24,8 @@ Below is the default config file provided. The two default links are in "homeLin
|
||||
"serviceRoot": "https://config-editor/",
|
||||
"uiBootstrapPath": "./ui-bootstrap.json",
|
||||
"authType": "disabled",
|
||||
"historyMaxSize": 5,
|
||||
"blockingTimeout": 30000,
|
||||
"homeLinks": [
|
||||
{
|
||||
"icon": "library_books",
|
||||
@@ -36,6 +39,9 @@ Below is the default config file provided. The two default links are in "homeLin
|
||||
]
|
||||
}
|
||||
|
||||
## Blocking timeout
|
||||
The blocking timeout is a property that can be configured in `ui-config.json` (as shown in the default config above), it can be omitted in `ui-config.json`, the default value is 30 seconds. This value is used for certain operations that require blocking the entire UI (eg. deleting a config), the UI will be blocked for this maximum amount of time after which an error will occur if the operation hasn't yet finished.
|
||||
|
||||
## Service configurations
|
||||
After selecting a service to edit you will be redirected to the service configuration page. An example is shown below.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user