[Harvester | master]: First version of the user interface - harvest page added, to get configuration template instances, then services, information and compatibility filter are displayed and harvest button triggers API call to start the harvest process.

This commit is contained in:
Konstantina Galouni 2023-10-17 13:36:15 +03:00
parent 90125442bd
commit 9f7aca0f10
12 changed files with 403 additions and 23 deletions

View File

@ -15,6 +15,7 @@ import {SharedModule} from "./openaireLibrary/shared/shared.module";
import {ErrorInterceptorService} from "./openaireLibrary/error-interceptor.service";
import {DEFAULT_TIMEOUT, TimeoutInterceptor} from "./openaireLibrary/timeout-interceptor.service";
import { HomeModule } from './home/home.module';
import {HarvesterModule} from "./harvester/harvester.module";
@NgModule({
imports: [
@ -26,10 +27,10 @@ import { HomeModule } from './home/home.module';
ErrorModule,
SharedModule,
BrowserAnimationsModule, LoadingModule, SideBarModule,
HomeModule
HomeModule, HarvesterModule
],
declarations: [
AppComponent,
AppComponent
],
providers: [
TitleCasePipe,

View File

@ -3,24 +3,29 @@ import {RouterModule, Routes} from '@angular/router';
import {LoginGuard} from "./openaireLibrary/login/loginGuard.guard";
const routes: Routes = [
{
path: '',
loadChildren: () => import('./home/home.module').then(m => m.HomeModule)
},
{
path: 'reload',
loadChildren: () => import('./reload/libReload.module').then(m => m.LibReloadModule),
data: {hasSidebar: false, hasHeader: false}
},
// { path: 'error',
// pathMatch: 'full',
// component: AdminErrorPageComponent,
// data: {hasSidebar: false}
// },
// { path: '**',
// pathMatch: 'full',
// component: AdminErrorPageComponent
// }
{
path: '',
loadChildren: () => import('./harvester/harvester.module').then(m => m.HarvesterModule)
// loadChildren: () => import('./home/home.module').then(m => m.HomeModule)
},
{
path: 'harvest',
redirectTo: '', pathMatch: 'full'
},
{
path: 'reload',
loadChildren: () => import('./reload/libReload.module').then(m => m.LibReloadModule),
data: {hasSidebar: false, hasHeader: false}
},
// { path: 'error',
// pathMatch: 'full',
// component: AdminErrorPageComponent,
// data: {hasSidebar: false}
// },
// { path: '**',
// pathMatch: 'full',
// component: AdminErrorPageComponent
// }
];

View File

@ -0,0 +1,10 @@
export class EOSCService {
// id: string;
name: string;
description: string;
webpage: string;
// configuration template instance info
baseurl: string;
compatibility: string;
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import{HarvesterComponent} from './harvester.component';
import {PreviousRouteRecorder} from '../openaireLibrary/utils/piwik/previousRouteRecorder.guard';
@NgModule({
imports: [
RouterModule.forChild([
{ path: '', component: HarvesterComponent, canDeactivate: [PreviousRouteRecorder] }
])
]
})
export class HarvesterRoutingModule { }

View File

@ -0,0 +1,109 @@
<div class="uk-section">
<div class="uk-container uk-container-xlarge">
<h1 class="uk-margin-large-bottom">EOSC Metadata Harvester</h1>
<div class="uk-grid">
<div class="uk-width-1-4@m search-filters">
<div [class.filterLoading]="false">
<div class="uk-flex uk-flex-middle">
<h4 class="uk-margin-right uk-margin-remove-bottom">Filters</h4>
<!-- <a *ngIf="(selectedRangeFilters + selectedFilters + selectedTypesNum)>0"-->
<!-- (click)="clearFilters()" class="uk-text-small"-->
<!-- [class.uk-disabled]="disabled" [class.uk-link-muted]="disabled">-->
<!-- Clear All-->
<!-- </a>-->
</div>
<!-- <div *ngIf="searchUtils.refineStatus == errorCodes.LOADING && existingFiltersWithValues === 0"-->
<!-- class="uk-margin-top" role="alert">-->
<!-- <loading></loading>-->
<!-- </div>-->
<div>
<div class="uk-flex uk-flex-middle uk-margin-bottom">
<h6 title="compatibility" class="uk-margin-top uk-margin-remove-bottom">{{_formatTitle("Compatibility", compatibilityFilterValues.length)}}</h6>
<!-- <a *ngIf="filter.countSelectedValues>0" class="uk-text-small uk-margin-left" (click)="clearFilter()" [class.uk-disabled]="isDisabled">Clear</a>-->
</div>
<div>
<div *ngFor="let compatibilityFilter of compatibilityFilterValues"
class="uk-animation-fade uk-text-small uk-margin-small-bottom">
<div [title]="compatibilityFilter.compatibility">
<span class="uk-flex uk-flex-middle">
<!-- [class.uk-disabled]="isDisabled || (showResultCount && value.number === 0)">-->
<label>
<input type="checkbox" class="uk-checkbox"
[(ngModel)]="compatibilityFilter.selected"
(ngModelChange)="filterChange(compatibilityFilter.selected)"/>
<span class="uk-margin-small-left">
{{_formatName(compatibilityFilter.compatibility)}}
</span>
</label>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="uk-width-expand">
<results-and-pages
type="Services"
[page]="page" [pageSize]="pageSize"
[totalResults]="services.length">
</results-and-pages>
<ul *ngIf="services && services.length > 0" class="uk-list uk-list-large uk-list-divider">
<li *ngFor="let service of services.slice((page-1)*pageSize, page*pageSize)" class="uk-card uk-card-default">
<div class="uk-card-body uk-text-small">
<!-- {{service | json}}-->
<h1 *ngIf="service.name" class="uk-h6">{{service.name}}</h1>
<div *ngIf="service.webpage">
<span class="uk-text-meta">Web page: </span>
<a target="_blank" [href]="service.webpage" class="custom-external uk-link">{{service.webpage}}</a>
</div>
<div *ngIf="service.baseurl">
<span class="uk-text-meta">Base URL: </span>
<a target="_blank" [href]="service.baseurl" class="custom-external uk-link">{{service.baseurl}}</a>
</div>
<div *ngIf="service.compatibility">
<span class="uk-text-meta">Compatibility: </span>
{{service.compatibility}}
</div>
<div *ngIf="service.description" class="uk-margin-top uk-height-max-medium uk-overflow-auto">
<p [innerHTML]="service.description"></p>
</div>
</div>
<div class="uk-card-footer uk-flex uk-flex-right">
<button class="uk-button uk-button-text" (click)="openFeedbackModal(service.baseurl)">Harvest</button>
</div>
</li>
</ul>
<paging-no-load *ngIf="services.length > 10"
class="uk-margin-top"
(pageChange)="updatePage($event)"
[currentPage]="page" [size]="pageSize"
[totalResults]="services.length">
</paging-no-load>
</div>
</div>
</div>
</div>
<modal-alert #feedbackModal (alertOutput)="harvest()" [okDisabled]="(form.invalid || sending)">
<div class="uk-flex uk-flex-column uk-flex-middle">
<div>Want feedback on the harvest status?</div>
<div>Please provide us with your email address and click the <i class="uk-text-bold">harvest</i> button.</div>
<div class="uk-margin-small-top">Otherwise, simply click the <i class="uk-text-bold">harvest</i> button to continue.</div>
<div input class="uk-width-2-3 uk-margin-medium-top uk-margin-medium-bottom"
[formInput]="form.get('email')" placeholder="E-mail">
<span note>(Optional)</span>
</div>
<div>
<re-captcha (resolved)="handleRecaptcha($event)" [siteKey]="properties.reCaptchaSiteKey"
[ngClass]="sending ? 'uk-hidden':''"></re-captcha>
<loading [ngClass]="sending ? '':'uk-hidden'"></loading>
</div>
</div>
</modal-alert>

View File

@ -0,0 +1,172 @@
import {Component, ViewChild} from '@angular/core';
import {HarvesterService} from "./harvester.service";
import {EOSCService} from "../entities/eoscservice";
import {OpenaireEntities} from "../openaireLibrary/utils/properties/searchFields";
import {HelperFunctions} from "../openaireLibrary/utils/HelperFunctions.class";
import {browser} from "protractor";
import {properties} from "../../environments/environment";
import {FormBuilder, UntypedFormGroup, Validators} from "@angular/forms";
import {StringUtils} from "../openaireLibrary/utils/string-utils.class";
import {NotificationHandler} from "../openaireLibrary/utils/notification-handler";
@Component({
selector: 'app-harvester',
templateUrl: './harvester.component.html',
styleUrls: ['./harvester.component.css']
})
export class HarvesterComponent {
public allServices: EOSCService[] = [];
public services: EOSCService[] = [];
public _maxCharacters: number = 28;
public compatibilityFilterValues: Array<{"compatibility": string, "selected": boolean}> = [];
public selectedFilters: number = 0;
public pageSize: number = 10;
public page: number = 1;
public selectedBaseUrl: string = "";
public sending: boolean = false;
public form: UntypedFormGroup;
@ViewChild('feedbackModal') feedbackModal;
constructor(private fb: FormBuilder, private harvesterService: HarvesterService) {}
ngOnInit() {
this.form = this.fb.group({
email: this.fb.control('', Validators.email),
recaptcha: this.fb.control('', Validators.required),
});
let servicesMap: Map<string, EOSCService> = new Map<string, EOSCService>();
this.harvesterService.getAllTemplateInstances().subscribe(res => {
// console.log(res);
let instances = res['results'];
let compatibilitiesSet: Set<string> = new Set<string>();
let serviceIds: Set<string> = new Set<string>();
let ids: string = "";
instances.forEach(subProfile => {
let serviceId = subProfile.resourceId;
if(!serviceIds.has(serviceId)) {
ids += ids.length > 0 ? ("," + serviceId) : serviceId;
let service = new EOSCService();
// service.id = serviceId;
service.baseurl = subProfile.payload.baseURL;
service.compatibility = subProfile.payload.compatibility;
servicesMap.set(serviceId, service);
if (!compatibilitiesSet.has(service.compatibility)) {
compatibilitiesSet.add(service.compatibility);
this.compatibilityFilterValues.push({"compatibility": service.compatibility, "selected": false});
}
}
serviceIds.add(serviceId);
});
compatibilitiesSet = null;
// console.log("Subprofiles: ", this.subProfiles);
this.harvesterService.getListOfServicesByIds(ids).subscribe(res => {
// this.services = res;
// console.log(this.services);
for(let result of res) {
let service = servicesMap.get(result.id);
// console.log(service);
service.name = result.name;
service.description = result.description;
service.webpage = result.webpage;
}
this.allServices = this.getValues(servicesMap);
this.services = [...this.allServices];
}, error => {});
}, error => {});
// this.harvesterService.getServiceById().subscribe(res => {
// // console.log("Get service by id: ", res);
// }, error => {});
}
public getValues(map): any[] {
return Array.from(map.values());
}
public _formatTitle(title, length) {
return (((title + " (" + length + ")").length > this._maxCharacters) ? (title.substring(0, (this._maxCharacters - (" (" + length + ")").length - ('...').length)) + "...") : title) + " (" + (length > 95 ? "100" : length) + ")";
}
public _formatName(value) {
//let maxLineLength = 24;
let maxLineLength = 35;
//1 space after checkbox
//3 space before number + parenthesis
if (value.length + 1 > maxLineLength) {
return value.substr(0, maxLineLength - 3 - 1) + '...';
}
return value;
}
public filterChange(selected: boolean) {
selected ? this.selectedFilters++ : this.selectedFilters--;
if(this.selectedFilters == 0 || this.selectedFilters == this.compatibilityFilterValues.length) {
this.services = [...this.allServices];
} else {
this.services = this.allServices.filter(service => {
for (let filter of this.compatibilityFilterValues) {
if (filter.selected && service.compatibility == filter.compatibility) {
return true;
}
}
return false;
})
}
}
public updatePage($event) {
this.page = $event.value;
HelperFunctions.scroll();
}
public openFeedbackModal(baseUrl: string = "") {
this.selectedBaseUrl = baseUrl;
this.sending = false;
this.feedbackModal.cancelButton = false;
this.feedbackModal.okButton = true;
this.feedbackModal.okButtonText = "Harvest";
this.feedbackModal.alertTitle = "Provide your email to get feedback";
this.feedbackModal.open();
}
public harvest() {
this.sending = true;
let email: string = this.form.get("email").value;
if(!StringUtils.validateEmails(email)) {
email = null;
}
this.harvesterService.harvest(this.selectedBaseUrl, email).subscribe(res => {
console.log("Harvest: ", res);
this.sending = false;
NotificationHandler.rise('Harvest has begun');
}, error => {
this.sending = false;
NotificationHandler.rise('An error occured', "danger");
});
}
public handleRecaptcha(captchaResponse: string) {
this.form.get('recaptcha').setValue(captchaResponse);
}
protected readonly properties = properties;
}

View File

@ -0,0 +1,30 @@
import {NgModule} from "@angular/core";
import {CommonModule} from "@angular/common";
import {FormsModule} from "@angular/forms";
import {RouterModule} from "@angular/router";
import {PreviousRouteRecorder} from "../openaireLibrary/utils/piwik/previousRouteRecorder.guard";
import {HarvesterRoutingModule} from "./harvester-routing.module";
import {HarvesterComponent} from "./harvester.component";
import {HarvesterService} from "./harvester.service";
import {InputModule} from "../openaireLibrary/sharedComponents/input/input.module";
import {PagingModule} from "../openaireLibrary/utils/paging.module";
import {AlertModalModule} from "../openaireLibrary/utils/modal/alertModal.module";
import {RecaptchaModule} from "ng-recaptcha";
import {LoadingModule} from "../openaireLibrary/utils/loading/loading.module";
@NgModule({
imports: [
CommonModule, FormsModule, RouterModule,
HarvesterRoutingModule, InputModule, PagingModule, AlertModalModule, RecaptchaModule, LoadingModule
],
declarations: [
HarvesterComponent
],
providers:[
PreviousRouteRecorder, HarvesterService
],
exports: [
HarvesterComponent
]
})
export class HarvesterModule {}

View File

@ -0,0 +1,37 @@
import {Injectable} from "@angular/core";
import {HttpClient} from "@angular/common/http";
import {Observable} from "rxjs";
import {map} from "rxjs/operators";
@Injectable({
providedIn: 'root'
})
export class HarvesterService {
constructor(private http: HttpClient) {
}
getAllTemplateInstances(): Observable<any> {
let url = "https://beta.providers.eosc-portal.eu/api/configurationTemplateInstance/all?from=0&quantity=100";
return this.http.get(url, {responseType: 'json'}).pipe(map(res => res));
}
// getAllSubprofiles(): Observable<any> {
// let url = "https://beta.providers.eosc-portal.eu/api/datasource/all?catalogue_id=all&suspended=false&quantity=100";
// // return this.http.get(url).pipe(map(res => res));
// return this.http.get(url, {responseType: 'json'}).pipe(map(res => res));
// }
getListOfServicesByIds(ids: string): Observable<any> {
// let url = "https://beta.providers.eosc-portal.eu/api/service/byID/openaire.validator,eudat.b2find,lindatclariah-cz.lindatclariah-cz_repository";
let url = "https://beta.providers.eosc-portal.eu/api/service/byID/"+ids;
return this.http.get(url, {responseType: 'json'}).pipe(map(res => res));
// return this.http.get(url, {responseType: 'text'}).pipe(map(res => res));
}
harvest(baseUrl: string, email: string = null): Observable<any> {
let url: string = "https://ip-90-147-152-76.na2.garrservices.it/oai-collector-service/api/collect?oaiBaseUrl="+baseUrl+(email ? ("&notificationEmail="+email) : "");
console.log(url);
return this.http.get(url, {responseType: 'json'}).pipe(map(res => res));
}
}

@ -1 +1 @@
Subproject commit 0c21afdd7f957bd640c5d40707af826a8d07fd7d
Subproject commit 8d6522270c26451c7474630fc71e7fa3e31c5c0e

View File

@ -1,5 +1,6 @@
import {EnvProperties} from '../app/openaireLibrary/utils/properties/env-properties';
export let properties: EnvProperties = {
environment: "beta"
environment: "beta",
reCaptchaSiteKey: "6LezhVIUAAAAAOb4nHDd87sckLhMXFDcHuKyS76P"
};

View File

@ -6,5 +6,6 @@
import {EnvProperties} from '../app/openaireLibrary/utils/properties/env-properties';
export let properties: EnvProperties = {
environment: 'development'
environment: 'development',
reCaptchaSiteKey: "6LcVtFIUAAAAAB2ac6xYivHxYXKoUvYRPi-6_rLu"
};