Commit f8fb4929 authored by Vitali Stupin's avatar Vitali Stupin
Browse files

Adding support for REST services

parent ce2301e3
export class Service {
status: string;
serviceCode: string;
openapi: string;
fullServiceName: string;
}
<div class="card">
<div class="card-header pointerCursor" (click)="showDetail()">
{{subsystem.fullSubsystemName}}
<span class="badge badge-secondary" *ngIf="!subsystem.methods.length && subsystem.subsystemStatus == 'OK'">{{'subsystem.statusEmpty' | translate}}</span>
<span class="badge badge-secondary" *ngIf="!subsystem.methods.length && !subsystem.services.length && subsystem.subsystemStatus == 'OK'">{{'subsystem.statusEmpty' | translate}}</span>
<span class="badge badge-danger" *ngIf="subsystem.subsystemStatus == 'ERROR'">{{'subsystem.statusError' | translate}}</span>
</div>
<div class="card-body" *ngIf="subsystem.subsystemStatus == 'ERROR'">
<p>{{'subsystem.statusErrorInfo' | translate}}</p>
</div>
<div class="card-body" *ngIf="subsystem.subsystemStatus == 'OK' && !subsystem.methods.length">
<div class="card-body" *ngIf="subsystem.subsystemStatus == 'OK' && !subsystem.methods.length && !subsystem.services.length">
<p>{{'subsystem.statusEmptyInfo' | translate}}</p>
</div>
<div class="card-body" *ngIf="subsystem.subsystemStatus == 'OK' && subsystem.methods.length">
<div class="card-body" *ngIf="subsystem.subsystemStatus == 'OK' && (subsystem.methods.length || subsystem.services.length)">
<h6 *ngIf="subsystem.methods.length" class="card-subtitle text-muted">SOAP</h6>
<p *ngFor="let method of getMethodsPreview()">
{{method.fullMethodName}}
<a href="{{getApiUrlBase()}}{{method.wsdl}}" class="badge badge-success"
*ngIf="method.wsdl" [target]="'_blank'">WSDL</a>
<span class="badge badge-info" *ngIf="method.methodStatus == 'REST'">REST</span>
<span class="badge badge-danger" *ngIf="method.methodStatus == 'ERROR'">{{'subsystem.statusWsdlError' | translate}}</span>
<span class="badge badge-danger" *ngIf="method.methodStatus == 'TIMEOUT'">{{'subsystem.statusWsdlTimeout' | translate}}</span>
<span class="badge badge-warning" *ngIf="method.methodStatus == 'SKIPPED'">{{'subsystem.statusWsdlSkipped' | translate}}</span>
</p>
<p *ngIf="getNotInPreview() > 0" class="pointerCursor" (click)="showDetail()">
{{'subsystem.moreMethods' | translate:{"count": getNotInPreview()} }}
</p>
<p *ngIf="getMethodsNotInPreview() > 0" class="pointerCursor" (click)="showDetail()">
{{'subsystem.moreMethods' | translate:{"count": getMethodsNotInPreview()} }}
</p>
<h6 *ngIf="subsystem.services.length" class="card-subtitle text-muted">REST</h6>
<p *ngFor="let service of getServicesPreview()">
{{service.fullServiceName}}
<a href="{{getApiUrlBase()}}{{service.openapi}}" class="badge badge-success"
*ngIf="service.openapi" [target]="'_blank'">OpenAPI</a>
<span class="badge badge-danger" *ngIf="service.status == 'ERROR'">{{'subsystem.statusOpenapiError' | translate}}</span>
<span class="badge badge-danger" *ngIf="service.status == 'TIMEOUT'">{{'subsystem.statusOpenapiTimeout' | translate}}</span>
<span class="badge badge-warning" *ngIf="service.status == 'SKIPPED'">{{'subsystem.statusOpenapiSkipped' | translate}}</span>
</p>
<p *ngIf="getServicesNotInPreview() > 0" class="pointerCursor" (click)="showDetail()">
{{'subsystem.moreMethods' | translate:{"count": getServicesNotInPreview()} }}
</p>
</div>
</div>
......@@ -8,6 +8,7 @@ import { Router } from '@angular/router';
import { Method } from 'src/app/method';
import { AppConfigMock } from 'src/app/app.config-mock';
import { AppConfig } from 'src/app/app.config';
import { Service } from 'src/app/service';
const PREVIEW_SIZE = 5;
......@@ -48,8 +49,10 @@ describe('SubsystemItemComponent', () => {
memberCode: 'CODE',
subsystemCode: 'SUB',
subsystemStatus: 'OK',
servicesStatus: 'OK',
fullSubsystemName: 'INST/CLASS/CODE/SUB',
methods: []
methods: [],
services: []
};
fixture.detectChanges();
});
......@@ -71,12 +74,28 @@ describe('SubsystemItemComponent', () => {
expect(component.getMethodsPreview().length).toBe(PREVIEW_SIZE);
});
it('should preview services', () => {
expect(component.getServicesPreview().length).toBe(0);
for (let i = 0; i < PREVIEW_SIZE + 10; i++) {
component.subsystem.services.push(new Service());
}
expect(component.getServicesPreview().length).toBe(PREVIEW_SIZE);
});
it('should calculate methods not in preview', () => {
expect(component.getNotInPreview()).toBe(0);
expect(component.getMethodsNotInPreview()).toBe(0);
for (let i = 0; i < PREVIEW_SIZE + 10; i++) {
component.subsystem.methods.push(new Method());
}
expect(component.getNotInPreview()).toBe(10);
expect(component.getMethodsNotInPreview()).toBe(10);
});
it('should calculate services not in preview', () => {
expect(component.getServicesNotInPreview()).toBe(0);
for (let i = 0; i < PREVIEW_SIZE + 10; i++) {
component.subsystem.services.push(new Service());
}
expect(component.getServicesNotInPreview()).toBe(10);
});
it('should go to detail view', () => {
......
import { Component, OnInit, Input } from '@angular/core';
import { Subsystem } from '../../subsystem';
import { Method } from '../../method';
import { Service } from '../../service';
import { SubsystemsService } from '../../subsystems.service';
import { Router } from '@angular/router';
import { AppConfig } from '../../app.config';
......@@ -27,13 +28,24 @@ export class SubsystemItemComponent implements OnInit {
return this.subsystem.methods.length ? this.subsystem.methods.slice(0, this.config.getConfig('PREVIEW_SIZE')) : [];
}
getNotInPreview(): number {
getServicesPreview(): Service[] {
return this.subsystem.services.length ? this.subsystem.services.slice(0, this.config.getConfig('PREVIEW_SIZE')) : [];
}
getMethodsNotInPreview(): number {
if (this.subsystem.methods.length - this.config.getConfig('PREVIEW_SIZE') < 0) {
return 0;
}
return this.subsystem.methods.length - this.config.getConfig('PREVIEW_SIZE');
}
getServicesNotInPreview(): number {
if (this.subsystem.services.length - this.config.getConfig('PREVIEW_SIZE') < 0) {
return 0;
}
return this.subsystem.services.length - this.config.getConfig('PREVIEW_SIZE');
}
showDetail() {
this.router.navigateByUrl(
'/' + this.subsystem.xRoadInstance
......
import { Method } from './method';
import { Service } from './service';
export class Subsystem {
memberClass: string;
subsystemCode: string;
xRoadInstance: string;
subsystemStatus: string;
servicesStatus: string;
memberCode: string;
fullSubsystemName: string;
methods: Method[];
services: Service[];
}
......@@ -11,30 +11,39 @@
<div class="card">
<div class="card-header">
{{subsystem.fullSubsystemName}}
<span class="badge badge-secondary" *ngIf="!subsystem.methods.length && subsystem.subsystemStatus == 'OK'">{{'subsystem.statusEmpty' | translate}}</span>
<span class="badge badge-secondary" *ngIf="!subsystem.methods.length && !subsystem.services.length && subsystem.subsystemStatus == 'OK'">{{'subsystem.statusEmpty' | translate}}</span>
<span class="badge badge-danger" *ngIf="subsystem.subsystemStatus == 'ERROR'">{{'subsystem.statusError' | translate}}</span>
</div>
<div class="card-body" *ngIf="subsystem.subsystemStatus == 'ERROR'">
<p>{{'subsystem.statusErrorInfo' | translate}}</p>
</div>
<div class="card-body" *ngIf="subsystem.subsystemStatus == 'OK' && !subsystem.methods.length">
<div class="card-body" *ngIf="subsystem.subsystemStatus == 'OK' && !subsystem.methods.length && !subsystem.services.length">
<p>{{'subsystem.statusEmptyInfo' | translate}}</p>
</div>
<div class="card-body" *ngIf="subsystem.subsystemStatus == 'OK' && subsystem.methods.length">
<div class="card-body" *ngIf="subsystem.subsystemStatus == 'OK' && (subsystem.methods.length || subsystem.services.length)">
<h6 *ngIf="subsystem.methods.length" class="card-subtitle text-muted">SOAP</h6>
<p *ngFor="let method of subsystem.methods">
{{method.fullMethodName}}
<a href="{{getApiUrlBase()}}{{method.wsdl}}" class="badge badge-success"
*ngIf="method.wsdl" [target]="'_blank'">WSDL</a>
<span class="badge badge-info" *ngIf="method.methodStatus == 'REST'">REST</span>
<span class="badge badge-danger" *ngIf="method.methodStatus == 'ERROR'">{{'subsystem.statusWsdlError' | translate}}</span>
<span class="badge badge-danger" *ngIf="method.methodStatus == 'TIMEOUT'">{{'subsystem.statusWsdlTimeout' | translate}}</span>
<span class="badge badge-warning" *ngIf="method.methodStatus == 'SKIPPED'">{{'subsystem.statusWsdlSkipped' | translate}}</span>
</p>
</p>
<h6 *ngIf="subsystem.services.length" class="card-subtitle text-muted">REST</h6>
<p *ngFor="let service of subsystem.services">
{{service.fullServiceName}}
<a href="{{getApiUrlBase()}}{{service.openapi}}" class="badge badge-success"
*ngIf="service.openapi" [target]="'_blank'">OpenAPI</a>
<span class="badge badge-danger" *ngIf="service.status == 'ERROR'">{{'subsystem.statusOpenapiError' | translate}}</span>
<span class="badge badge-danger" *ngIf="service.status == 'TIMEOUT'">{{'subsystem.statusOpenapiTimeout' | translate}}</span>
<span class="badge badge-warning" *ngIf="service.status == 'SKIPPED'">{{'subsystem.statusOpenapiSkipped' | translate}}</span>
</p>
</div>
</div>
</div>
<br>
<p *ngIf="subsystemSubject.value?.methods?.length">
<p *ngIf="subsystemSubject.value?.methods?.length || subsystemSubject.value?.services?.length">
<button type="button" [ngClass]="'btn btn-secondary'" (click)="scrollToTop()">{{'scrollToTop' | translate}}</button>
</p>
......@@ -68,9 +68,11 @@ describe('SubsystemComponent', () => {
subsystemCode: '',
xRoadInstance: '',
subsystemStatus: '',
servicesStatus: '',
memberCode: '',
fullSubsystemName: 'INST/CLASS/MEMBER/SYSTEM',
methods: []
methods: [],
services: []
}
]);
});
......@@ -114,9 +116,11 @@ describe('SubsystemComponent', () => {
subsystemCode: '',
xRoadInstance: '',
subsystemStatus: '',
servicesStatus: '',
memberCode: '',
fullSubsystemName: 'INST/CLASS/MEMBER2/SYSTEM',
methods: []
methods: [],
services: []
}
]);
fixture = TestBed.createComponent(SubsystemComponent);
......@@ -223,9 +227,11 @@ describe('SubsystemComponent (with instance version)', () => {
subsystemCode: '',
xRoadInstance: '',
subsystemStatus: '',
servicesStatus: '',
memberCode: '',
fullSubsystemName: 'INST/CLASS/MEMBER/SYSTEM',
methods: []
methods: [],
services: []
}
]);
});
......
......@@ -2,6 +2,7 @@ import { SubsystemsService } from './subsystems.service';
import { of, defer } from 'rxjs';
import { Subsystem } from './subsystem';
import { Method } from './method';
import { Service } from './service';
import { HttpErrorResponse } from '@angular/common/http';
import { tick, fakeAsync } from '@angular/core/testing';
import { AppConfigMock } from './app.config-mock';
......@@ -46,6 +47,22 @@ describe('SubsystemsService', () => {
subsystemStatus: 'OK',
memberCode: 'MEMBER2',
methods: []
},
{
memberClass: 'CLASS',
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'OK',
memberCode: 'MEMBER3',
methods: [],
services: [
{
status: 'OK',
serviceCode: 'SERVICE',
openapi: 'URL'
}
]
}
];
const expectedSubsystems = [
......@@ -54,6 +71,7 @@ describe('SubsystemsService', () => {
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'ERROR',
memberCode: 'MEMBER',
fullSubsystemName: 'INST/CLASS/MEMBER/SYSTEM',
methods: [
......@@ -64,16 +82,37 @@ describe('SubsystemsService', () => {
serviceVersion: 'VER',
fullMethodName: 'INST/CLASS/MEMBER/SYSTEM/SERVICE/VER'
}
]
],
services: []
},
{
memberClass: 'CLASS',
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'ERROR',
memberCode: 'MEMBER2',
fullSubsystemName: 'INST/CLASS/MEMBER2/SYSTEM',
methods: []
methods: [],
services: []
},
{
memberClass: 'CLASS',
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'OK',
memberCode: 'MEMBER3',
fullSubsystemName: 'INST/CLASS/MEMBER3/SYSTEM',
methods: [],
services: [
{
status: 'OK',
serviceCode: 'SERVICE',
openapi: 'URL',
fullServiceName: 'INST/CLASS/MEMBER3/SYSTEM/SERVICE'
}
]
}
];
httpClientSpy.get.and.returnValue(of(sourceSubsystems));
......@@ -184,19 +223,34 @@ describe('SubsystemsService', () => {
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'ERROR',
memberCode: 'MEMBER',
fullSubsystemName: 'INST/CLASS/MEMBER/SYSTEM',
methods: [{} as Method]
methods: [{} as Method],
services: []
},
{
memberClass: 'CLASS',
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'ERROR',
memberCode: 'MEMBER2',
fullSubsystemName: 'INST/CLASS/MEMBER2/SYSTEM',
methods: []
}
methods: [],
services: []
},
{
memberClass: 'CLASS',
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'OK',
memberCode: 'MEMBER3',
fullSubsystemName: 'INST/CLASS/MEMBER3/SYSTEM',
methods: [],
services: [{} as Service]
},
];
const expectedSubsystems = [
{
......@@ -204,9 +258,22 @@ describe('SubsystemsService', () => {
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'ERROR',
memberCode: 'MEMBER',
fullSubsystemName: 'INST/CLASS/MEMBER/SYSTEM',
methods: [{} as Method]
methods: [{} as Method],
services: []
},
{
memberClass: 'CLASS',
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'OK',
memberCode: 'MEMBER3',
fullSubsystemName: 'INST/CLASS/MEMBER3/SYSTEM',
methods: [],
services: [{} as Service]
}
];
service.subsystemsSubject.next(sourceSubsystems);
......@@ -221,6 +288,7 @@ describe('SubsystemsService', () => {
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'ERROR',
memberCode: 'MEMBER',
fullSubsystemName: 'INST/CLASS/MEMBER/SYSTEM',
methods: [
......@@ -238,16 +306,43 @@ describe('SubsystemsService', () => {
serviceVersion: 'VER',
fullMethodName: 'INST/CLASS/MEMBER/SYSTEM/SERVICE2/VER'
}
]
],
services: []
},
{
memberClass: 'CLASS',
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'ERROR',
memberCode: 'MEMBER2',
fullSubsystemName: 'INST/CLASS/MEMBER2/SYSTEM',
methods: []
methods: [],
services: []
},
{
memberClass: 'CLASS',
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'OK',
memberCode: 'MEMBER3',
fullSubsystemName: 'INST/CLASS/MEMBER3/SYSTEM',
methods: [],
services: [
{
status: 'OK',
serviceCode: 'SERVICE',
openapi: 'URL',
fullServiceName: 'INST/CLASS/MEMBER3/SYSTEM/RESTSRV'
},
{
status: 'OK',
serviceCode: 'SERVICE2',
openapi: 'URL',
fullServiceName: 'INST/CLASS/MEMBER3/SYSTEM/RESTSRV2'
}
]
}
];
const expectedSubsystems1 = [
......@@ -256,9 +351,11 @@ describe('SubsystemsService', () => {
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'ERROR',
memberCode: 'MEMBER2',
fullSubsystemName: 'INST/CLASS/MEMBER2/SYSTEM',
methods: []
methods: [],
services: []
}
];
const expectedSubsystems2 = [
......@@ -267,6 +364,7 @@ describe('SubsystemsService', () => {
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'ERROR',
memberCode: 'MEMBER',
fullSubsystemName: 'INST/CLASS/MEMBER/SYSTEM',
methods: [
......@@ -277,6 +375,27 @@ describe('SubsystemsService', () => {
serviceVersion: 'VER',
fullMethodName: 'INST/CLASS/MEMBER/SYSTEM/SERVICE2/VER'
}
],
services: []
}
];
const expectedSubsystems3 = [
{
memberClass: 'CLASS',
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'OK',
memberCode: 'MEMBER3',
fullSubsystemName: 'INST/CLASS/MEMBER3/SYSTEM',
methods: [],
services: [
{
status: 'OK',
serviceCode: 'SERVICE2',
openapi: 'URL',
fullServiceName: 'INST/CLASS/MEMBER3/SYSTEM/RESTSRV2'
}
]
}
];
......@@ -294,6 +413,12 @@ describe('SubsystemsService', () => {
tick(config.getConfig('FILTER_DEBOUNCE'));
expect(service.filteredSubsystemsSubject.value).toEqual(expectedSubsystems2);
// Search member with multiple services
service.setFilter('RESTSRV2');
// Waiting for a debounce time to apply filter
tick(config.getConfig('FILTER_DEBOUNCE'));
expect(service.filteredSubsystemsSubject.value).toEqual(expectedSubsystems3);
// Search with limit
const sourceSubsystems2 = [];
for (let i = 0; i < config.getConfig('DEFAULT_LIMIT') + 1; i++) {
......@@ -303,9 +428,11 @@ describe('SubsystemsService', () => {
subsystemCode: 'SYSTEM',
xRoadInstance: 'INST',
subsystemStatus: 'OK',
servicesStatus: 'OK',
memberCode: 'MEMBER' + i,
fullSubsystemName: 'INST/CLASS/MEMBER' + i + '/SYSTEM',
methods: []
methods: [],
services: []
}
);
}
......
......@@ -4,6 +4,7 @@ import { of, BehaviorSubject, Subject } from 'rxjs';
import { catchError, debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { Subsystem } from './subsystem';
import { Method } from './method';
import { Service } from './service';
import { AppConfig } from './app.config';
import { InstanceVersion } from './instance-version';
......@@ -41,35 +42,43 @@ export class SubsystemsService {
const filtered: Subsystem[] = [];
let limit: number = this.limit;
for (let subsystem of this.subsystemsSubject.value) {
if (this.nonEmpty && !subsystem.methods.length) {
if (this.nonEmpty && !subsystem.methods.length && !subsystem.services.length) {
// Filtering out empty subsystems
continue;
}
if (
this.filter !== ''
&& !subsystem.methods.length
&& !subsystem.services.length
) {
// Subsystem without methods
// Subsystem without methods and services
if (subsystem.fullSubsystemName.toLowerCase().indexOf(this.filter.toLowerCase()) < 0) {
// Subsystem name does not match the filter
continue;
}
} else if (this.filter !== '') {
// Subsystem with methods
// Subsystem with methods and/or services
const filteredMethods: Method[] = [];
for (const method of subsystem.methods) {
if (method.fullMethodName.toLowerCase().indexOf(this.filter.toLowerCase()) >= 0) {
filteredMethods.push(method);
}
}
if (!filteredMethods.length) {
// No matching method names found
const filteredServices: Service[] = [];
for (const service of subsystem.services) {
if (service.fullServiceName.toLowerCase().indexOf(this.filter.toLowerCase()) >= 0) {
filteredServices.push(service);
}
}
if (!filteredMethods.length && !filteredServices.length) {
// No matching method and/or services names found
continue;
}
// Copy object to avoid overwriting methods array in subsystem object
subsystem = Object.assign(Object.create(subsystem), subsystem);
// Leaving only matcing methods
// Leaving only matcing methods and services
subsystem.methods = filteredMethods;
subsystem.services = filteredServices;
}
filtered.push(subsystem);
limit -= 1;
......@@ -91,6 +100,18 @@ export class SubsystemsService {
+ '/' + method.serviceCode
+ '/' + method.serviceVersion;
}
if (!subsystem.servicesStatus) {
// Fix missing data in previous versions
subsystem.servicesStatus = 'ERROR';
}
if (!subsystem.services) {
// Fix missing data in previous versions
subsystem.services = [];
}
for (const service of subsystem.services) {
service.fullServiceName = subsystem.fullSubsystemName
+ '/' + service.serviceCode;
}
}
return subsystems;
}
......
......@@ -8,8 +8,8 @@
},
"INSTANCES": {
"EE": "https://www.x-tee.ee/catalogue-data/EE/",
"ee-test": "https://www.x-tee.ee/catalogue-data/EE/",
"ee-dev": "https://www.x-tee.ee/catalogue-data/EE/"
"ee-test": "https://www.x-tee.ee/catalogue-data/ee-test/",
"ee-dev": "https://www.x-tee.ee/catalogue-data/ee-dev/"
},
"API_SERVICE": "index.json",
"API_HISTORY": "history.json",
......
......@@ -37,6 +37,9 @@
"statusWsdlError": "Error while downloading or parsing of WSDL",
"statusWsdlTimeout": "WSDL query timed out",