diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 61ed7455dff2226ba99fc029daafee342494fcbd..16c8bbc2057b5f2fdf556aeb01b38a8deec93d71 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -11,6 +11,9 @@ import { CallbackComponent } from './callback/callback.component'; import { FdpGuard } from './authentication/services/fdp.guard'; import { RepositoryinfoComponent } from './repositoryinfo/repositoryinfo.component'; import { PublishApiComponent } from './publishapi/publishapi.component'; +import {SemanticEnrichmentComponent} from './semantic-enrichment/semantic-enrichment.component'; +import {CatalogComponent} from './catalog/catalog.component'; +import {DatasetComponent} from './dataset/dataset.component'; export interface ICustomRoute extends Route { name?: string; @@ -26,6 +29,8 @@ const routes: ICustomRoute[] = [ { path: 'repositoryinfo', component: RepositoryinfoComponent }, { path: 'stats', component: StatsComponent }, { path: 'publishapi', component: PublishApiComponent }, + { path: 'catalog/:id', component: CatalogComponent }, + { path: 'dataset/:id', component: DatasetComponent } ] }, {path: 'fdpsignin', component: SignupComponent, canActivate: [AuthGuardService]}, @@ -36,4 +41,4 @@ const routes: ICustomRoute[] = [ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) -export class AppRoutingModule { } \ No newline at end of file +export class AppRoutingModule { } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index c95b7f81cf32e8d72b25e10e0015f8cb02ed7fe7..b1a9e02e4bfcbecf613c362b0fb02f5f5b4522df 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -44,6 +44,13 @@ import { NgProgressModule } from 'ngx-progressbar'; import { NgProgressHttpModule } from 'ngx-progressbar/http'; import { AuthHeaderInterceptor } from './authentication/interceptor/authHeader.interceptor'; import { CallbackComponent } from './callback/callback.component'; +import { SemanticEnrichmentComponent } from './semantic-enrichment/semantic-enrichment.component'; +import {MatAutocompleteModule} from '@angular/material/autocomplete'; +import {MatFormFieldModule} from '@angular/material/form-field'; +import { OrderByScorePipe } from './semantic-enrichment/pipes/order-by-score.pipe'; +import { CatalogComponent } from './catalog/catalog.component'; +import { DatasetComponent } from './dataset/dataset.component'; +import {MatTooltipModule} from '@angular/material/tooltip'; @@ -63,47 +70,56 @@ import { CallbackComponent } from './callback/callback.component'; FeedbackDialogComponent, DatasetsDialogComponent, CallbackComponent, - - ], - imports: [ - BrowserModule, - BrowserAnimationsModule, - MatMenuModule, - MatDividerModule, - MatCardModule, - MatInputModule, - MatDialogModule, - MatProgressSpinnerModule, - MatSelectModule, - MatSidenavModule, - FormsModule, - ReactiveFormsModule, - HttpClientModule, - FileSaverModule, - SearchModule, - AppRoutingModule, - AuthenticationModule, - NebularModule, - BrowserAnimationsModule, - NbThemeModule.forRoot({ name: 'default' }), - NbLayoutModule, - NbSpinnerModule, - NbSelectModule, - NbTabsetModule, - NbTooltipModule, - NbAutocompleteModule, - NbListModule, - NbAccordionModule, - NgProgressModule.withConfig({ - - spinnerPosition: "left", - thick: true, - meteor: false - }), - NgProgressHttpModule, - NbToastrModule.forRoot(), - NbDialogModule.forRoot() + SemanticEnrichmentComponent, + OrderByScorePipe, + CatalogComponent, + DatasetComponent, + ], + imports: [ + BrowserModule, + BrowserAnimationsModule, + MatMenuModule, + MatDividerModule, + MatCardModule, + MatInputModule, + MatDialogModule, + MatProgressSpinnerModule, + MatSelectModule, + MatSidenavModule, + FormsModule, + ReactiveFormsModule, + HttpClientModule, + FileSaverModule, + SearchModule, + AppRoutingModule, + AuthenticationModule, + NebularModule, + BrowserAnimationsModule, + NbThemeModule.forRoot({name: 'default'}), + NbLayoutModule, + NbSpinnerModule, + NbSelectModule, + NbTabsetModule, + NbTooltipModule, + NbAutocompleteModule, + NbListModule, + NbAccordionModule, + NgProgressModule.withConfig({ + + spinnerPosition: 'left', + thick: true, + meteor: false + }), + NgProgressHttpModule, + NbToastrModule.forRoot(), + NbDialogModule.forRoot(), + MatAutocompleteModule, + MatFormFieldModule, + MatInputModule, + ReactiveFormsModule, + MatTooltipModule + ], entryComponents: [DatasetsDialogComponent, FeedbackDialogComponent], providers: [ AppConfiguration, diff --git a/src/app/authentication/services/auth.service.ts b/src/app/authentication/services/auth.service.ts index 885cddd297ce5d5350caa038a16b0b95bda79bfe..503a65155f8e6d629a6c738343178c784d57c348 100644 --- a/src/app/authentication/services/auth.service.ts +++ b/src/app/authentication/services/auth.service.ts @@ -42,9 +42,9 @@ export class AuthService { FdpSignUp(user: SmartHarvesterUser): Observable<any> { const httpOptions = { - headers: new HttpHeaders({ 'Content-Type': 'application/json', 'Authorization' : `Baerer ${this.getToken()}` }) + headers: new HttpHeaders({ 'Content-Type': 'application/json', Authorization : `Baerer ${this.getToken()}` }) }; - + return this.http.post(`${this.baseUrl}/harvester/auth/signup`, { firstName: user.firstName, lastName: user.lastName, @@ -57,6 +57,9 @@ export class AuthService { const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json'}) }; + if (!environment.staging && !environment.production) { + this.FdpBaseUrl = this.FdpBaseUrl.replace(':8080', ''); + } return this.http.post(`${this.FdpBaseUrl}/tokens`, { email: email, password: password @@ -69,7 +72,7 @@ export class AuthService { } updateToken(token) { - this.tokenService.saveTokenSmartHarveser(token) + this.tokenService.saveTokenSmartHarveser(token); } fetchToken(code: string, state: string): Observable<any> { @@ -86,13 +89,24 @@ export class AuthService { } isFdpLoggedIn(): boolean { - + return !this.tokenService.getFDPToken(); } - + logout() { return this.http.post(this.baseUrl + '/harvester/logout', this.getToken(), this.httpOptions); } + + getUser(accessToken: string): Observable<any> { + const options = { + headers: new HttpHeaders({ + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': 'Bearer ' + accessToken + }) + }; + return this.http.get(this.baseUrl + '/harvester/api/username', options); + } } diff --git a/src/app/authentication/services/token-storage.service.ts b/src/app/authentication/services/token-storage.service.ts index b949094e4f57e64c3ac0d6223782f02720adcd33..7056492bacbae28dc574e2a8024a827a91cf2408 100644 --- a/src/app/authentication/services/token-storage.service.ts +++ b/src/app/authentication/services/token-storage.service.ts @@ -17,9 +17,9 @@ export class TokenStorageService { public saveTokenSmartHarveser(SmartHarvesterToken: string): void { window.sessionStorage.removeItem(TOKEN_KEY); - + window.sessionStorage.setItem(TOKEN_KEY, SmartHarvesterToken); - + } public saveTokenFDP(fdpToken: string): void { @@ -41,11 +41,11 @@ export class TokenStorageService { public getUser(): any { return JSON.parse(sessionStorage.getItem(USER_KEY)); } - public removeToken(){ + public removeToken() { window.sessionStorage.removeItem(TOKEN_KEY); window.sessionStorage.removeItem(USER_KEY); } - + } diff --git a/src/app/authentication/signup/signup.component.ts b/src/app/authentication/signup/signup.component.ts index 3955dc577421b017f48b4a8bd46f85f189717325..030c76831e097801e5a4ddc3503e2d0a5692d9a9 100644 --- a/src/app/authentication/signup/signup.component.ts +++ b/src/app/authentication/signup/signup.component.ts @@ -23,14 +23,14 @@ export class SignupComponent implements OnInit, OnDestroy { errorMessage = ''; showPassword = false; user: SmartHarvesterUser = new SmartHarvesterUser(); - userFDP: SmartHarvesterUser = new SmartHarvesterUser();$ + userFDP: SmartHarvesterUser = new SmartHarvesterUser(); private _isDead = new Subject(); constructor(private authService: AuthService, private router: Router, private storageService: TokenStorageService, private http: HttpClient) { - + } - + ngOnInit(): void { const httpOptions = { @@ -97,7 +97,7 @@ export class SignupComponent implements OnInit, OnDestroy { err => console.log(err), () => this.storageService.removeToken() ); - + } getInputType() { diff --git a/src/app/callback/callback.component.ts b/src/app/callback/callback.component.ts index 072e023daac82607b2f1a66f4fd9ef3b2317b925..cf15828fbbfa0183248f05c7bd1b46b1a1bfb198 100644 --- a/src/app/callback/callback.component.ts +++ b/src/app/callback/callback.component.ts @@ -1,6 +1,11 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { AuthService } from '../authentication/services/auth.service'; +import {environment} from '../../environments/environment'; +import {takeUntil} from 'rxjs/operators'; +import {Subject} from 'rxjs'; +import {SmartHarvesterUser} from '../user/model/user'; +import {TokenStorageService} from '../authentication/services/token-storage.service'; @Component({ selector: 'app-callback', @@ -9,15 +14,35 @@ import { AuthService } from '../authentication/services/auth.service'; }) export class CallbackComponent implements OnInit { - constructor(private route: ActivatedRoute, private router: Router, private authService: AuthService) { } + user: SmartHarvesterUser = new SmartHarvesterUser(); + + constructor(private route: ActivatedRoute, private router: Router, private authService: AuthService, + private tokenService: TokenStorageService) { } ngOnInit(): void { this.route.queryParams.subscribe(p => { this.authService.fetchToken(p.code, p.state).subscribe(data => { this.authService.updateToken(data.accessToken); - this.router.navigate(['/dashboard']); - }) - }) + this.authService.getUser(data.accessToken) + .subscribe( + response => { + this.user.email = response['principal']['userInfo']['email']; + this.user.lastName = response['principal']['userInfo']['familyName']; + this.user.firstName = response['principal']['userInfo']['givenName']; + this.tokenService.saveUser(this.user); + }, + err => { + console.log(err.error.message); + this.authService.logout().subscribe( + value => this.router.navigateByUrl('/login'), + error => console.log(error), + () => this.tokenService.removeToken() + ); + }, + () => this.router.navigate(['/dashboard'])); + }); + }); + } } diff --git a/src/app/catalog/catalog.component.html b/src/app/catalog/catalog.component.html new file mode 100644 index 0000000000000000000000000000000000000000..9eeaf2c3457c26ce1e2889833a6beb27ec8ea06c --- /dev/null +++ b/src/app/catalog/catalog.component.html @@ -0,0 +1,34 @@ +<ng-container *ngIf="!isLoading else loading"> + <ng-container *ngFor="let dataset of datasets" [nbSpinner]="isLoading" > + <nb-card accent="info" class="card-background" (click)="edit(dataset.id)"> + <nb-card-header>{{dataset.title}}</nb-card-header> + <nb-card-body> + {{dataset.description}} + </nb-card-body> + <nb-card-footer> + <ul> + <li *ngIf="dataset.keywords">keywords: <span *ngFor="let keyword of dataset.keywords | keyvalue">{{keyword.key}}, </span></li> + <li *ngIf="dataset.conceptIri">themes: <span *ngFor="let theme of dataset.conceptIri ">{{theme}}, </span></li> + </ul> + </nb-card-footer> + </nb-card> + </ng-container> +</ng-container> + +<ng-template #loading xmlns="http://www.w3.org/1999/html"> + <nb-card size="small" [nbSpinner]="true" nbSpinnerStatus="primary" nbSpinnerSize="giant"></nb-card> +</ng-template> + +<ng-template #dialog let-data let-ref="dialogRef"> + <nb-card> + <nb-card-body>{{ messageError }}</nb-card-body> + <nb-card-footer> + <button nbButton (click)="getDatasetByCatalogId(); ref.close()">Retry</button> + <button nbButton (click)="ref.close()">Close</button> + </nb-card-footer> + </nb-card> +</ng-template> + + + + diff --git a/src/app/catalog/catalog.component.scss b/src/app/catalog/catalog.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..33f8498ca3005c20900b3235396cb7bf2ade47ca --- /dev/null +++ b/src/app/catalog/catalog.component.scss @@ -0,0 +1,13 @@ +.button-center{ + vertical-align: middle; + margin: auto +} + +.half-width { + width: 50%; +} +.card-background{ + card-background-color: #a5a5a5; + cursor: pointer; +} + diff --git a/src/app/catalog/catalog.component.ts b/src/app/catalog/catalog.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..39cf50d1ac33c786691a5a55d37af674c3186e81 --- /dev/null +++ b/src/app/catalog/catalog.component.ts @@ -0,0 +1,67 @@ +import {Component, OnInit, TemplateRef, ViewChild} from '@angular/core'; +import {ActivatedRoute, Router} from '@angular/router'; +import {CatalogService} from './service/catalog.service'; +import {KeywordResponse} from '../mapping/class/dataset'; +import {Observable, of} from 'rxjs'; +import {ESModel, Result} from '../semantic-enrichment/ESModel'; +import {map} from 'rxjs/operators'; +import {PostService} from '../semantic-enrichment/services/post.service'; +import {NbDialogService} from '@nebular/theme'; +import {MatAutocompleteSelectedEvent} from '@angular/material/autocomplete'; +import {ConceptsRequest, DataConcept} from '../semantic-enrichment/ConceptsRequest'; +import {FeedbackDialogComponent} from '../mapping/dialog/feedback-dialog/feedback-dialog.component'; +import {MatDialog} from '@angular/material/dialog'; + +@Component({ + selector: 'app-catalog', + templateUrl: './catalog.component.html', + styleUrls: ['./catalog.component.scss'] +}) +export class CatalogComponent implements OnInit { + + @ViewChild('dialog') dialog: TemplateRef<any>; + datasets: KeywordResponse[] = []; + isLoading: boolean; + messageError: string; + values: string[] = []; + id: string; + + constructor(private route: ActivatedRoute, private service: CatalogService, + private dialogService: NbDialogService, + private router: Router) { + + } + + ngOnInit(): void { + this.getDatasetByCatalogId(); + + } + + getDatasetByCatalogId() { + this.isLoading = true; + this.route.params.subscribe(param => { + this.service.getCatalog(param.id).subscribe( + (response: KeywordResponse[]) => { + this.datasets = response; + }, + error => { + this.isLoading = false; + this.messageError = error.message; + this.openDialog(this.dialog); + }, + () => this.isLoading = false + ); + }); + } + + openDialog(dialog: TemplateRef<any>) { + this.dialogService.open(dialog, { + context: { + }, + }); + } + + edit(id: string) { + this.router.navigate(['/dashboard/dataset', id]); + } +} diff --git a/src/app/catalog/service/catalog.service.ts b/src/app/catalog/service/catalog.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..317fb599c6b376cb3831fbfeb74d07666b41b9a9 --- /dev/null +++ b/src/app/catalog/service/catalog.service.ts @@ -0,0 +1,67 @@ +import { Injectable } from '@angular/core'; +import {TokenStorageService} from '../../authentication/services/token-storage.service'; +import {environment} from '../../../environments/environment'; +import {HttpClient, HttpHeaders} from '@angular/common/http'; +import {MetaModel} from '../../stats/MetaModel'; +import {Observable} from 'rxjs'; +import {KeywordResponse} from '../../mapping/class/dataset'; +import {ConceptsRequest} from '../../semantic-enrichment/ConceptsRequest'; + +@Injectable({ + providedIn: 'root' +}) +export class CatalogService { + + fdpToken = this.tokenService.getFDPToken(); + fdpURl = environment.fdpUrl; + smartharveserToken = this.tokenService.getToken(); + smartHarvesterUrl = environment.smartharvesterUrl; + + constructor(private tokenService: TokenStorageService, private http: HttpClient) { + } + + getCatalog(catId: string): Observable<KeywordResponse[]> { + const httpOptions = { + headers: new HttpHeaders({ + Authorization: 'Bearer ' + this.smartharveserToken + }) + }; + if (!environment.staging && !environment.production) { + this.fdpURl = this.fdpURl.replace(':8080', ''); + } + return this.http.get<KeywordResponse[]>( + `${this.smartHarvesterUrl}/harvester/api/catalog/${catId}?baseUrl=${this.fdpURl}&token=${this.fdpToken}`, + httpOptions + ); + } + + getDataset(datasetId: string): Observable<KeywordResponse> { + const httpOptions = { + headers: new HttpHeaders({ + Authorization: 'Bearer ' + this.smartharveserToken + }) + }; + if (!environment.staging && !environment.production) { + this.fdpURl = this.fdpURl.replace(':8080', ''); + } + return this.http.get<KeywordResponse>( + `${this.smartHarvesterUrl}/harvester/api/dataset/${datasetId}?baseUrl=${this.fdpURl}&token=${this.fdpToken}`, + httpOptions + ); + } + + editDataset(data: ConceptsRequest): Observable<any> { + const httpOptionsFDP = { + headers: new HttpHeaders({ + Authorization: 'Bearer ' + this.smartharveserToken, + Accept: 'application/json', + ContentType: 'application/json' + }) + }; + if (!environment.staging && !environment.production) { + this.fdpURl = this.fdpURl.replace(':8080', ''); + } + + return this.http.put(`${this.smartHarvesterUrl}/harvester/api/mapping`, data, httpOptionsFDP); + } +} diff --git a/src/app/dashboard/dashboard.component.html b/src/app/dashboard/dashboard.component.html index 0b462b964bf6830f13e00198f8f0d1aa3d2861d1..def56452631b72c8f300f59f0bbb7427b6b7fb7a 100644 --- a/src/app/dashboard/dashboard.component.html +++ b/src/app/dashboard/dashboard.component.html @@ -5,20 +5,20 @@ <img width="80" alt="Angular Logo" src="assets/images/logo.png" /> </a> <h3 style="width: 100%;text-align: center;"> <strong></strong></h3> - + <!--User badge--> - - <nb-user style="margin-right: 10px;" - name="{{user.firstName}}" + + <nb-user style="margin-right: 10px;" + name="{{user.firstName}}" title="{{user.lastName}}" [nbContextMenu]="menuItems" - nbContextMenuTag="my-context-menu" + nbContextMenuTag="my-context-menu" badgePosition="right"> </nb-user> <button (click)="logout()" nbContextMenuPlacement="right" status="danger" outline nbButton>Logout</button> - + </nb-layout-header> - + <nb-sidebar responsive start state="compacted"> <!--Menu Items--> <nb-menu [items]="menuItems" autoCollapse="true"></nb-menu> @@ -29,6 +29,5 @@ </nb-layout-column> <nb-layout-footer>Contact us</nb-layout-footer> - + </nb-layout> - \ No newline at end of file diff --git a/src/app/dashboard/dashboard.component.ts b/src/app/dashboard/dashboard.component.ts index abde7a53cb4a242f79f73a85ff0d69f5c70b1e12..a66ce8dd0c0ddd54d712bcf8c8251e1f2bb285b4 100644 --- a/src/app/dashboard/dashboard.component.ts +++ b/src/app/dashboard/dashboard.component.ts @@ -76,8 +76,8 @@ export class DashboardComponent implements OnInit { ]; constructor(private readonly sidebarService: NbSidebarService, - private authService: AuthService, - private tokeService: TokenStorageService, private route: Router, private http: HttpClient) { } + private authService: AuthService, + private tokeService: TokenStorageService, private route: Router, private http: HttpClient) { } ngOnInit(): void { this.routerUrl = this.route.url; @@ -86,28 +86,9 @@ export class DashboardComponent implements OnInit { this.routerUrl = event.url; } }); - if (this.tokeService.getUser() === null) { - const httpOptions = { - headers: new HttpHeaders({ - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': 'Bearer ' + this.authService.getToken() - }) - }; - this.http.get(environment.smartharvesterUrl + '/harvester/api/username', httpOptions) - .pipe(takeUntil(this._isDead)) - .subscribe( - data => { - this.user.email = data['principal']['userInfo']['email']; - this.user.lastName = data['principal']['userInfo']['familyName']; - this.user.firstName = data['principal']['userInfo']['givenName']; - this.tokeService.saveUser(this.user); - }, - err => console.log(err.error.message), - () => {}); - } else { - this.user = this.tokeService.getUser(); - } + + this.user = this.tokeService.getUser(); + } toggleSidebar(): boolean { @@ -121,6 +102,6 @@ export class DashboardComponent implements OnInit { err => console.log(err), () => this.tokeService.removeToken() ); - + } } diff --git a/src/app/dataset/dataset.component.html b/src/app/dataset/dataset.component.html new file mode 100644 index 0000000000000000000000000000000000000000..9db87816fc6857a8358e693693e0ff989ad792b8 --- /dev/null +++ b/src/app/dataset/dataset.component.html @@ -0,0 +1,157 @@ + + +<ng-template #noConcept> + <nb-card> + <nb-card-body> + <h5>No concepts found...</h5> + </nb-card-body> + </nb-card> +</ng-template> + +<ng-template #noKeywordsFound> + <nb-card> + <nb-card-body> + <h5>No keywords found...</h5> + </nb-card-body> + </nb-card> +</ng-template> + + +<ng-container *ngIf="!isLoading; else loading"> + <ng-container> + <h4><a href="{{dataset.url}}" target="_blank">{{dataset.title}}</a> + </h4> + + <nb-card *ngIf="dataset.keywords; else noKeywordsFound" [size]="'tiny'"> + <nb-card-header> + Keyword(s) found + </nb-card-header> + <nb-list> + <nb-list-item *ngFor="let keyword of dataset.keywords | keyvalue"> + {{ keyword.key }} + </nb-list-item> + </nb-list> + </nb-card> + + <nb-card *ngIf="dataset.conceptIri.length > 0; else noConcept; let i = index" [size]="'tiny'"> + <nb-card-header> + Concept iri found + </nb-card-header> + <nb-list> + <nb-list-item *ngFor="let k of dataset.conceptIri"> + <span><button nbButton ghost> + <nb-icon icon="trash-2-outline" status="danger" + (click)="deleteExistingConcepts(i)"> + </nb-icon> + </button></span> {{ k }} + </nb-list-item> + </nb-list> + </nb-card> + <nb-card> + <nb-card-body> + <mat-form-field class="half-width"> + <input + type="text" + placeholder="enter a value with at least 4 characters" + [(ngModel)]="value" + (ngModelChange)="onModelChange($event)" + matInput + [matAutocomplete]="auto"> + <mat-autocomplete #auto="matAutocomplete" [displayWith]="viewHandle" + (optionSelected)="getResult($event)"> + <mat-option + + *ngFor="let option of filteredOptions | async" + [value]="option" + [matTooltip]="option.source.document.description ? option.source.document.description: 'no definition found'"> + {{option.source.document.label}} + </mat-option> + </mat-autocomplete> + + </mat-form-field> + <div class="mt0-m"> + <table *ngIf="autocompleteResults.length > 0"> + <thead style="background-color: #3366ff"> + <th></th> + <th class="text-center">label</th> + <th class="text-center">definition</th> + <th class="text-center">synonyms</th> + </thead> + <tbody> + <tr *ngFor="let value of autocompleteResults; let i = index "> + <td> + <button nbButton ghost> + <nb-icon icon="trash-2-outline" status="danger" + (click)="deleteProperty(i)"> + </nb-icon> + </button> + </td> + <td class="text-center"><a href="{{value.source.document.iri}}" + target="_blank">{{ value.source.document.label }}</a></td> + <td class="text-center">{{ value.source.document.description }}</td> + <td class="text-center">{{ value.source.document.synonyms }}</td> + + </tr> + </tbody> + </table> + </div> + </nb-card-body> + </nb-card> + <nb-card [size]="'small'" *ngIf="dataset.keywords"> + <nb-card-body> + <nb-tabset> + <nb-tab *ngFor="let objet of dataset.keywords | keyvalue" tabTitle="{{(objet.key)}}"> + + <table *ngIf="objet.value.results; else noConcept"> + <thead style="background-color: #3366ff"> + <th></th> + <th class="text-center">label</th> + <th class="text-center">definition</th> + <th class="text-center">synonyms</th> + <th class="text-center">score</th> + </thead> + <tbody> + <tr *ngFor="let result of (objet.value).results "> + <td class="text-center"> + <nb-checkbox [(checked)]="result.checked"></nb-checkbox> + </td> + <td class="text-center"><a href="{{result.source.document.iri}}" + target="_blank">{{ result.source.document.label }}</a></td> + <td class="text-center">{{ result.source.document.description }}</td> + <td class="text-center">{{ result.source.document.synonyms }}</td> + <td class="text-center">{{ result.score | number: '2.2-2' }}</td> + </tr> + </tbody> + </table> + </nb-tab> + </nb-tabset> + </nb-card-body> + </nb-card> + + </ng-container> + + + <div class="row"> + <div class="button-center"> + <button nbButton status="primary" (click)="onSubmit()" [nbSpinner]="loadingPublish" [disabled]="loadingPublish" + nbSpinnerStatus="basic">Edit + </button> + </div> + </div> +</ng-container> + + +<ng-template #loading xmlns="http://www.w3.org/1999/html"> + <nb-card size="small" [nbSpinner]="true" nbSpinnerStatus="primary" nbSpinnerSize="giant"></nb-card> +</ng-template> + +<ng-template #dialog let-data let-ref="dialogRef"> + <nb-card> + <nb-card-body>{{ messageError }}</nb-card-body> + <nb-card-footer> + <button nbButton (click)="getDataset(); ref.close()">Retry</button> + <button nbButton (click)="ref.close()">Close</button> + </nb-card-footer> + </nb-card> +</ng-template> + diff --git a/src/app/dataset/dataset.component.scss b/src/app/dataset/dataset.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..2f1e5b6b43210c359432cbd8345a730f434c5826 --- /dev/null +++ b/src/app/dataset/dataset.component.scss @@ -0,0 +1,10 @@ +.button-center{ + vertical-align: middle; + margin: auto +} + +.half-width { + width: 100%; +} + + diff --git a/src/app/dataset/dataset.component.ts b/src/app/dataset/dataset.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..156fdaa1e70a62d4a4183379f1152369e400be73 --- /dev/null +++ b/src/app/dataset/dataset.component.ts @@ -0,0 +1,150 @@ +import {Component, OnInit, TemplateRef, ViewChild} from '@angular/core'; +import {ActivatedRoute} from '@angular/router'; +import {KeywordResponse} from '../mapping/class/dataset'; +import {CatalogService} from '../catalog/service/catalog.service'; +import {PostService} from '../semantic-enrichment/services/post.service'; +import {NbDialogService} from '@nebular/theme'; +import {Observable, of} from 'rxjs'; +import {map} from 'rxjs/operators'; +import {MatAutocompleteSelectedEvent} from '@angular/material/autocomplete'; +import {ESModel, Result} from '../semantic-enrichment/ESModel'; +import {ConceptsRequest, DataConcept} from '../semantic-enrichment/ConceptsRequest'; +import {FeedbackDialogComponent} from '../mapping/dialog/feedback-dialog/feedback-dialog.component'; +import {MatDialog} from '@angular/material/dialog'; + +@Component({ + selector: 'app-dataset', + templateUrl: './dataset.component.html', + styleUrls: ['./dataset.component.scss'] +}) +export class DatasetComponent implements OnInit { + + @ViewChild('dialog') dialog: TemplateRef<any>; + dataset: KeywordResponse; + isLoading: boolean; + loadingPublish: boolean; + messageError: string; + filteredOptions: Observable<Result[]>; + autocompleteResults: Result[] = []; + value = ''; + + constructor(private route: ActivatedRoute, private service: CatalogService, + private semanticService: PostService, private dialogService: NbDialogService, private matDialog: MatDialog) { } + + ngOnInit(): void { + this.getDataset(); + } + + openDialog(dialog: TemplateRef<any>) { + this.dialogService.open(dialog, { + context: { + }, + }); + } + + getDataset() { + this.isLoading = true; + this.route.params.subscribe(param => { + this.service.getDataset(param.id).subscribe( + (response: KeywordResponse) => { + this.dataset = response; + }, + error => { + this.isLoading = false; + this.messageError = error.message; + this.openDialog(this.dialog); + }, + () => this.isLoading = false + ); + }); + } + + filter(val: string): Observable<any> { + if (val.length > 3) { + return this.semanticService.getData(val) + .pipe( + map((response) => response.results )); + } + return of([]); + } + + viewHandle(value: any) { + if (typeof value !== 'string' && typeof value !== 'undefined' && null !== value) { + if (value.source ) { + return value.source.document.label; + } + } + return value; + } + + onModelChange(value: string) { + Promise.resolve(null).then(() => this.filteredOptions = this.filter(value)); + } + + deleteProperty(i: number) { + this.autocompleteResults.splice(i, 1); + } + + getResult($event: MatAutocompleteSelectedEvent) { + console.log($event); + this.autocompleteResults.push($event.option.value); + } + + deleteExistingConcepts(i) { + this.dataset.conceptIri.splice(i, 1); + } + + onSubmit() { + const mappingData = new ConceptsRequest(); + mappingData.fdpToken = this.service.fdpToken; + mappingData.dataConcepts = []; + const concepts = new DataConcept(); + concepts.url = this.dataset.url; + concepts.iris = []; + Object.values(this.dataset.keywords).forEach((data: ESModel) => { + if ((null !== data && data.results.filter(e => e.checked).length > 0)) { + if (data) { + data.results.forEach((result: Result) => { + if (result.checked) { + concepts.iris.push(result.source.document.iri); + } + }); + } + } + }); + if (this.autocompleteResults && this.autocompleteResults.length > 0) { + this.autocompleteResults.forEach((result: Result) => concepts.iris.push(result.source.document.iri)); + } + + if (this.dataset.conceptIri && this.dataset.conceptIri.length > 0) { + this.dataset.conceptIri.forEach((iri: string) => concepts.iris.push(iri)); + } + mappingData.dataConcepts.push(concepts); + console.log(mappingData); + const postedDatasets = []; + const notPostedDatasets = []; + this.loadingPublish = true; + + this.service.editDataset(mappingData).subscribe((resp) => { + resp.publishedUrl.forEach(e => postedDatasets.push(e)); + resp.notPublishedUrl.forEach(e => notPostedDatasets.push(e)); + this.matDialog.open(FeedbackDialogComponent, { + data: { + postedMetadatas: postedDatasets, + notPostedMetadatas: notPostedDatasets + } + }).afterClosed().subscribe(); + }, + error => { + this.loadingPublish = false; + this.matDialog.open(FeedbackDialogComponent, { + data: { + errorMessage: error.message, + postedMetadatas: postedDatasets, + notPostedMetadatas: notPostedDatasets + } + }); + }, + () => this.loadingPublish = false); + } +} diff --git a/src/app/datasets/datasets.component.ts b/src/app/datasets/datasets.component.ts index c22c9679a2c8bf87a970a5e83ac89e4a3520638e..7b4ff85d44279faff694666aa1ddd5deb3177364 100644 --- a/src/app/datasets/datasets.component.ts +++ b/src/app/datasets/datasets.component.ts @@ -10,6 +10,7 @@ import { MatDialog } from '@angular/material/dialog'; import { HttpClient } from '@angular/common/http'; import { HttpMethod } from '../publishapi/class/http-enum'; import { Observable } from 'rxjs'; +import {TokenStorageService} from '../authentication/services/token-storage.service'; interface RequestInfo { value?: string; @@ -54,7 +55,8 @@ export class DatasetsComponent implements OnInit, AfterViewChecked, AfterContent constructor( private dataSetService: DatasetCrudService, public dialog: MatDialog, - private httpClient: HttpClient + private httpClient: HttpClient, + private storageService: TokenStorageService ) { } get urlRepo(): string { @@ -180,7 +182,7 @@ export class DatasetsComponent implements OnInit, AfterViewChecked, AfterContent } - async launchRequest(pathName: string, httpMethod: string, preview: boolean, datasetId?: number) { + async launchRequest(pathName: string, httpMethod: string, preview: boolean, datasetId?: string) { if (preview) { this.spinners.set(pathName, true); } @@ -253,7 +255,7 @@ export class DatasetsComponent implements OnInit, AfterViewChecked, AfterContent switch (httpMethod.toLowerCase()) { case HttpMethod.GET: - if (this.openApi.info['x-result'] === 'xml' && this.getTagByPathName(pathName, httpMethod) == OpenApiTag.dataset) { + if (this.openApi.info['x-result'] === 'xml' && this.getTagByPathName(pathName, httpMethod) === OpenApiTag.dataset) { requestObs = this.httpClient.get(requestUrl, { headers, responseType: 'text' }); } else { requestObs = this.httpClient.get(requestUrl, { headers }); @@ -407,7 +409,7 @@ export class DatasetsComponent implements OnInit, AfterViewChecked, AfterContent }).finally(() => this.loading = false); } - private onCloseDatasetDialog(datasetRequestPathName: string, datasetRequestHttpMethod: string, datasetId: number[]) { + private onCloseDatasetDialog(datasetRequestPathName: string, datasetRequestHttpMethod: string, datasetId: string[]) { if (datasetId == null || datasetId.length === 0) { this.ready = false; return; @@ -420,6 +422,7 @@ export class DatasetsComponent implements OnInit, AfterViewChecked, AfterContent for (let i = 0; i < datasetId.length; i++) { this.dataSetService.saveUrls(this.getUrls(datasetRequestPathName, datasetRequestHttpMethod, datasetId[i]), datasetId[i] ); + let requestPromise: Promise<void>; if (this.openApi.info['x-result'] !== 'xml') { diff --git a/src/app/datasets/services/dataset-crud.service.ts b/src/app/datasets/services/dataset-crud.service.ts index 08181e6e50b303b0720fa826faaabbeded225ce8..7a00f71ddee6f7b6696475a3c3b4005f63a6367b 100644 --- a/src/app/datasets/services/dataset-crud.service.ts +++ b/src/app/datasets/services/dataset-crud.service.ts @@ -19,9 +19,9 @@ export class DatasetCrudService { smartHarvesterToken = this.sessionStorage.getToken(); public results: string[] = []; itemsDataset: Object[] = []; - ids: number[] = []; + ids: string[] = []; urls: Object[] = []; - idsUrls: number[] = [] + idsUrls: string[] = []; constructor( private http: HttpClient, @@ -46,7 +46,7 @@ export class DatasetCrudService { httpOptions.append('Content-Type', 'application/rdf+xml'); httpOptions.append('Accept', 'application/rdf+xml'); httpOptions.append('Authorization', `Bearer ${this.smartHarvesterToken}`) - let url = `${SMARTHARVESTER_API}/harvester/api/transform?url=${xmlUrl}&catalogId=${catalogID}`; + const url = `${SMARTHARVESTER_API}/harvester/api/mapping?url=${xmlUrl}&catalogId=${catalogID}`; const myInit = { method: 'GET', headers: httpOptions, responseType: 'text' }; const myRequest = new Request(url, myInit); @@ -152,7 +152,7 @@ export class DatasetCrudService { getLocally<T = any>(path: string): Observable<T> { return this.http.get<T>(`${path}`); } - saveDatasets(obj: Object, id: number) { + saveDatasets(obj: Object, id: string) { this.itemsDataset.push(obj); this.ids.push(id); } @@ -161,7 +161,7 @@ export class DatasetCrudService { this.ids = []; } - saveUrls(url: Object, id: number) { + saveUrls(url: Object, id: string) { this.urls.push(url); this.idsUrls.push(id); } diff --git a/src/app/mapping/class/dataset.ts b/src/app/mapping/class/dataset.ts index f1e198cceed173236101d580a585bf7d57a7cb7f..8945deba597bc3be672c12e23f5041be7facd8c6 100644 --- a/src/app/mapping/class/dataset.ts +++ b/src/app/mapping/class/dataset.ts @@ -1,3 +1,5 @@ +import {ESModel} from '../../semantic-enrichment/ESModel'; + export class Property { public property: string; public uri: string; @@ -7,7 +9,7 @@ export class Property { public geodcat: boolean; public isDuplicated?: boolean = false; public dcatClass?: string; - + constructor(name: string, identifier: string, usageNote: string, isDuplicated: boolean, card: string, geodcat:boolean) { this.property = name; this.uri = identifier; @@ -24,13 +26,13 @@ export class OldProperty { public identifier: string; public usageNote: string; public isDuplicated?: boolean = false; - + constructor(name: string, identifier: string, usageNote: string, isDuplicated: boolean, card: string, geodcat:boolean) { this.name = name; this.identifier = identifier; this.usageNote = usageNote; this.isDuplicated = isDuplicated; - + } } export class DatasetPath { @@ -56,7 +58,7 @@ export class Distribution { public geodcat: boolean; public isDuplicated?: boolean = false; - + } export class ResponseFileTsv { @@ -67,4 +69,12 @@ export class ResponseFileTsv { public object_category: string; } +export class KeywordResponse { + public id: string; + public url: string; + public title: string; + public description: string; + public keywords: Map<string, ESModel>; + public conceptIri: string[]; +} diff --git a/src/app/mapping/class/dataverse.ts b/src/app/mapping/class/dataverse.ts deleted file mode 100644 index af158a9f89ea35d8435773376bde79367b0d0e8c..0000000000000000000000000000000000000000 --- a/src/app/mapping/class/dataverse.ts +++ /dev/null @@ -1,52 +0,0 @@ -export interface Dataverse { - status: string, - data: Data -} - -export interface Data { - id: number, - identifier: string, - persistentUrl: string, - protocol : string, - authority: string, - publisher: string, - publicationDate: string, - storageIdentifier: string, - latestVersion: LatestVersion -} - -export interface LatestVersion { - id: number, - datasetId: number, - datasetPersistentId: string, - storageIdentifier: string, - versionNumber: number, - versionMinorNumber: number, - versionState: string, - distributionDate: string, - productionDate: string, - lastUpdateTime: string, - releaseTime: string, - createTime: string, - license: string, - termsOfUse: string, - fileAccessRequest: boolean, - metadataBlocks: MetadataBlock, - file: Array<string> -} - -export interface MetadataBlock { - citation: Citation -} - -export interface Citation { - displayName: string, - fields: Array<Field> -} - -export interface Field { - typeName: string, - multiple: boolean, - typeClass: string, - value: Field | string -} \ No newline at end of file diff --git a/src/app/mapping/class/requestMapping.ts b/src/app/mapping/class/requestMapping.ts index 1ee7bfc81f37b2f18da848efd75de518e252120c..5bb9c18869077d89b3daaf80a8a1f5ccc60e074e 100644 --- a/src/app/mapping/class/requestMapping.ts +++ b/src/app/mapping/class/requestMapping.ts @@ -1,10 +1,10 @@ export class RequestMapping { - urls: string[]; + ids: string[]; paths: Path[]; fdpToken: string; - constructor (urls: string[], paths: Path[], fdpToken: string) { - this.urls = urls; + constructor (ids: string[], paths: Path[], fdpToken: string) { + this.ids = ids; this.paths = paths; this.fdpToken = fdpToken; } @@ -27,4 +27,4 @@ export class Path { export class ResponseMapping { notPublishedUrl: string[]; publishedUrl: string[]; -} \ No newline at end of file +} diff --git a/src/app/mapping/dialog/feedback-dialog/feedback-dialog.component.html b/src/app/mapping/dialog/feedback-dialog/feedback-dialog.component.html index 176c0ca3ffd9bc21f19f36bfdef1cdf3c68d7aad..abd5b634c8994a853450413bb994430f1105150b 100644 --- a/src/app/mapping/dialog/feedback-dialog/feedback-dialog.component.html +++ b/src/app/mapping/dialog/feedback-dialog/feedback-dialog.component.html @@ -3,17 +3,18 @@ <h3 style="text-align: center;">Feedback: </h3> </nb-card-header> <nb-card-body> - <nb-card > + <p *ngIf="data.errorMessage">{{data.errorMessage}}</p> + <nb-card *ngIf="data.notPostedMetadatas.length > 0"> <nb-card-body> - <p class="error" *ngIf="data.notPostedMetadatas.length > 0">An error occurred while publishing the following Datasets: </p> + <p class="error" >An error occurred while publishing the following Datasets: </p> <ul class="error" *ngFor="let data of data.notPostedMetadatas"> <li class="error">{{ data }}</li> </ul> </nb-card-body> </nb-card> - <nb-card> + <nb-card *ngIf="data.postedMetadatas.length > 0"> <nb-card-body> - <p class="success" *ngIf="data.postedMetadatas.length > 0">The following datasets have been published successfully: </p> + <p class="success" >The following datasets have been published successfully: </p> <ul *ngFor="let data of data.postedMetadatas"> <li class="success" >{{ data }}</li> </ul> @@ -27,4 +28,4 @@ </div> </div> </nb-card-footer> -</nb-card> \ No newline at end of file +</nb-card> diff --git a/src/app/mapping/dialog/feedback-dialog/feedback-dialog.component.ts b/src/app/mapping/dialog/feedback-dialog/feedback-dialog.component.ts index d6b6ecdea6a1aadd4c668914bc2cbe321b2fea6e..fa3f7644f30d2be710628a51efb7145196b0a8e5 100644 --- a/src/app/mapping/dialog/feedback-dialog/feedback-dialog.component.ts +++ b/src/app/mapping/dialog/feedback-dialog/feedback-dialog.component.ts @@ -9,10 +9,10 @@ import { NbDialogRef } from '@nebular/theme'; }) export class FeedbackDialogComponent implements OnInit { - + constructor(private dialog: MatDialogRef<FeedbackDialogComponent>, - @Inject(MAT_DIALOG_DATA) public data: {postedMetadatas: any[], notPostedMetadatas: any[]} ) { } + @Inject(MAT_DIALOG_DATA) public data: {errorMessage: string, postedMetadatas: any[], notPostedMetadatas: any[]} ) { } ngOnInit(): void { } diff --git a/src/app/mapping/mapping.component.html b/src/app/mapping/mapping.component.html index da877ae6197fa95109b19d03f1e6f2beca1714e9..c86e88d44f03eec6ec096b09cd0c9757570e51f9 100644 --- a/src/app/mapping/mapping.component.html +++ b/src/app/mapping/mapping.component.html @@ -202,7 +202,7 @@ <input *ngIf="isMandatory(distribution.card) && distribution.uri !== 'dcat:distribution'" required fullWidth name=" distri{{index}}" nbInput - (ngModelChange)="onModelChange($event)" + (ngModelChange)="onModelChange($event)" [(ngModel)]="distributionSelectedPaths[index]" [value]="distributionSelectedPaths[index]? distributionSelectedPaths[index] : ''" (focus)="reset()" /> @@ -210,7 +210,7 @@ <input *ngIf="!isMandatory(distribution.card) && distribution.uri !== 'dcat:distribution'" fullWidth name=" distri{{index}}" nbInput - (ngModelChange)="onModelChange($event)" + (ngModelChange)="onModelChange($event)" [(ngModel)]="distributionSelectedPaths[index]" [value]="distributionSelectedPaths[index]? distributionSelectedPaths[index] : ''" (focus)="reset()" /> @@ -371,12 +371,12 @@ </div> </div> - <div class="row"> + <!-- <div class="row"> <div class="button-center" *ngIf="!first "> - <button nbButton status="primary" (click)=" publishDataset()" [nbSpinner]="loading" + <button nbButton status="primary" (click)="getKeywords()" [nbSpinner]="loading" nbSpinnerStatus="basic">Publish</button> </div> - </div> + </div>--> </nb-card-footer> </nb-card> @@ -423,4 +423,4 @@ </nb-card-footer> </nb-card> -</ng-template> \ No newline at end of file +</ng-template> diff --git a/src/app/mapping/mapping.component.ts b/src/app/mapping/mapping.component.ts index 152445461bdb20646a99d2a0d3cfac4071529e47..f996c536c3ff604e54bc3d1bda761fb98c5fe288 100644 --- a/src/app/mapping/mapping.component.ts +++ b/src/app/mapping/mapping.component.ts @@ -1,7 +1,7 @@ import { HttpResponse } from '@angular/common/http'; -import { ChangeDetectorRef, Component, Input, OnInit, ViewChild } from '@angular/core'; +import {ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Router } from '@angular/router'; @@ -12,7 +12,7 @@ import { environment } from 'src/environments/environment'; import { TokenStorageService } from '../authentication/services/token-storage.service'; import { DatasetCrudService } from '../datasets/services/dataset-crud.service'; -import { Property, DatasetPath, Distribution, ResponseFileTsv, OldProperty } from './class/dataset'; +import {Property, DatasetPath, Distribution, ResponseFileTsv, OldProperty, KeywordResponse} from './class/dataset'; import { Path, RequestMapping } from './class/requestMapping'; import { FeedbackDialogComponent } from './dialog/feedback-dialog/feedback-dialog.component'; import { MappingService } from './service/mapping.service'; @@ -25,11 +25,20 @@ import { JSONPath } from 'jsonpath-plus'; }) export class MappingComponent implements OnInit { - addDistribution = false + @Output() + dataEvent = new EventEmitter<RequestMapping>(); + + @Output() + isJsonPathEvent = new EventEmitter(); + + + + paths: Path[]; + addDistribution = false; check = false; distributions: Property[] = []; ontology: string; - itemsdataset: any[] = [] + itemsdataset: any[] = []; urls: any[] = []; datasets: Property[]; vocabularies: Property[]; @@ -39,19 +48,21 @@ export class MappingComponent implements OnInit { distributionSelectedPaths: string[]; datasetMappedMetadatas: Array<Array<Map<number, string>>>; distributionMappedMetadatas: Array<Array<Map<number, string>>>; - index: number = 0 - first: boolean = true; + index = 0; + first = true; loading = false; loadingCr = false; FDP_URL = environment.fdpUrl; - ids: number[]; + ids: string[]; default = true; isJsonPath = false; @ViewChild('autoInput') input; @Input() type: string; @Input() catalogId: any; - constructor(private ref: ChangeDetectorRef, private dataSetService: DatasetCrudService, private dialog: MatDialog, private route: Router, private tokenStorage: TokenStorageService, private mappingService: MappingService) { + constructor(private ref: ChangeDetectorRef, private dataSetService: DatasetCrudService, + private dialog: MatDialog, private route: Router, private tokenStorage: TokenStorageService, + private mappingService: MappingService) { ref.detach(); setInterval(() => { this.ref.detectChanges(); @@ -59,15 +70,15 @@ export class MappingComponent implements OnInit { } ngOnInit() { - console.log(this.type) + console.log(this.type); this.urls = this.dataSetService.urls; console.table(this.urls); this.ids = this.dataSetService.idsUrls; - if (this.type !== "ISO 19115") { + if (this.type !== 'ISO 19115') { this.urls = this.dataSetService.urls; this.ids = this.dataSetService.idsUrls; this.itemsdataset = this.dataSetService.itemsDataset; - console.log(this.itemsdataset) + console.log(this.itemsdataset); this.ids = this.dataSetService.ids; this.keys = []; this.getKeysFromMetadataCustom(this.itemsdataset[0], ''); @@ -82,15 +93,16 @@ export class MappingComponent implements OnInit { selectOntology(): void { this.dataSetService.getLocally(`./assets/geodcat.json`).subscribe( datasets => { - this.datasets = this.ontology === "dcat" ? datasets.filter(d => !d.geodcat) : datasets; - this.vocabularies = this.ontology === "dcat" ? datasets.filter(d => !d.geodcat) : datasets; + this.datasets = this.ontology === 'dcat' ? datasets.filter(d => !d.geodcat) : datasets; + this.vocabularies = this.ontology === 'dcat' ? datasets.filter(d => !d.geodcat) : datasets; this.vocabularies.forEach(dataset => dataset.dcatClass = 'dcat:dataset'); this.selectedPaths = []; this.dataSetService.getLocally('./assets/distribution.json').subscribe( (distributions: Distribution[]) => { - this.distributions = this.ontology === "dcat" ? distributions.filter(d => !d.geodcat) : distributions; + this.distributions = this.ontology === 'dcat' ? distributions.filter(d => !d.geodcat) : distributions; this.distributions.forEach(distribution => distribution.dcatClass = 'dcat:distribution'); - this.vocabularies = this.vocabularies.concat(this.ontology === "dcat" ? this.distributions.filter(d => !d.geodcat) : distributions); + this.vocabularies = this.vocabularies.concat(this.ontology === 'dcat' ? + this.distributions.filter(d => !d.geodcat) : distributions); this.distributionSelectedPaths = []; } ); @@ -106,9 +118,9 @@ export class MappingComponent implements OnInit { mapFromXslt() { this.loading = true; this.itemsdataset = []; - let requestPromises: Promise<any>[] = []; + const requestPromises: Promise<any>[] = []; this.urls.forEach((url: string) => { - let requestPromise = this.dataSetService.getDcatFromApi(this.catalogId, url) + const requestPromise = this.dataSetService.getDcatFromApi(this.catalogId, url) .then((response: Response) => { response.text() .then((text) => { @@ -122,12 +134,13 @@ export class MappingComponent implements OnInit { createDataset(item: Object, classDcat: string): Map<number, string> { - let mappedMetadata: Map<number, string> = new Map() - const array: string[] = (classDcat === "dataset") ? this.selectedPaths : this.distributionSelectedPaths; + const mappedMetadata: Map<number, string> = new Map(); + const array: string[] = (classDcat === 'dataset') ? this.selectedPaths : this.distributionSelectedPaths; for (let i = 0; i < array.length; i++) { if (array[i] && array.length > 0) { - let tab = array[i].split(' : '); - !this.isJsonPath ? mappedMetadata.set(i, this.getValueCustom(tab, item)) : mappedMetadata.set(i, this.getValueJsonPath(array[i], item)); + const tab = array[i].split(' : '); + !this.isJsonPath ? mappedMetadata.set(i, this.getValueCustom(tab, item)) + : mappedMetadata.set(i, this.getValueJsonPath(array[i], item)); } } this.loadingCr = false; @@ -135,7 +148,7 @@ export class MappingComponent implements OnInit { } publishDataset(): void { - let paths: Path[] = []; + const paths: Path[] = []; for (let i = 0; i < this.datasets.length; i++) { if (this.selectedPaths[i]) { @@ -144,19 +157,22 @@ export class MappingComponent implements OnInit { } for (let i = 0; i < this.distributions.length; i++) { if (this.distributionSelectedPaths[i]) { - paths.push(new Path(this.distributions[i].uri, this.distributionSelectedPaths[i], this.distributions[i].card, this.distributions[i].dcatClass)); + paths.push( + new Path( + this.distributions[i].uri, this.distributionSelectedPaths[i], this.distributions[i].card, this.distributions[i].dcatClass + ) + ); } } - - let data: RequestMapping = new RequestMapping(this.urls, paths, this.tokenStorage.getFDPToken()); - - let postedDatasets = []; - let notPostedDatasets = []; + const data: RequestMapping = new RequestMapping(this.ids, paths, this.tokenStorage.getFDPToken()); + // this.dataEvent.emit(data); + /*const postedDatasets = []; + const notPostedDatasets = []; this.loading = true; const requestPromises: Promise<any>[] = []; - let requestPromise = this.mappingService.postToFdpFropSmartharvester(this.catalogId, data, this.isJsonPath).then(resp => { + const requestPromise = this.mappingService.postToFdpFropSmartharvester(this.catalogId, data, this.isJsonPath).then(resp => { if (resp) { console.log(resp); resp.json().then((datas: any) => { @@ -177,14 +193,14 @@ export class MappingComponent implements OnInit { } }).afterClosed().subscribe(); - }) + });*/ } buildRDF(vocabularies: Property[], array: Map<number, string>[], i: number): string { - let properties = ""; + let properties = ''; let version = false; array[i].forEach((value: string, key: number) => { - let uri = vocabularies[key].uri; + const uri = vocabularies[key].uri; if (uri === 'dct:hasVersion') { version = true; } @@ -193,7 +209,7 @@ export class MappingComponent implements OnInit { if (this.isReplicable(this.getCard(uri))) { value.forEach(v => { properties += this.writeRdf(uri, v); - }) + }); } else { properties += this.writeRdf(uri, value[0]); } @@ -201,21 +217,21 @@ export class MappingComponent implements OnInit { properties += this.writeRdf(uri, value); } - }) + }); - if (!version) properties += `dct:hasVersion "undefined";\n`; + if (!version) { properties += `dct:hasVersion "undefined";\n`; } return properties; } publishDatasetFromGeodcatApi() { - let postedDatastes = []; - let notPostedDatasets = []; + const postedDatastes = []; + const notPostedDatasets = []; this.loading = true; const requestPromises: Promise<any>[] = []; for (let i = 0; i < this.urls.length; i++) { - let requestPromise = this.dataSetService.createDatasetXml(this.itemsdataset[i]).then((resp: HttpResponse<any>) => { + const requestPromise = this.dataSetService.createDatasetXml(this.itemsdataset[i]).then((resp: HttpResponse<any>) => { if (resp.status.toString().startsWith('2')) { const location: string = resp.headers.get('location'); console.log(location); @@ -224,7 +240,7 @@ export class MappingComponent implements OnInit { } else { notPostedDatasets.push(this.ids[i]); } - }) + }); requestPromises.push(requestPromise); } Promise.all(requestPromises).finally(() => { @@ -236,15 +252,15 @@ export class MappingComponent implements OnInit { } }).afterClosed().subscribe(); - }) + }); } private writeRdf(key: string, value: string): string { - let properties = ""; - value = (value && typeof value === "string") ? value.replace(/\n/g, '') : value; + let properties = ''; + value = (value && typeof value === 'string') ? value.replace(/\n/g, '') : value; switch (key) { case 'dcat:landingPage': @@ -276,14 +292,14 @@ export class MappingComponent implements OnInit { if (typeof e === 'object') { Object.keys(e).forEach(k => { this.keys.push(keyParent + k); - }) + }); this.getKeysFromMetadataCustom(e, keyParent + key + ' : '); } }); - this.keys.push(keyParent + key) + this.keys.push(keyParent + key); this.getKeysFromMetadataCustom(obj[key], keyParent + key + ' : '); } else { - this.keys.push(keyParent + key) + this.keys.push(keyParent + key); this.getKeysFromMetadataCustom(obj[key], keyParent + key + ' : '); } } else { @@ -301,15 +317,15 @@ export class MappingComponent implements OnInit { if (typeof obj[key] === 'object') { if (Array.isArray(obj[key])) { if (keyParent) { - this.keys.push(keyParent + ' : ' + key) + this.keys.push(keyParent + ' : ' + key); } else { - this.keys.push(key) + this.keys.push(key); } obj[key].forEach(e => { if (typeof e === 'object') { this.getKeysFromMetadataDataverse(e, keyParent + ' : ' + key + ' : ' + obj[key].indexOf(e)); } else { - this.keys.push(keyParent + ' : ' + key + ' : ' + obj[key].indexOf(e)) + this.keys.push(keyParent + ' : ' + key + ' : ' + obj[key].indexOf(e)); } }); } else { @@ -321,9 +337,9 @@ export class MappingComponent implements OnInit { } } else { if (keyParent) { - this.keys.push(keyParent + ' : ' + key) + this.keys.push(keyParent + ' : ' + key); } else { - this.keys.push(key) + this.keys.push(key); } } }); @@ -332,10 +348,10 @@ export class MappingComponent implements OnInit { let value = JSONPath({ path: jsonPath, json: item - }) + }); - if (value === null || value === "") { - value = "undefined"; + if (value === null || value === '') { + value = 'undefined'; } return value; @@ -343,17 +359,17 @@ export class MappingComponent implements OnInit { private getValueCustom(tab: string[], item: Object) { let obj: Object; try { - if (tab.length == 1) { + if (tab.length === 1) { return item[tab[0]]; } else { for (let i = 0; i < tab.length; i++) { - if (i == 0) { - obj = item[tab[i]] + if (i === 0) { + obj = item[tab[i]]; } else if (i < tab.length - 1 && obj[tab[i]]) { obj = obj[tab[i]]; } else { if (Array.isArray(obj) && obj.some(e => typeof e === 'object')) { - let array = []; + const array = []; obj.forEach(e => { if (e[tab[i]]) { array.push(e[tab[i]]); @@ -361,13 +377,13 @@ export class MappingComponent implements OnInit { }); return array; } else if (typeof obj === 'object' && Object.values(obj).every(e => Array.isArray(e))) { - let array = []; - for (let element in obj) { + const array = []; + for (const element in obj) { obj[element].forEach(e => { if (e[tab[i]]) { array.push(e[tab[i]]); } - }) + }); } return array; } else if (Array.isArray(obj[tab[i]])) { @@ -379,17 +395,17 @@ export class MappingComponent implements OnInit { } } } catch { - return "undefined" + return 'undefined'; } - - + + } private getDistributionItem(tab: string[], item: Object) { if (tab.length === 0) { return []; } - let currentPath = tab[0]; + const currentPath = tab[0]; if (!item.hasOwnProperty(currentPath) || item[currentPath] == null) { return []; } @@ -403,31 +419,31 @@ export class MappingComponent implements OnInit { } private getValueDataverse(tab: string[], item: Object): string { - let obj: Object + let obj: Object; for (let i = 0; i < tab.length; i++) { - if (tab.length == 1) { + if (tab.length === 1) { return item[tab[0]]; } else { - if (i == 0) { - obj = item[tab[i]] + if (i === 0) { + obj = item[tab[i]]; } else if (i < tab.length - 1 && obj[tab[i]]) { obj = obj[tab[i]]; } else { if (obj[tab[i]] && typeof obj[tab[i]] !== 'object') { return obj[tab[i]]; } else { - if (i == tab.length - 2) { - this.getKeysFromMetadataDataverse(item, ""); - console.log(tab[i] + " " + i) + if (i === tab.length - 2) { + this.getKeysFromMetadataDataverse(item, ''); + console.log(tab[i] + ' ' + i); this.keys.forEach(k => { - let table = k.split(" : "); + const table = k.split(' : '); if (table[table.length - 2] === tab[i]) { for (let i = 0; i < tab.length; i++) { - if (table.length == 1) { + if (table.length === 1) { return item[tab[0]]; } else { - if (i == 0) { - obj = item[table[i]] + if (i === 0) { + obj = item[table[i]]; } else if (i < table.length - 1 && obj[table[i]]) { obj = obj[table[i]]; } else { @@ -440,7 +456,7 @@ export class MappingComponent implements OnInit { } } } - }) + }); } } } @@ -455,44 +471,46 @@ export class MappingComponent implements OnInit { mapDataset() { this.loadingCr = true; this.datasetMappedMetadatas = new Array<Array<Map<number, string>>>(); - this.distributionMappedMetadatas = new Array<Array<Map<number, string>>>() + this.distributionMappedMetadatas = new Array<Array<Map<number, string>>>(); for (let i = 0; i < this.itemsdataset.length; i++) { - this.datasetMappedMetadatas.push([this.createDataset(this.itemsdataset[i], "dataset")]); + this.datasetMappedMetadatas.push([this.createDataset(this.itemsdataset[i], 'dataset')]); if (this.distributionSelectedPaths.length > 0) { if (this.distributionSelectedPaths[0] !== '' && this.distributionSelectedPaths[0] !== null && this.distributionSelectedPaths[0]) { - let distributionItems: Object[] = (this.isJsonPath) ? this.getValueJsonPath(this.distributionSelectedPaths[0], this.itemsdataset[i]) : this.getDistributionItem(this.distributionSelectedPaths[0].split(' : '), this.itemsdataset[i]); - let distributionArray: Map<number, string>[] = [] + const distributionItems: Object[] = (this.isJsonPath) + ? this.getValueJsonPath(this.distributionSelectedPaths[0], this.itemsdataset[i]) + : this.getDistributionItem(this.distributionSelectedPaths[0].split(' : '), this.itemsdataset[i]); + const distributionArray: Map<number, string>[] = []; for (let j = 0; j < distributionItems.length; j++) { - let item = distributionItems[j] - distributionArray.push(this.createDataset(distributionItems[j], "distribution")); + const item = distributionItems[j]; + distributionArray.push(this.createDataset(distributionItems[j], 'distribution')); } this.distributionMappedMetadatas.push(distributionArray); } else { - this.distributionMappedMetadatas.push([this.createDataset(this.itemsdataset[i], "distribution")]) + this.distributionMappedMetadatas.push([this.createDataset(this.itemsdataset[i], 'distribution')]); } } } this.first = false; } next() { - //this.loadingCr = true; + // this.loadingCr = true; if (this.index < this.itemsdataset.length) { this.index += 1; - //this.createDataset(this.itemsdataset[this.index]) + // this.createDataset(this.itemsdataset[this.index]) console.log('index = ' + this.index); } } prev() { - //this.loadingCr = true; + // this.loadingCr = true; if (this.index > 0) { this.index -= 1; } - //this.createDataset(this.itemsdataset[this.index]) + // this.createDataset(this.itemsdataset[this.index]) console.log('index = ' + this.index); } createObjectToExport(): DatasetPath[] { - let datasetPathArray: DatasetPath[] = [] + const datasetPathArray: DatasetPath[] = []; for (let i = 0; i < this.datasets.length; i++) { if (this.selectedPaths[i]) { datasetPathArray.push(new DatasetPath(this.datasets[i], this.selectedPaths[i], 'dcat:dataset', this.datasets[i].card)); @@ -500,7 +518,9 @@ export class MappingComponent implements OnInit { } for (let i = 0; i < this.distributions.length; i++) { if (this.distributionSelectedPaths[i]) { - datasetPathArray.push(new DatasetPath(this.distributions[i], this.distributionSelectedPaths[i], 'dcat:distribution', this.distributions[i].card)) + datasetPathArray.push( + new DatasetPath(this.distributions[i], this.distributionSelectedPaths[i], 'dcat:distribution', this.distributions[i].card) + ); } } return datasetPathArray; @@ -508,7 +528,7 @@ export class MappingComponent implements OnInit { downloadJson() { - let datasetPathArray = this.createObjectToExport(); + const datasetPathArray = this.createObjectToExport(); @@ -544,57 +564,59 @@ export class MappingComponent implements OnInit { this.selectedPaths = []; this.distributionSelectedPaths = []; reader.onloadend = () => { - if (jsonFile.name.split('.')[1] === "tsv") { - let paths: ResponseFileTsv[] = <ResponseFileTsv[]>this.mappingService.tsvToJson(reader.result as string); - let datasetPath: ResponseFileTsv[] = []; - let distributionPath: ResponseFileTsv[] = []; - console.table(paths) + if (jsonFile.name.split('.')[1] === 'tsv') { + const paths: ResponseFileTsv[] = this.mappingService.tsvToJson(reader.result as string) as ResponseFileTsv[]; + const datasetPath: ResponseFileTsv[] = []; + const distributionPath: ResponseFileTsv[] = []; + console.table(paths); paths.forEach(path => { (path.object_category.includes('dcat:dataset')) ? datasetPath.push(path) : distributionPath.push(path); - }) + }); - console.log(datasetPath) - console.log(distributionPath) - if (distributionPath.length > 0) this.addDistribution = true; + console.log(datasetPath); + console.log(distributionPath); + if (distributionPath.length > 0) { this.addDistribution = true; } this.fillFromTsv(this.datasets, this.selectedPaths, datasetPath, 'dcat:dataset'); this.fillFromTsv(this.distributions, this.distributionSelectedPaths, distributionPath, 'dcat:distribution'); } else { - let paths: DatasetPath[] = <DatasetPath[]>JSON.parse(reader.result as string); - console.table(paths) + const paths: DatasetPath[] = JSON.parse(reader.result as string) as DatasetPath[]; + console.table(paths); for (let i = 0; i < paths.length; i++) { if (i > 0 && paths[i].dcatProperty === paths[i - 1].dcatProperty) { - const voc: Property = this.datasets.find(e => e.uri === (<Property>paths[i].dcatProperty).uri); + const voc: Property = this.datasets.find(e => e.uri === (paths[i].dcatProperty as Property).uri); this.datasets.splice(i + 1, 0, voc); this.selectedPaths.splice(i + 1, 0, paths[i].path); if (typeof paths[i].dcatProperty !== 'object') { - this.datasets[i] = this.vocabularies.filter(e => e.dcatClass === 'dcat:dataset').find(v => v.uri === <string>paths[i].dcatProperty); + this.datasets[i] = this.vocabularies.filter(e => e.dcatClass === 'dcat:dataset') + .find(v => v.uri === paths[i].dcatProperty as string); } else { - this.datasets[i] = this.vocabularies.filter(e => e.dcatClass === 'dcat:dataset').find(v => v.uri === (<OldProperty><unknown>paths[i].dcatProperty).identifier); + this.datasets[i] = this.vocabularies.filter(e => e.dcatClass === 'dcat:dataset') + .find(v => v.uri === (paths[i].dcatProperty as unknown as OldProperty).identifier); } } else { this.selectedPaths[i] = paths[i].path; if (typeof paths[i].dcatProperty !== 'object') { - this.datasets[i] = this.vocabularies.find(v => v.uri === <string>paths[i].dcatProperty); + this.datasets[i] = this.vocabularies.find(v => v.uri === paths[i].dcatProperty as string); } else { - this.datasets[i] = this.vocabularies.find(v => v.uri === (<OldProperty><unknown>paths[i].dcatProperty).identifier); + this.datasets[i] = this.vocabularies.find(v => v.uri === (paths[i].dcatProperty as unknown as OldProperty).identifier); } } } } - } + }; reader.readAsText(jsonFile); - this.ref.markForCheck() + this.ref.markForCheck(); } addField(index: number, properties: Property[], paths: string[]) { - let addedDcat: Property = properties.find(e => e.uri === properties[index].uri); + const addedDcat: Property = properties.find(e => e.uri === properties[index].uri); properties.splice(index + 1, 0, addedDcat); paths.splice(index + 1, 0, ''); @@ -619,7 +641,7 @@ export class MappingComponent implements OnInit { /* autocompletion functions */ private filter(value: string): string[] { - let filterValue = value.toLowerCase(); + const filterValue = value.toLowerCase(); return this.keys.filter(optionValue => optionValue.toLowerCase().includes(filterValue)); } @@ -629,10 +651,10 @@ export class MappingComponent implements OnInit { ); } onModelChange(value: string) { - if (!this.isJsonPath) this.filteredOptions = of(this.filter(value)); + if (!this.isJsonPath) { this.filteredOptions = of(this.filter(value)); } } reset() { - if (!this.isJsonPath) this.filteredOptions = of(this.keys); + if (!this.isJsonPath) { this.filteredOptions = of(this.keys); } } toggle(checked: boolean) { @@ -641,7 +663,8 @@ export class MappingComponent implements OnInit { toggleJsonPath(checked: boolean) { this.isJsonPath = checked; - if (this.isJsonPath) this.filteredOptions = of([]); + if (this.isJsonPath) { this.filteredOptions = of([]); } + // this.isJsonPathEvent.emit(this.isJsonPath); } toggleDistri(checked: boolean) { @@ -649,34 +672,52 @@ export class MappingComponent implements OnInit { } isMandatory(card: string): boolean { - return card.startsWith("1"); + return card.startsWith('1'); } isReplicable(card: string): boolean { - return card.endsWith("n"); + return card.endsWith('n'); } getCard(uri: string): string { return this.vocabularies.find((vocabulary: Property) => vocabulary.uri === uri).card; } + getKeywords() { + + const paths: Path[] = []; + + if (this.datasets) { + for (let i = 0; i < this.datasets.length; i++) { + if (this.selectedPaths[i]) { + paths.push(new Path(this.datasets[i].uri, this.selectedPaths[i], this.datasets[i].card, this.datasets[i].dcatClass)); + } + } + } + + const data: RequestMapping = new RequestMapping(this.dataSetService.idsUrls, paths, this.tokenStorage.getFDPToken()); + + this.dataEvent.emit(data); + this.isJsonPathEvent.emit(this.isJsonPath); + } + private getDistributionEmpty(properties: string, datasetId: string): string { return `@prefix dcat: <http://www.w3.org/ns/dcat#>. @prefix dct: <http://purl.org/dc/terms/>. @prefix adms: <http://www.w3.org/ns/adms#> . @prefix dqv: <http://www.w3.org/ns/dqv#> . @prefix geodcat: <http://data.europa.eu/930/>. - @prefix prov: <http://www.w3.org/ns/prov#>. - @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>. - @prefix skos: <http://www.w3.org/2004/02/skos/core#>. - @prefix spdx: <http://spdx.org/rdf/terms#>. - @prefix foaf: <http://xmlns.com/foaf/0.1/>. - @prefix odrl: <http://www.w3.org/ns/odrl/2/>. + @prefix prov: <http://www.w3.org/ns/prov#>. + @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>. + @prefix skos: <http://www.w3.org/2004/02/skos/core#>. + @prefix spdx: <http://spdx.org/rdf/terms#>. + @prefix foaf: <http://xmlns.com/foaf/0.1/>. + @prefix odrl: <http://www.w3.org/ns/odrl/2/>. @prefix cnt: <http://www.w3.org/2011/content#>. @prefix language: <http://id.loc.gov/vocabulary/iso639-1/>. @prefix s: <${this.FDP_URL}/>. @prefix c: <${this.FDP_URL}/dataset/>. - + s:new a dcat:Distribution, dcat:Resource; dct:isPartOf c:${datasetId}; @@ -689,12 +730,12 @@ export class MappingComponent implements OnInit { @prefix adms: <http://www.w3.org/ns/adms#> . @prefix dqv: <http://www.w3.org/ns/dqv#> . @prefix geodcat: <http://data.europa.eu/930/>. - @prefix prov: <http://www.w3.org/ns/prov#>. - @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>. + @prefix prov: <http://www.w3.org/ns/prov#>. + @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>. @prefix language: <http://id.loc.gov/vocabulary/iso639-1/>. @prefix s: <${this.FDP_URL}/>. @prefix c: <${this.FDP_URL}/catalog/>. - + s:new a dcat:Dataset, dcat:Resource; dct:isPartOf c:${this.catalogId} ; diff --git a/src/app/mapping/service/mapping.service.ts b/src/app/mapping/service/mapping.service.ts index 3f95a4e70f22d57e33ca2f9e95c5ae85568ee6a1..e3097fe8486812b4129ea23db370e8119ca68603 100644 --- a/src/app/mapping/service/mapping.service.ts +++ b/src/app/mapping/service/mapping.service.ts @@ -3,8 +3,10 @@ import { Injectable } from '@angular/core'; import { TokenStorageService } from 'src/app/authentication/services/token-storage.service'; import { SmartHarvesterUser } from 'src/app/user/model/user'; import { environment } from 'src/environments/environment'; -import { DatasetPath } from '../class/dataset'; +import {DatasetPath, KeywordResponse} from '../class/dataset'; import { RequestMapping } from '../class/requestMapping'; +import {Observable} from 'rxjs'; +import {ConceptsRequest} from '../../semantic-enrichment/ConceptsRequest'; @Injectable({ @@ -16,15 +18,11 @@ export class MappingService { smartHarvesterUrl = environment.smartharvesterUrl; fdpUrl = environment.fdpUrl; fds2Token: string = this.storageService.getToken(); - fdpToken: string = this.storageService.getFDPToken() + fdpToken: string = this.storageService.getFDPToken(); constructor(private storageService: TokenStorageService, private http: HttpClient) { } - postFile(objArray: DatasetPath[], catalogId: String) { - - - - + postFile(objArray: DatasetPath[], catalogId: string) { const body: Blob = new Blob(['\ufeff' + this.convertToTSV(objArray, [ 'subject_label', @@ -40,20 +38,17 @@ export class MappingService { data.append('file', body); const req = new HttpRequest('POST', `${this.smartHarvesterUrl}/harvester/api/storeMapping/upload/${catalogId}`, data, { headers: new HttpHeaders({ - - 'Authorization': 'Bearer ' + this.fds2Token - }), - reportProgress: true, - responseType: 'json' - }); - - return this.http.request(req); + + Authorization: 'Bearer ' + this.fds2Token + }), reportProgress: true, responseType: 'json'}); + + return this.http.request(req); } downloadFile(data: DatasetPath[], filename = 'dcatMapping') { - let tsvData = this.convertToTSV(data, [ + const tsvData = this.convertToTSV(data, [ 'subject_label', 'predicate_id', 'object_id', @@ -61,12 +56,12 @@ export class MappingService { 'object_category' ]); - let blob = new Blob(['\ufeff' + tsvData], { + const blob = new Blob(['\ufeff' + tsvData], { type: 'text/tsv;charset=utf-8;', }); - let dwldLink = document.createElement('a'); - let url = URL.createObjectURL(blob); - let safariBrowser = navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1; + const dwldLink = document.createElement('a'); + const url = URL.createObjectURL(blob); + const safariBrowser = navigator.userAgent.indexOf('Safari') !== -1 && navigator.userAgent.indexOf('Chrome') === -1; if (safariBrowser) { dwldLink.setAttribute('target', '_blank'); } @@ -79,13 +74,14 @@ export class MappingService { } convertToTSV(objArray: DatasetPath[], headerList: string[]): string { - let date = new Date(); - let user: SmartHarvesterUser = this.storageService.getUser(); - let array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray; - let str = `#mapping_date: ${date.getDate()}/${date.getMonth()}/${date.getFullYear()}\n#creator_label: ${user.firstName} ${user.lastName}\n#curie_map:\n# dcat: "http://www.w3.org/ns/dcat#"\n# dct: "http://purl.org/dc/terms/"\n# adms: "http://www.w3.org/ns/adms#\n# dqv: "http://www.w3.org/ns/dqv#"\n# geodcat: "http://data.europa.eu/930/"\n# prov: "http://www.w3.org/ns/prov#"\n# rdfs: "http://www.w3.org/2000/01/rdf-schema#"\n`; + const date = new Date(); + const user: SmartHarvesterUser = this.storageService.getUser(); + const array = typeof objArray !== 'object' ? JSON.parse(objArray) : objArray; + let str = + `#mapping_date: ${date.getDate()}/${date.getMonth()}/${date.getFullYear()}\n#creator_label: ${user.firstName} ${user.lastName}\n#curie_map:\n# dcat: "http://www.w3.org/ns/dcat#"\n# dct: "http://purl.org/dc/terms/"\n# adms: "http://www.w3.org/ns/adms#\n# dqv: "http://www.w3.org/ns/dqv#"\n# geodcat: "http://data.europa.eu/930/"\n# prov: "http://www.w3.org/ns/prov#"\n# rdfs: "http://www.w3.org/2000/01/rdf-schema#"\n`; let row = ''; - for (let index in headerList) { + for (const index in headerList) { row += headerList[index] + '\t'; } row = row.slice(0, -1); @@ -94,8 +90,8 @@ export class MappingService { if (array[i].path) { let line = ''; - for (let index in headerList) { - let head = headerList[index]; + for (const index in headerList) { + const head = headerList[index]; switch (head) { case 'subject_label': line += array[i].path + '\t'; @@ -113,7 +109,7 @@ export class MappingService { line += array[i].dcatClass + '\t'; } } - str += (i != array.length - 1) ? line + '\n' : line; + str += (i !== array.length - 1) ? line + '\n' : line; } @@ -123,29 +119,29 @@ export class MappingService { } tsvToJson(tsv: string): Object { - let lines: string[] = tsv.split('\n'); - let result = []; - console.table(lines) + const lines: string[] = tsv.split('\n'); + const result = []; + console.table(lines); while (lines[0].startsWith('#')) { lines.shift(); } - console.table(lines) - let headers = [ + console.table(lines); + const headers = [ 'subject_label', 'predicate_id', 'object_id', 'match_type', 'object_category' - ] + ]; - lines.shift() + lines.shift(); for (let i = 0; i < lines.length; i++) { - let obj = {}; - let currentLine = lines[i].split('\t'); + const obj = {}; + const currentLine = lines[i].split('\t'); - for (var j = 0; j < headers.length; j++) { + for (let j = 0; j < headers.length; j++) { obj[headers[j]] = currentLine[j]; } @@ -155,14 +151,16 @@ export class MappingService { return result; } - async postToFdpFropSmartharvester(catalogId: string, data: RequestMapping, isJsonpath: boolean): Promise<any> { - - if(this.fds2Token) { + async postToFdpFropSmartharvester(catalogId: string, data: ConceptsRequest, isJsonpath: boolean): Promise<any> { + + + if (this.fds2Token) { const httpOptions = new Headers(); httpOptions.append('Content-Type', 'application/json'); httpOptions.append('Accept', 'application/json'); - httpOptions.append('Authorization', `Bearer ${this.fds2Token}`) - let url = `${this.smartHarvesterUrl}/harvester/api/transform/publish?fdpUrl=${this.fdpUrl}&catalogId=${catalogId}&isJsonpath=${isJsonpath}`; + httpOptions.append('Authorization', `Bearer ${this.fds2Token}`); + const url = + `${this.smartHarvesterUrl}/harvester/api/mapping/publish?fdpUrl=${this.fdpUrl}&catalogId=${catalogId}&isJsonpath=${isJsonpath}`; const myInit = { method: 'POST', body: JSON.stringify(data), headers: httpOptions}; const myRequest = new Request(url, myInit); @@ -177,12 +175,14 @@ export class MappingService { const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Authorization': 'Bearer ' + this.fdpToken + Accept: 'application/json', + Authorization: 'Bearer ' + this.fdpToken }) }; return this.http.put(`${url}/meta/state`, { current: 'PUBLISHED' }, httpOptions).toPromise(); } } + + } diff --git a/src/app/publishapi/class/keywordRequest.ts b/src/app/publishapi/class/keywordRequest.ts new file mode 100644 index 0000000000000000000000000000000000000000..1d6d00608010c39df4b7952c93a94ddf31519637 --- /dev/null +++ b/src/app/publishapi/class/keywordRequest.ts @@ -0,0 +1,9 @@ +export class KeywordRequest { + isJsonPAth: boolean; + keywordPaths: string []; + + constructor(isJsonPath: boolean, keywordPaths: string []) { + this.isJsonPAth = isJsonPath; + this.keywordPaths = keywordPaths; + } +} diff --git a/src/app/publishapi/publishapi.component.html b/src/app/publishapi/publishapi.component.html index d11d5b4863840d0e9fe0580a63e7670a747a9318..e4286f892f82e10060a6783aec6888362869782d 100644 --- a/src/app/publishapi/publishapi.component.html +++ b/src/app/publishapi/publishapi.component.html @@ -4,7 +4,7 @@ <nb-card> <nb-card-body> - <nb-stepper #stepper orientation="horizontal" disableStepNavigation> + <nb-stepper #stepper orientation="horizontal" disableStepNavigation > <nb-step [label]="labelOne"> <ng-template #labelOne>First step</ng-template> <h4>Describe API</h4> @@ -315,21 +315,20 @@ <p class="lorem"> Map your metadata schema with DCAT schema </p> - <app-mapping *ngIf="initLabelThree && datasets.ready" [catalogId]="openApi.info['x-catalog-id']" [type]="openApi.info['x-format']"> + <app-mapping *ngIf="initLabelThree && datasets.ready" [catalogId]="openApi.info['x-catalog-id']" + [type]="openApi.info['x-format']" (dataEvent)="dataEventHandler($event)" (isJsonPathEvent)="isJsonPathEventHandler($event)"> </app-mapping> <button class="prev-button" nbButton nbStepperPrevious (click)="initLabelThree=false; resetDataset(); datasets.ready = false">prev</button> - <button class="next-button" nbButton disabled nbStepperNext>next</button> + <button class="next-button" nbButton nbStepperNext (click)="initLabelFour = true; mappingComponent.getKeywords(); ">next</button> </nb-step> - <!--<nb-step [label]="labelFour"> + <nb-step [label]="labelFour" > <ng-template #labelFour>Fourth step</ng-template> - <h4>Populate FDP</h4> - <p class="lorem"> - Populate FDP with datasets metadata - </p> - <button class="prev-button" nbButton nbStepperPrevious>prev</button> + <h4>Semantic Enrichment</h4> + <app-semantic-enrichment *ngIf="initLabelFour" [data]="data" [isJsonPath]="isJsonPath" [catalogId]="openApi.info['x-catalog-id']"></app-semantic-enrichment> + <button class="prev-button" nbButton nbStepperPrevious (click)="initLabelFour = false">prev</button> <button class="next-button" nbButton disabled nbStepperNext>next</button> - </nb-step>--> + </nb-step> </nb-stepper> </nb-card-body> </nb-card> diff --git a/src/app/publishapi/publishapi.component.ts b/src/app/publishapi/publishapi.component.ts index 5caf7af56dcfe26be67b6c29566db137bd577208..fc157135f8ce84bdd8d11aee7bf740548209337e 100644 --- a/src/app/publishapi/publishapi.component.ts +++ b/src/app/publishapi/publishapi.component.ts @@ -5,11 +5,16 @@ import { cloneDeep } from 'lodash'; import { DatasetCrudService } from '../datasets/services/dataset-crud.service'; import { CatalogService, FdpApiResponseItem } from '../services/catalog.service'; import HttpStatusCode from './class/http-enum'; -import { OpenApi, Parameter, Path, Request, Response } from './class/openapi'; +import {OpenApi, Parameter, Path, Request, Response} from './class/openapi'; import { OpenApiTag } from './class/openapi-dto'; import { ParameterType, ShemaType } from './class/openapi-enum'; import { OpenApiDTOMappingService } from './services/openapi-dto-mapping-service'; import { OpenApiService } from './services/openapi-service'; +import { RequestMapping} from '../mapping/class/requestMapping' ; +import {MappingComponent} from '../mapping/mapping.component'; +import {KeywordResponse} from '../mapping/class/dataset'; +import {Observable} from 'rxjs'; +import {SemanticEnrichmentComponent} from '../semantic-enrichment/semantic-enrichment.component'; @Component({ selector: 'app-publishapi', @@ -22,6 +27,14 @@ export class PublishApiComponent implements OnInit { @ViewChild('catalogSelect') catalogSelect; @ViewChild('datasets') datasets; @ViewChild('stepper') stepper: NbStepperComponent; + @ViewChild(MappingComponent) + private mappingComponent: MappingComponent; + + @ViewChild(SemanticEnrichmentComponent) + private semanticComponent: SemanticEnrichmentComponent; + + data: RequestMapping; + isJsonPath: boolean; constructor( private openApiService: OpenApiService, @@ -34,21 +47,40 @@ export class PublishApiComponent implements OnInit { parametersTypes: string[]; shemaTypes: string[]; httpStatusCodes: string[]; - catalogList: { title: string, catId: string, server: string, type: string, version?: string, description?: string, result?: string}[] = []; + catalogList: { + title: string, + catId: string, + server: string, + type: string, + version?: string, + description?: string, + result?: string + }[] = []; canNext = false; type: string; initLabelThree = false; + initLabelFour = false; ngOnInit(): void { this.openApi = new OpenApi(null, null); this.parametersTypes = Object.keys(ParameterType); this.shemaTypes = Object.keys(ShemaType); - const httpStatusCodeskeys = Object.keys(HttpStatusCode).filter(k => typeof HttpStatusCode[k as any] === "number"); + const httpStatusCodeskeys = Object.keys(HttpStatusCode).filter(k => typeof HttpStatusCode[k as any] === 'number'); this.httpStatusCodes = httpStatusCodeskeys.map(k => HttpStatusCode[k as any]); this.initCatalogList(); this.datasetService.resetDataset(); } + dataEventHandler($event: RequestMapping) { + this.data = $event; + } + + isJsonPathEventHandler($event: boolean) { + this.isJsonPath = $event; + } + + + getPrettyJson(): string { return this.openApiService.getPrettyJson(this.openApi); } @@ -59,7 +91,7 @@ export class PublishApiComponent implements OnInit { const titlePromises: Promise<any>[] = []; response.forEach((catalog) => { titlePromises.push( - + this.catalogService.getCatalogContentById(catalog.catId).toPromise().then( (response2) => { this.catalogList.push({ @@ -107,9 +139,9 @@ export class PublishApiComponent implements OnInit { error: () => { this.openApi = new OpenApi(catId, this.getServer(catId)); this.openApi.info['x-format'] = this.getType(catId); - this.openApi.info['title'] = this.getTitle(catId); - this.openApi.info['version'] = this.getVersion(catId); - this.openApi.info['description'] = this.getDescription(catId); + this.openApi.info.title = this.getTitle(catId); + this.openApi.info.version = this.getVersion(catId); + this.openApi.info.description = this.getDescription(catId); this.openApi.info['x-result'] = this.getResult(catId); this.openApi.paths.push(this.openApiDTOMappingService.getEmptyPath(OpenApiTag.search)); this.openApi.paths.push(this.openApiDTOMappingService.getEmptyPath(OpenApiTag.dataset)); @@ -176,11 +208,11 @@ export class PublishApiComponent implements OnInit { } } return null; - } + } getType(catId: string): string { for (const catalog of this.catalogList) { - if(catalog.catId === catId) { + if (catalog.catId === catId) { return catalog.type; } } @@ -189,7 +221,7 @@ export class PublishApiComponent implements OnInit { getTitle(catId: string): string { for (const catalog of this.catalogList) { - if(catalog.catId === catId) { + if (catalog.catId === catId) { return catalog.title; } } @@ -198,7 +230,7 @@ export class PublishApiComponent implements OnInit { getVersion(catId: string): string { for (const catalog of this.catalogList) { - if(catalog.catId === catId) { + if (catalog.catId === catId) { return catalog.version; } } @@ -207,7 +239,7 @@ export class PublishApiComponent implements OnInit { getDescription(catId: string): string { for (const catalog of this.catalogList) { - if(catalog.catId === catId) { + if (catalog.catId === catId) { return catalog.description; } } @@ -216,8 +248,8 @@ export class PublishApiComponent implements OnInit { getResult(catId: string): string { for (const catalog of this.catalogList) { - if(catalog.catId === catId) { - return (catalog.type === "ISO 19115") ? 'xml' : 'json'; + if (catalog.catId === catId) { + return (catalog.type === 'ISO 19115') ? 'xml' : 'json'; } } return null; @@ -228,15 +260,15 @@ export class PublishApiComponent implements OnInit { } selectAllJson(): void { - var range = document.createRange(); + const range = document.createRange(); range.selectNodeContents(document.getElementById('json')); - var sel = window.getSelection(); + const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } downloadJson(): void { - let windowVar: any = window.navigator; + const windowVar: any = window.navigator; const newBlob = new Blob([this.openApiService.getPrettyJson(this.openApi)], { type: 'application/json' }); if (windowVar && windowVar.msSaveOrOpenBlob) { windowVar.msSaveOrOpenBlob(newBlob); @@ -284,7 +316,7 @@ export class PublishApiComponent implements OnInit { } addParameter(request: Request): void { - let emptyParameter: Parameter = this.openApiDTOMappingService.getEmptyParameter(false, ''); + const emptyParameter: Parameter = this.openApiDTOMappingService.getEmptyParameter(false, ''); request.parameters.unshift(emptyParameter); } @@ -302,7 +334,7 @@ export class PublishApiComponent implements OnInit { } addResponse(request: Request): void { - let emptyResponse: Response = this.openApiDTOMappingService.getEmptyResponse(); + const emptyResponse: Response = this.openApiDTOMappingService.getEmptyResponse(); request.responses.unshift(emptyResponse); } @@ -320,7 +352,7 @@ export class PublishApiComponent implements OnInit { importJson(event: Event): void { const files: FileList = (event.target as HTMLInputElement).files; const jsonFile: File = files[0]; - var reader = new FileReader(); + const reader = new FileReader(); reader.onload = () => { console.log(reader.result as string); @@ -330,14 +362,14 @@ export class PublishApiComponent implements OnInit { this.openApi = this.openApiService.getFromString(reader.result as string); this.openApi.cleanServers(servers[0].url); this.openApi.info['x-catalog-id'] = catId; - + }; reader.readAsText(jsonFile); } goToLink() { - window.open("/swaggerapi"); + window.open('/swaggerapi'); } duplicatePath(path: Path) { @@ -363,5 +395,10 @@ export class PublishApiComponent implements OnInit { openDatasetsSelectDialog() { this.datasets.openDatasetsSelectDialog(this.stepper); } + + /*getKeywords() { + + }*/ + } diff --git a/src/app/publishapi/services/openapi-dto-mapping-service.ts b/src/app/publishapi/services/openapi-dto-mapping-service.ts index 29d4d04b401a3c55ff56a5ab7315e1fa21693ef2..7df332e9f627dc9f07f00c9c1e3eb1e57db6c1bc 100644 --- a/src/app/publishapi/services/openapi-dto-mapping-service.ts +++ b/src/app/publishapi/services/openapi-dto-mapping-service.ts @@ -13,21 +13,21 @@ export class OpenApiDTOMappingService { constructor() { } mapContentFromDTO(mimeTypeToContentMapDTO: MimeTypeToContentMapDTO, contentType: string) { - let contentDTO: ContentDTO = mimeTypeToContentMapDTO[contentType]; - let content: Content = {} as Content; + const contentDTO: ContentDTO = mimeTypeToContentMapDTO[contentType]; + const content: Content = {} as Content; content.contentType = contentType; content.example = contentDTO.example; return content; } mapResponseFromDTO(httpCodeToResponseMapDTO: HttpCodeToResponseMapDTO, httpStatusCodeToreponseDTOMap: string): Response { - let responseDTO: ResponseDTO = httpCodeToResponseMapDTO[httpStatusCodeToreponseDTOMap]; - let response: Response = {} as Response; + const responseDTO: ResponseDTO = httpCodeToResponseMapDTO[httpStatusCodeToreponseDTOMap]; + const response: Response = {} as Response; response.description = responseDTO.description; response.httpStatusCode = httpStatusCodeToreponseDTOMap as unknown as HttpStatusCode; response.contents = [] as Content[]; Object.keys(responseDTO.content).forEach((contentType) => { - let content: Content = this.mapContentFromDTO(responseDTO.content, contentType); + const content: Content = this.mapContentFromDTO(responseDTO.content, contentType); response.contents.push(content); }); return response; @@ -38,13 +38,13 @@ export class OpenApiDTOMappingService { } mapRequestFromDTO(methodToRequestMapDTO: MethodToRequestMapDTO, httpMethod: HttpMethod): Request { - let requestDTO: RequestDTO = methodToRequestMapDTO[httpMethod]; + const requestDTO: RequestDTO = methodToRequestMapDTO[httpMethod]; if (requestDTO == null) { return this.getEmptyRequest(OpenApiTag.other); } - let request: Request = {} as Request; + const request: Request = {} as Request; request.httpmethod = httpMethod; request.title = requestDTO.title; request.summary = requestDTO.summary; @@ -53,32 +53,32 @@ export class OpenApiDTOMappingService { request.responses = [] as Response[]; request.toggled = false; Object.keys(requestDTO.responses).forEach((HttpStatusCode) => { - let response: Response = this.mapResponseFromDTO(requestDTO.responses, HttpStatusCode); + const response: Response = this.mapResponseFromDTO(requestDTO.responses, HttpStatusCode); request.responses.push(response); }); return request; } mapPathFromDTO(pathMapDTO: PathMapDTO, pathName: string): Path { - let methodToRequestMapDTO: MethodToRequestMapDTO = pathMapDTO[pathName]; - let path: Path = {} as Path; + const methodToRequestMapDTO: MethodToRequestMapDTO = pathMapDTO[pathName]; + const path: Path = {} as Path; path.pathName = pathName.trim(); path.requests = [] as Request[]; Object.keys(methodToRequestMapDTO).forEach((httpMethod: HttpMethod) => { - let request: Request = this.mapRequestFromDTO(methodToRequestMapDTO, httpMethod); + const request: Request = this.mapRequestFromDTO(methodToRequestMapDTO, httpMethod); path.requests.push(request); }); return path; } mapOpenApiFromDTO(openApiDTO: OpenApiDTO): OpenApi { - const serverUrl = (openApiDTO.servers.length > 0) ? openApiDTO.servers[0].url : ''; - let openApi: OpenApi = new OpenApi(openApiDTO.info['x-catalog-id'], serverUrl); + const serverUrl = (openApiDTO.servers.length > 0) ? openApiDTO.servers[0].url : ''; + const openApi: OpenApi = new OpenApi(openApiDTO.info['x-catalog-id'], serverUrl); openApi.info = openApiDTO.info as InfoMap; openApi.openapi = openApiDTO.openapi; openApi.paths = [] as Path[]; Object.keys(openApiDTO.paths).forEach((pathName) => { - let path: Path = this.mapPathFromDTO(openApiDTO.paths, pathName); + const path: Path = this.mapPathFromDTO(openApiDTO.paths, pathName); openApi.paths.push(path); }); return openApi; @@ -88,7 +88,7 @@ export class OpenApiDTOMappingService { * @param openApi */ mapToDto(openApi: OpenApi): OpenApiDTO { - let openApiDTO: OpenApiDTO = {} as OpenApiDTO; + const openApiDTO: OpenApiDTO = {} as OpenApiDTO; openApiDTO.openapi = openApi.openapi; openApiDTO.info = openApi.info as InfoMapDTO; openApiDTO.servers = openApi.servers as ServerDTO[]; @@ -102,9 +102,10 @@ export class OpenApiDTOMappingService { openApiDTO.paths[path.pathName][request.httpmethod].parameters = request.parameters as ParameterDTO[]; openApiDTO.paths[path.pathName][request.httpmethod].responses = {}; request.responses.forEach(response => { - openApiDTO.paths[path.pathName][request.httpmethod].responses[response.httpStatusCode.toString()] = {} + openApiDTO.paths[path.pathName][request.httpmethod].responses[response.httpStatusCode.toString()] = {}; openApiDTO.paths[path.pathName][request.httpmethod].responses[response.httpStatusCode.toString()].content = {}; - openApiDTO.paths[path.pathName][request.httpmethod].responses[response.httpStatusCode.toString()].description = response.description; + openApiDTO.paths[path.pathName][request.httpmethod] + .responses[response.httpStatusCode.toString()].description = response.description; response.contents.forEach(content => { if (openApiDTO.paths[path.pathName][request.httpmethod].responses[response.httpStatusCode.toString()].content[content.contentType] == null) { openApiDTO.paths[path.pathName][request.httpmethod].responses[response.httpStatusCode.toString()].content[content.contentType] = {}; diff --git a/src/app/repository/services/publish-repository.service.ts b/src/app/repository/services/publish-repository.service.ts index 8e47736d5167c25ebfb2ea1b6cce9dfd6bdaca24..19fd94d6403992071644deaefbe4d35dcf1b506d 100644 --- a/src/app/repository/services/publish-repository.service.ts +++ b/src/app/repository/services/publish-repository.service.ts @@ -11,7 +11,7 @@ interface PersistentUrlResponse { persistentUrl: string; } -const FDP_URL = environment.fdpUrl; +let FDP_URL = environment.fdpUrl; @Injectable({ providedIn: 'root' }) @@ -26,7 +26,7 @@ export class PublishRepositoryService { ) { } get smartApiUrl(): string { - return environment.smartharvesterUrl + '/harvester/api' + return environment.smartharvesterUrl + '/harvester/api'; } async publishRepository(data: string) { @@ -39,12 +39,15 @@ export class PublishRepositoryService { myHeaders.append('Authorization', 'Bearer ' + this.fds2Token); const myInit = { method: 'POST', body: data, headers: myHeaders }; - const myRequest = new Request(FDP_URL + "/catalog", myInit); + if (!environment.staging && !environment.production) { + FDP_URL = FDP_URL.replace(':8080', ''); + } + const myRequest = new Request(FDP_URL + '/catalog', myInit); const response = await fetch(myRequest, myInit); - const id = await response.headers.get("location"); - const url = id - const catId = id.substring(id.search("catalog/") + 8, id.length); + const id = await response.headers.get('location'); + const url = id; + const catId = id.substring(id.search('catalog/') + 8, id.length); console.log('catalog creaetd with fdp / id = ' + catId); this.publishMetadata(url); return this.addUserCatalog(catId); @@ -58,17 +61,23 @@ export class PublishRepositoryService { const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Authorization': 'Bearer ' + this.fds2Token + Accept: 'application/json', + Authorization: 'Bearer ' + this.fds2Token }) }; - + if (!environment.staging && !environment.production) { + url = url.replace(':8080', ''); + } return this.http.put(`${url}/meta/state`, { current: 'PUBLISHED' }, httpOptions).toPromise(); } } getPersistentUrl(): Observable<PersistentUrlResponse> { - return this.http.get<PersistentUrlResponse>(environment.fdpUrl + '/configs/bootstrap'); + let url = environment.fdpUrl; + if (!environment.staging && !environment.production) { + url = url.replace(':8080', ''); + } + return this.http.get<PersistentUrlResponse>(url + '/configs/bootstrap'); } async addUserCatalog(catId: string): Promise<any> { diff --git a/src/app/search/search.component.ts b/src/app/search/search.component.ts index 3f21a45cb702910a908d62950eec175fabb40b9a..b2cd5025b755ec12376f6ed4eda58c894378420a 100644 --- a/src/app/search/search.component.ts +++ b/src/app/search/search.component.ts @@ -1,11 +1,10 @@ -import { Component, OnInit } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { ParseXmlService } from '../services/parse-xml.service'; -import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; -import { environment } from 'src/environments/environment'; +import {Component, OnInit} from '@angular/core'; +import {ParseXmlService} from '../services/parse-xml.service'; +import {FormBuilder} from '@angular/forms'; +import {environment} from 'src/environments/environment'; -export interface formData{ +export interface formData { [key: string]: string; } @@ -17,60 +16,60 @@ export interface formData{ export class SearchComponent implements OnInit { - public results: string[] = []; - firstTime = true; - searchForm; - searchedTerm; - - constructor(private parserService: ParseXmlService, private formBuilder: FormBuilder) { this.searchForm = this.formBuilder.group({ inputSearchTerm: '', }); - } - - ngOnInit(): void { } + + public results: string[] = []; + firstTime = true; + searchForm; + searchedTerm; + loading = false; + ngOnInit(): void { + } + hasResults() { this.loading = this.results.length > 0; setTimeout(() => this.loading = false, 2000); } - simpleSearch(formData){ + simpleSearch(formData) { this.firstTime = false; - //curl -X POST -H 'Accept: application/rdf+xml' -i 'http://10.6.10.9:8888/blazegraph/sparql' --data 'query=SELECT * where {?s a <http://www.w3.org/ns/dcat#Dataset> }' - let term = formData["inputSearchTerm"] + const term = formData.inputSearchTerm; this.searchedTerm = term; - let query='query=PREFIX dcat: <http://www.w3.org/ns/dcat#>\n\ - PREFIX dcterms: <http://purl.org/dc/terms/>\n\ - SELECT ?title ?description ?keyword ?uri ?dataset \n\ - where {\n\?dataset a dcat:Dataset ;\n\ - dcterms:title ?title ;\n\ - dcterms:description ?description; \n\ - dcterms:isPartOf* <'+ environment.fdpUrl +'>;\n\ - dcat:keyword ?keyword ; \n\ - FILTER (contains( ?description, "' + - term +'") || contains( ?title, "'+ term +'") || contains( ?keyword, "'+ term +'"))\n\.\n\ - }' - + const query = 'PREFIX dcat: <http://www.w3.org/ns/dcat#> ' + + 'PREFIX dcterms: <http://purl.org/dc/terms/> ' + + 'SELECT DISTINCT ?title ?description ?keyword ?uri ?dataset ' + + 'WHERE {?dataset a dcat:Dataset ; ' + + 'dcterms:title ?title ; ' + + 'dcterms:description ?description; ' + + 'dcterms:isPartOf* <' + environment.fdpUrl + '>; ' + + 'dcat:keyword ?keyword ; ' + + 'FILTER (contains( lcase(str(?description)), "' + + term.toLowerCase() + '") || contains( lcase(str(?title)), "' + term.toLowerCase() + '") ||' + + ' contains( lcase(str(?keyword)), "' + term.toLowerCase() + '")). ' + + '} ' ; + this.parserService.getXmlResult(query).subscribe( - data=>{ - if (data){ + data => { + if (data) { this.results = []; data.results.bindings.forEach(element => { this.results.push(element); }); } - }) - this.searchForm.reset(); + }); + this.searchForm.reset(); } - isXmlItemsEmpty(){ + isXmlItemsEmpty() { return this.results.length === 0; - } + } } diff --git a/src/app/semantic-enrichment/ConceptsRequest.ts b/src/app/semantic-enrichment/ConceptsRequest.ts new file mode 100644 index 0000000000000000000000000000000000000000..1e8d7c65a6dee49a82f3625391b4eeedcd442cc2 --- /dev/null +++ b/src/app/semantic-enrichment/ConceptsRequest.ts @@ -0,0 +1,13 @@ +import {Path} from '../mapping/class/requestMapping'; + +export class ConceptsRequest { + dataConcepts: DataConcept[]; + paths: Path[]; + fdpToken: string; +} + +export class DataConcept { + iris: string[]; + id: string; + url: string; +} diff --git a/src/app/semantic-enrichment/ESModel.ts b/src/app/semantic-enrichment/ESModel.ts new file mode 100644 index 0000000000000000000000000000000000000000..114fa739a4c8cd99f71313b1d8d440d28d608fe2 --- /dev/null +++ b/src/app/semantic-enrichment/ESModel.ts @@ -0,0 +1,42 @@ +export class ESModel { + public search1: string; + public search2: string; + public count: number; + public best: number; + public results: Result[]; + +} + +export class Result { + public checked: boolean; + public score: number; + public source: Source; + + constructor() { + this.source = new Source(); + } + + +} + +export class Source { + public document: Document; + public timestamp: Date; + constructor() { + this.document = new Document(); + } +} + +export class Document { + public iri: string; + public synonyms: string[]; + public description: string; + public label: string; + public resourceIri: string; + public resourceAcronym: string; + public resourceReusingAcronyms: string[]; + public resourceDate: Date; + public resourceName: string; + public resourceVersion: string; + public domains: string[]; +} diff --git a/src/app/semantic-enrichment/pipes/order-by-score.pipe.ts b/src/app/semantic-enrichment/pipes/order-by-score.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..5bae7b4d3ef2fdeb702aec58a771abfbc6b08e69 --- /dev/null +++ b/src/app/semantic-enrichment/pipes/order-by-score.pipe.ts @@ -0,0 +1,23 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'orderByScore' +}) +export class OrderByScorePipe implements PipeTransform { + + transform(value: any, args?: any): any { + return value.sort((a: any, b: any) => { + const score1 = a.score; + const score2 = b.score; + + if (score1 > score2) { + return -1; + } else if (score1 < score2) { + return 1; + } else { + return 0; + } + }); + } + +} diff --git a/src/app/semantic-enrichment/semantic-enrichment.component.html b/src/app/semantic-enrichment/semantic-enrichment.component.html new file mode 100644 index 0000000000000000000000000000000000000000..e39aa7ea0cb225a99f3caea9b234579f911721f5 --- /dev/null +++ b/src/app/semantic-enrichment/semantic-enrichment.component.html @@ -0,0 +1,152 @@ +<ng-template #dialog let-data let-ref="dialogRef"> + <nb-card> + <nb-card-body>{{ errorMess }}</nb-card-body> + <nb-card-footer> + <button nbButton (click)=" getConceptsByKeywords();ref.close()">Retry</button> + <button nbButton (click)="ref.close()">Close</button> + </nb-card-footer> + </nb-card> +</ng-template> + +<ng-template #noConcept> + <nb-card> + <nb-card-body> + <h5>No concepts found...</h5> + </nb-card-body> + </nb-card> +</ng-template> + +<mat-autocomplete #auto="matAutocomplete" [displayWith]="viewHandle" (optionSelected)="setIri($event)"> + <mat-option + *ngFor="let option of filteredOptions | async" + [value]="option" + [matTooltip]="option.source.document.description ? option.source.document.description: 'no definition found'"> + {{option.source.document.label}} + </mat-option> +</mat-autocomplete> + + +<ng-template #noKeywordsFound> + <nb-card> + <nb-card-body> + <h3>No keywords found...</h3> + </nb-card-body> + </nb-card> +</ng-template> + +<ng-template #loading xmlns="http://www.w3.org/1999/html"> + <nb-card size="small" [nbSpinner]="true" nbSpinnerStatus="primary" nbSpinnerSize="giant"></nb-card> +</ng-template> +<ng-container *ngIf="!isLoading; else loading"> + <ng-container *ngFor="let keyword of response; let i = index"> + <nb-accordion> + <nb-accordion-item> + <nb-accordion-item-header><a href="{{keyword.url}}" target="_blank">{{keyword.title}}</a> + <div> + <nb-icon icon="checkmark-circle-2-outline" + [status]="getStatus(getValues(keyword.keywords), autocompleteMap.get(keyword.id))"></nb-icon> + </div> + </nb-accordion-item-header> + + <nb-accordion-item-body> + <nb-card *ngIf="getkeys(keyword.keywords).length > 0; else noKeywordsFound" [size]="'tiny'"> + <nb-card-header> + Keyword(s) found + </nb-card-header> + <nb-list> + <nb-list-item *ngFor="let k of getkeys(keyword.keywords)"> + {{ k }} + </nb-list-item> + </nb-list> + </nb-card> + + <nb-card> + <nb-card-body> + <mat-form-field class="half-width"> + <input + type="text" + placeholder="enter a value with at least 4 characters" + [(ngModel)]="values[i]" + (ngModelChange)="onModelChange($event)" + matInput + [matAutocomplete]="auto" + (focusin)="setId(keyword.id)"> + + </mat-form-field> + <div class="mt0-m"> + <table *ngIf="autocompleteMap.get(keyword.id).length > 0"> + <thead style="background-color: #3366ff"> + <th></th> + <th class="text-center">label</th> + <th class="text-center">definition</th> + <th class="text-center">synonyms</th> + </thead> + <tbody> + <tr *ngFor="let value of autocompleteMap.get(keyword.id); let i = index "> + <td> + <button nbButton ghost> + <nb-icon icon="trash-2-outline" status="danger" + (click)="deleteProperty(keyword.id, i)"> + </nb-icon> + </button> + </td> + <td class="text-center"><a href="{{value.source.document.iri}}" + target="_blank">{{ value.source.document.label }}</a></td> + <td class="text-center">{{ value.source.document.description }}</td> + <td class="text-center">{{ value.source.document.synonyms }}</td> + + </tr> + </tbody> + </table> + </div> + </nb-card-body> + </nb-card> + <nb-card [size]="'small'" *ngIf="getValues(keyword.keywords)"> + <nb-card-body> + <nb-tabset> + <nb-tab *ngFor="let objet of keyword.keywords | keyvalue" tabTitle="{{(objet.key)}}"> + + <table *ngIf="objet.value.results; else noConcept"> + <thead style="background-color: #3366ff"> + <th></th> + <th class="text-center">label</th> + <th class="text-center">definition</th> + <th class="text-center">synonyms</th> + <th class="text-center">score</th> + </thead> + <tbody> + <tr *ngFor="let result of (objet.value).results "> + <td class="text-center"> + <nb-checkbox [(checked)]="result.checked"></nb-checkbox> + </td> + <td class="text-center"><a href="{{result.source.document.iri}}" + target="_blank">{{ result.source.document.label }}</a></td> + <td class="text-center">{{ result.source.document.description }}</td> + <td class="text-center">{{ result.source.document.synonyms }}</td> + <td class="text-center">{{ result.score | number: '2.2-2' }}</td> + </tr> + </tbody> + </table> + </nb-tab> + </nb-tabset> + </nb-card-body> + </nb-card> + </nb-accordion-item-body> + </nb-accordion-item> + </nb-accordion> + + </ng-container> + + + <div class="row"> + <div class="button-center"> + <button nbButton status="primary" (click)="onSubmit()" [nbSpinner]="loadingPublish" [disabled]="loadingPublish" + nbSpinnerStatus="basic">Publish + </button> + </div> + </div> +</ng-container> + + + + diff --git a/src/app/semantic-enrichment/semantic-enrichment.component.scss b/src/app/semantic-enrichment/semantic-enrichment.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..c4eca40f2477990820a4a05081027c4bfeded0d7 --- /dev/null +++ b/src/app/semantic-enrichment/semantic-enrichment.component.scss @@ -0,0 +1,8 @@ +.button-center{ + vertical-align: middle; + margin: auto +} + +.half-width { + width: 100%; +} diff --git a/src/app/semantic-enrichment/semantic-enrichment.component.spec.ts b/src/app/semantic-enrichment/semantic-enrichment.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..f515fc3d7eafc7a427cd8ae048230c12dcfff992 --- /dev/null +++ b/src/app/semantic-enrichment/semantic-enrichment.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SemanticEnrichmentComponent } from './semantic-enrichment.component'; + +describe('SemanticEnrichmentComponent', () => { + let component: SemanticEnrichmentComponent; + let fixture: ComponentFixture<SemanticEnrichmentComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SemanticEnrichmentComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SemanticEnrichmentComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/semantic-enrichment/semantic-enrichment.component.ts b/src/app/semantic-enrichment/semantic-enrichment.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..d5882e749d3cddf3aeed5508b3de1fe214dd18b4 --- /dev/null +++ b/src/app/semantic-enrichment/semantic-enrichment.component.ts @@ -0,0 +1,218 @@ + +import { + Component, + Input, + OnInit, + TemplateRef, + ViewChild +} from '@angular/core'; +import {Observable, of} from 'rxjs'; +import {map} from 'rxjs/operators'; +import {PostService} from './services/post.service'; +import {KeywordResponse} from '../mapping/class/dataset'; +import {ESModel, Result} from './ESModel'; +import {ConceptsRequest, DataConcept} from './ConceptsRequest'; +import {RequestMapping} from '../mapping/class/requestMapping'; +import {NbDialogService} from '@nebular/theme'; +import {MappingService} from '../mapping/service/mapping.service'; +import {FeedbackDialogComponent} from '../mapping/dialog/feedback-dialog/feedback-dialog.component'; +import {MatDialog} from '@angular/material/dialog'; +import {MatAutocompleteSelectedEvent} from '@angular/material/autocomplete'; + +@Component({ + selector: 'app-semantic-enrichment', + templateUrl: './semantic-enrichment.component.html', + styleUrls: ['./semantic-enrichment.component.scss'] +}) +export class SemanticEnrichmentComponent implements OnInit { + + @Input() + data: RequestMapping; + @Input() + isJsonPath: boolean; + + @Input() + catalogId: string; + + @ViewChild('dialog') dialog: TemplateRef<any>; + isLoading: boolean; + errorMess: string; + + response: KeywordResponse[]; + filteredOptions: Observable<Result[]>; + autocompleteMap: Map<string, Result[]> = new Map<string, Result[]>(); + values: string[] = []; + + private loadingPublish: boolean; + private id: string; + + constructor(private service: PostService, private dialogService: NbDialogService, + private mappingService: MappingService, private matDialog: MatDialog) { + + } + + + ngOnInit(): void { + this.getConceptsByKeywords(); + + } + + + + + getConceptsByKeywords() { + this.errorMess = null; + this.isLoading = true; + this.service.getKeywords(this.catalogId, this.isJsonPath, this.data).subscribe( + (response: KeywordResponse[]) => { + this.response = response; + this.response.forEach((resp: KeywordResponse) => { + this.autocompleteMap.set(resp.id, []); + if (resp.keywords) { + Object.values(resp.keywords).forEach((value: ESModel) => { + if (value.results) { + value.results.forEach((result: Result) => { + if (value.results.indexOf(result) === 0) { + result.checked = true; + } else { + result.checked = false; + } + }); + } + }); + } + }); + }, + (error) => { + this.isLoading = false; + console.log(error.message); + this.errorMess = error.message; + this.openDialog(this.dialog); + }, + () => this.isLoading = false + ); + } + + filter(val: string): Observable<any> { + if (val.length > 3) { + return this.service.getData(val) + .pipe( + map((response) => response.results )); + } + return of([]); + } + + openDialog(dialog: TemplateRef<any>) { + this.dialogService.open(dialog, { + context: { + title: 'This is a title passed to the dialog component', + }, + }); + } + + viewHandle(value: any) { + if (typeof value !== 'string' && typeof value !== 'undefined' && null !== value) { + if (value.source ) { + return value.source.document.label; + } + } + return value; + } + + onSubmit() { + const mappingData = new ConceptsRequest(); + mappingData.paths = this.data.paths; + mappingData.fdpToken = this.data.fdpToken; + mappingData.dataConcepts = []; + this.response.forEach((data: KeywordResponse) => { + if (data.keywords) { + const concepts = new DataConcept(); + concepts.id = data.id; + concepts.iris = []; + Object.values(data.keywords).forEach((value: ESModel) => { + + if (value && value.results) { + value.results.forEach((result: Result) => { + if (result.checked) { + concepts.iris.push(result.source.document.iri); + } + }); + } + if (this.autocompleteMap.get(data.id) && this.autocompleteMap.get(data.id).length > 0) { + this.autocompleteMap.get(data.id).forEach((result: Result) => concepts.iris.push(result.source.document.iri)); + } + mappingData.dataConcepts.push(concepts); + }); + } + }); + + console.log(mappingData); + const postedDatasets = []; + const notPostedDatasets = []; + this.loadingPublish = true; + const requestPromises: Promise<any>[] = []; + + const requestPromise = this.mappingService.postToFdpFropSmartharvester(this.catalogId, mappingData, this.isJsonPath).then(resp => { + if (resp) { + resp.json().then((datas: any) => { + console.log(datas.message); + datas.publishedUrl.forEach(e => postedDatasets.push(e)); + datas.notPublishedUrl.forEach(e => notPostedDatasets.push(e)); + }); + } + }); + requestPromises.push(requestPromise); + + Promise.all(requestPromises).finally(() => { + this.loadingPublish = false; + this.matDialog.open(FeedbackDialogComponent, { + data: { + postedMetadatas: postedDatasets, + notPostedMetadatas: notPostedDatasets + } + }).afterClosed().subscribe(); + + }); + } + + + onModelChange(value: string) { + Promise.resolve(null).then(() => this.filteredOptions = this.filter(value)); + } + + setId(id: string) { + this.id = id; + } + + setIri(option: MatAutocompleteSelectedEvent) { + const iris = this.autocompleteMap.get(this.id); + if (iris) { + iris.push(option.option.value); + } + this.autocompleteMap.set(this.id, iris); + console.log(this.autocompleteMap); + } + + deleteProperty(datasetKeyword: string, i: number) { + const results: Result[] = this.autocompleteMap.get(datasetKeyword); + results.splice(i, 1); + this.autocompleteMap.set(datasetKeyword, results); + } + + getStatus(concepts: ESModel[], results: Result[]) { + if (!concepts && concepts.filter(e => e.results.length > 0).length > 0 && results.length === 0) { + return 'danger'; + } + return 'success'; + } + + getValues(dataMap: Map<string, ESModel>): ESModel[] { + return Object.values(dataMap); + } + + getkeys(dataMap: Map<string, ESModel>): string[] { + return Object.keys(dataMap); + } + +} + diff --git a/src/app/semantic-enrichment/services/post.service.ts b/src/app/semantic-enrichment/services/post.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..d83ea9485f6608f03a8ead2ff005743d1848488d --- /dev/null +++ b/src/app/semantic-enrichment/services/post.service.ts @@ -0,0 +1,56 @@ +import {Injectable} from '@angular/core'; +import {HttpClient, HttpHeaders} from '@angular/common/http'; +import {Observable, of} from 'rxjs'; +import {tap} from 'rxjs/operators'; +import {ESModel} from '../ESModel'; +import {environment} from '../../../environments/environment'; +import {TokenStorageService} from '../../authentication/services/token-storage.service'; +import {RequestMapping} from '../../mapping/class/requestMapping'; +import {KeywordResponse} from '../../mapping/class/dataset'; + +@Injectable({ + providedIn: 'root' +}) +export class PostService { + smartHarvesterUrl = environment.smartharvesterUrl + '/harvester/api/es'; + fds2Token: string = this.storageService.getToken(); + + constructor(private http: HttpClient, private storageService: TokenStorageService) { } + + opt = []; + + + getData(value: string): Observable<ESModel> { + + if (this.fds2Token) { + const httpOptions = { + headers: new HttpHeaders({ + 'Content-Type': 'application/json', + Accept: 'application/json', + Authorization: 'Bearer ' + this.fds2Token + }) + }; + return this.opt.length ? + of(this.opt) : + this.http.get<ESModel>(`${this.smartHarvesterUrl}/autocomplete?search1=${value}`, httpOptions).pipe( + tap((data: any) => this.opt = data) + ); + } + } + + getKeywords(catalogId: string, isJsonPath: boolean, data: RequestMapping): Observable<KeywordResponse[]> { + if (this.fds2Token) { + const httpOptions = { + headers: new HttpHeaders({ + 'Content-Type': 'application/json', + Accept: 'application/json', + Authorization: 'Bearer ' + this.fds2Token + }) + }; + return this.http + .post<KeywordResponse[]>( + `${this.smartHarvesterUrl}/keywords?catalogId=${catalogId}&isJsonPath=${isJsonPath}`, data, httpOptions + ); + } + } +} diff --git a/src/app/services/catalog.service.ts b/src/app/services/catalog.service.ts index 2f0d632cbc7f44dfd29253ea8cf485e63bfeceb1..0dbc3578c12e9b3b7094af24b0c7a1369fa4adb7 100644 --- a/src/app/services/catalog.service.ts +++ b/src/app/services/catalog.service.ts @@ -3,6 +3,8 @@ import { Injectable } from '@angular/core'; import { environment } from 'src/environments/environment'; import { TokenStorageService } from '../authentication/services/token-storage.service'; import { SmartHarvesterUser } from '../user/model/user'; +import {map, takeUntil} from 'rxjs/operators'; +import {Observable} from 'rxjs'; export interface FdpApiResponseItem { subject: { @@ -17,7 +19,7 @@ export interface FdpApiResponseItem { context: string; } -const FDP_URL = environment.fdpUrl; +let FDP_URL = environment.fdpUrl; @Injectable({ providedIn: 'root' @@ -46,15 +48,23 @@ export class CatalogService { getAllCatalogId() { const user: SmartHarvesterUser = this.tokenService.getUser(); + return this.http.get<{ catId: string, uuid: string }[]>(this.smartApiUrl + '/user/' + user.email + '/catalogs', this.httpOptionsSmartHarvester); } getCatalogContentById(catId: string) { + if (!environment.staging && !environment.production) { + FDP_URL = FDP_URL.replace(':8080', ''); + } return this.http.get<FdpApiResponseItem[]>(FDP_URL + '/catalog/' + catId, this.httpOptionsFDP); } deleteCatalog(catId: string) { return this.http.delete(this.smartApiUrl + '/catalogs/' + catId, this.httpOptionsSmartHarvester); } + + getUser(): Observable<any> { + return this.http.get(this.smartApiUrl + '/harvester/api/username', this.httpOptionsSmartHarvester); + } } diff --git a/src/app/services/parse-xml.service.ts b/src/app/services/parse-xml.service.ts index eebcb43b3771761db9d0df509388fa1df73283eb..ba67cc411ce6cb141dac9508bf286f187df34ce0 100644 --- a/src/app/services/parse-xml.service.ts +++ b/src/app/services/parse-xml.service.ts @@ -2,25 +2,48 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { environment } from 'src/environments/environment'; +import {TokenStorageService} from '../authentication/services/token-storage.service'; +import {MetaModel} from '../stats/MetaModel'; @Injectable({ providedIn: 'root' }) export class ParseXmlService { - - blazePath = environment.fdpUrl+ "/blazegraph/sparql"; - constructor(private http:HttpClient) { } - - + + token: string = this.storageService.getToken(); + FDP_URL = environment.fdpUrl; + + constructor(private http: HttpClient, private storageService: TokenStorageService) { } + getXmlResult(body): Observable<any> { + let blazePath: string; + if (!environment.production && !environment.staging) { + blazePath = environment.fdpUrl + '/blazegraph/sparql'; + } else { + blazePath = environment.fdpUrl + '/blazegraph/sparql'; + } + const httpOptions = { - headers: new HttpHeaders({ - 'Content-Type': 'application/x-www-form-urlencoded', - 'responseType': 'application/sparql-results+xml' - }) - }; - return this.http.post(this.blazePath,body, httpOptions); + headers: new HttpHeaders({ + 'Content-Type': 'application/json', + Accept: 'application/json', + Authorization: 'Bearer ' + this.token + }) + }; + return this.http.post(`${environment.smartharvesterUrl}/harvester/api/sparql?url=${(blazePath)}`, body, httpOptions); + } + + getCatalogMetaById(catId: string) { + const httpOptionsFDP = { + headers: new HttpHeaders({ + Authorization: 'Bearer ' + this.storageService.getFDPToken() + }) + }; + if (!environment.staging && !environment.production) { + this.FDP_URL = this.FDP_URL.replace(':8080', ''); + } + return this.http.get<MetaModel>(this.FDP_URL + '/catalog/' + catId + '/meta', httpOptionsFDP); } } diff --git a/src/app/stats/MetaModel.ts b/src/app/stats/MetaModel.ts new file mode 100644 index 0000000000000000000000000000000000000000..6b1758e603cf2c28555f5a48737e2451e3df906d --- /dev/null +++ b/src/app/stats/MetaModel.ts @@ -0,0 +1,33 @@ +export class MetaModel { + member: Member; + state: State; + path: Object; +} + +export class Member { + user: User; + membership: MemberShip; +} +export class User { + uuid: string; + firstName: string; + lastName: string; + email: string; +} + +export class MemberShip { + uuid: string; + name: string; + permission: Permission[]; + allowEntities: string[]; +} +export class Permission { + mask: number; + code: string; +} + +export class State { + current: string; + children: Object; +} + diff --git a/src/app/stats/stats.component.html b/src/app/stats/stats.component.html index 77e578ad54412f2d55a710744ecdf8bd5f7b3208..7b37955b8eb1783514e03ddcaef339a9935d960c 100644 --- a/src/app/stats/stats.component.html +++ b/src/app/stats/stats.component.html @@ -10,4 +10,20 @@ </tr> </tbody> </table> -</div> \ No newline at end of file +</div> + +<div class="w3-container" style="margin-top: 10px;"> + <h4 style="text-align: center;">My Metadata</h4> + <table class="w3-table-all w3-card-4"> + <thead style="background-color: #3366ff"> + <th style="text-align: center;">catalog</th> + <th style="text-align: center;">Number of Datasets</th> + </thead> + <tbody> + <tr *ngFor="let cat of catalogList"> + <td style="text-align: center;"><a [routerLink]="['/dashboard/catalog', cat.catId]">{{cat.title}}</a></td> + <td style="text-align: center;"><strong>{{cat.count}}</strong></td> + </tr> + </tbody> + </table> +</div> diff --git a/src/app/stats/stats.component.ts b/src/app/stats/stats.component.ts index 0b27976113fa4b1d82fc507173b1f657cbbeb6c2..7e1e85be2de4d8e6a9cd0b21ee6753a26c13a3d9 100644 --- a/src/app/stats/stats.component.ts +++ b/src/app/stats/stats.component.ts @@ -1,8 +1,8 @@ -import { Component, OnInit } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { ParseXmlService } from '../services/parse-xml.service'; -import { environment } from 'src/environments/environment'; - +import {Component, OnInit} from '@angular/core'; +import {ParseXmlService} from '../services/parse-xml.service'; +import {environment} from 'src/environments/environment'; +import {CatalogService} from '../services/catalog.service'; +import {MetaModel} from './MetaModel'; @Component({ @@ -11,36 +11,101 @@ import { environment } from 'src/environments/environment'; styleUrls: ['./stats.component.scss'] }) export class StatsComponent implements OnInit { - - public results: string[] = []; + + public results: string[] = []; public stats: string[] = []; + catalogList: { + title: string, + catId: string, + count: number, + }[] = []; - constructor(private parserService: ParseXmlService) { } + constructor(private parserService: ParseXmlService, private catalogService: CatalogService) { } ngOnInit(): void { + const query1 = 'prefix dct: <http://purl.org/dc/terms/> ' + + 'SELECT (COUNT(?s) AS ?triples) ' + + 'WHERE { ?s a <http://www.w3.org/ns/dcat#Catalog>; ' + + 'dct:isPartOf <' + environment.fdpUrl + '>}'; + this.parserService.getXmlResult(query1) + .subscribe(data => { + if (data) { + this.results = []; + data.results.bindings.forEach(element => { + this.results.push(element); + }); + this.stats.push(this.results[0]['triples'].value); + + const query2 = 'prefix dct: <http://purl.org/dc/terms/> ' + + 'SELECT (COUNT(?s) AS ?triples) ' + + 'WHERE { ?s a <http://www.w3.org/ns/dcat#Dataset>;' + + ' dct:isPartOf* <' + environment.fdpUrl + '> }'; + this.parserService.getXmlResult(query2) + .subscribe(data => { + if (data) { + this.results = []; + data.results.bindings.forEach(element => { + this.results.push(element); + }); + this.stats.push(this.results[0]['triples'].value); + } + }); + } + }); + this.getCatalogIdByUser(); + } - let query1='query=prefix dct: <http://purl.org/dc/terms/>\n\ - SELECT (COUNT(?s) AS ?triples) \n\ - WHERE { ?s a <http://www.w3.org/ns/dcat#Catalog>;\n\ - dct:isPartOf <'+ environment.fdpUrl +'>\n\ - }' - this.parserService.getXmlResult(query1).subscribe(data=>{if (data){this.results = []; data.results.bindings.forEach(element => { this.results.push(element);}); - this.stats.push(this.results[0]["triples"].value) - - let query2='query=prefix dct: <http://purl.org/dc/terms/>\n\ - SELECT (COUNT(?s) AS ?triples) \n\ - WHERE { ?s a <http://www.w3.org/ns/dcat#Dataset>;\n\ - dct:isPartOf* <'+ environment.fdpUrl +'>\n\ - }' - this.parserService.getXmlResult(query2).subscribe(data=>{if (data){this.results = []; data.results.bindings.forEach(element => { this.results.push(element);}); - this.stats.push(this.results[0]["triples"].value); }}) - - console.log(this.stats); - }}) + getCatalogIdByUser() { + this.catalogService.getAllCatalogId().subscribe({ + next: (response) => { + const titlePromises: Promise<any>[] = []; + response.forEach((catalog) => { + titlePromises.push( + this.parserService.getCatalogMetaById(catalog.catId).toPromise().then( + (response2) => { + this.catalogList.push({ + catId: catalog.catId, + title: this.getTitleFromFdpApi(response2), + count: this.getCountDatasetByCatalog(response2) + }); + }).catch( + (error) => { + if (error.status === 404) { + this.catalogService.deleteCatalog(catalog.catId).subscribe(); + } else { + this.catalogList.push({ catId: catalog.catId, title: catalog.catId, count: null}); + } + } + ) + ); + }); + }, + error: (error) => { + if (error.staus === 404) { /* Ignore */ } + } + }); } + getTitleFromFdpApi(fdpApiResponse: MetaModel): string { + let title = ''; + if (fdpApiResponse) { + Object.values(fdpApiResponse.path).forEach(e => { + if ( e.parent !== null) { + title = e.title; + } + } + ); + } + return title; + } + getCountDatasetByCatalog(fdpApiResponse: MetaModel): number { + if (fdpApiResponse) { + return Object.values(fdpApiResponse.state.children).length; + } + return null; + } } diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 6b547d4efd3da0fe2ade884853be3b58de53d1ea..fd79f2409e5dfcb24afbc7f2451c6e3946c6d3e8 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,5 +1,6 @@ export const environment = { production: true, + staging: false, smartharvesterUrl: 'https://f2ds.eosc-pillar.eu', fdpUrl: 'https://f2ds.eosc-pillar.eu' }; diff --git a/src/environments/environment.staging.ts b/src/environments/environment.staging.ts index 1946fcc6f766811d8acde218cf3e38da8ed99720..93c94c2dfe2ba61a3f3c8af69e30a6a46ede0f43 100644 --- a/src/environments/environment.staging.ts +++ b/src/environments/environment.staging.ts @@ -1,5 +1,6 @@ export const environment = { production: false, + staging: true, smartharvesterUrl: 'https://f2ds-dev.eosc-pillar.eu', fdpUrl: 'https://f2ds-dev.eosc-pillar.eu' - }; \ No newline at end of file + }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index dce50e66ad148b5797eaa94a9ce741151d0643f2..17a2bd3453a01de97b35343fe4d807efd27dd341 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -4,8 +4,9 @@ export const environment = { production: false, + staging: false, smartharvesterUrl: 'http://localhost:8080', - fdpUrl: 'https://f2ds-dev.eosc-pillar.eu' + fdpUrl: 'http://10.6.10.97:8080' }; /* diff --git a/version b/version index 1689437620fd77429da4523c5cae0efdd540420e..53d1c14db376eadb7a2a9ac8afa1dfdb7dae8247 100644 --- a/version +++ b/version @@ -1 +1 @@ -v21 +v22