diff --git a/src/app/mapping/class/dataset.ts b/src/app/mapping/class/dataset.ts index 808875f1def9bf3adeba1b4322164bc9559c2a8b..9d939c3bbe5cdf2cb4809ac9708e05fab442b8a2 100644 --- a/src/app/mapping/class/dataset.ts +++ b/src/app/mapping/class/dataset.ts @@ -72,6 +72,7 @@ export class ResponseFileTsv { export class KeywordResponse { public id: string; public url: string; + public title: string; public keywords: string[]; public concepts: ESModel; } 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..be9d5cbb43881d448bda9893565dc6e8bc80bfaa 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,17 @@ <h3 style="text-align: center;">Feedback: </h3> </nb-card-header> <nb-card-body> - <nb-card > + <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 +27,4 @@ </div> </div> </nb-card-footer> -</nb-card> \ No newline at end of file +</nb-card> diff --git a/src/app/mapping/mapping.component.ts b/src/app/mapping/mapping.component.ts index c325049261d3776a78a219dc72bfa1b82b93e111..f996c536c3ff604e54bc3d1bda761fb98c5fe288 100644 --- a/src/app/mapping/mapping.component.ts +++ b/src/app/mapping/mapping.component.ts @@ -695,7 +695,7 @@ export class MappingComponent implements OnInit { } } - const data: RequestMapping = new RequestMapping(this.ids, paths, this.tokenStorage.getFDPToken()); + const data: RequestMapping = new RequestMapping(this.dataSetService.idsUrls, paths, this.tokenStorage.getFDPToken()); this.dataEvent.emit(data); this.isJsonPathEvent.emit(this.isJsonPath); diff --git a/src/app/search/search.component.ts b/src/app/search/search.component.ts index 3f21a45cb702910a908d62950eec175fabb40b9a..400b8535051b631318bbf3f7bad7f4c25739f9c6 100644 --- a/src/app/search/search.component.ts +++ b/src/app/search/search.component.ts @@ -5,7 +5,7 @@ import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { environment } from 'src/environments/environment'; -export interface formData{ +export interface formData { [key: string]: string; } @@ -17,60 +17,61 @@ 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\ + const 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\ + SELECT DISTINCT ?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\ + dcterms:isPartOf* <' + environment.fdpUrl + '>;\n\ dcat:keyword ?keyword ; \n\ - FILTER (contains( ?description, "' + - term +'") || contains( ?title, "'+ term +'") || contains( ?keyword, "'+ term +'"))\n\.\n\ - }' - + FILTER (contains( lcase(str(?description)), "' + + term.toLowerCase() + '") || contains( lcase(str(?title)), "' + term.toLowerCase() + '") ||' + + ' contains( lcase(str(?keyword)), "' + term.toLowerCase() + '"))\n\.\n\ + }\n\ + GROUP BY ?dataset '; + 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/ESModel.ts b/src/app/semantic-enrichment/ESModel.ts index 1ac037e3a072036025ac0c0ed21cc77b88e40a18..fa08b7b3bd6573917d16308502501edaab5cf9e6 100644 --- a/src/app/semantic-enrichment/ESModel.ts +++ b/src/app/semantic-enrichment/ESModel.ts @@ -28,6 +28,7 @@ export class Source { export class Document { public iri: string; public synonyms: string[]; + public description: string; public label: string; public resourceIri: string; public resourceAcronym: string; diff --git a/src/app/semantic-enrichment/semantic-enrichment.component.html b/src/app/semantic-enrichment/semantic-enrichment.component.html index 57934e6f50459c7a05bef1e9d721725be9296265..97e211f45ce60c4182c9eaa93bf77e843d547086 100644 --- a/src/app/semantic-enrichment/semantic-enrichment.component.html +++ b/src/app/semantic-enrichment/semantic-enrichment.component.html @@ -1,4 +1,3 @@ - <ng-template #dialog let-data let-ref="dialogRef"> <nb-card> <nb-card-body>{{ errorMess }}</nb-card-body> @@ -17,18 +16,37 @@ </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" > + <ng-container *ngFor="let keyword of response; let i = index"> <nb-accordion> <nb-accordion-item> - <nb-accordion-item-header>{{keyword.id}} - <a href="{{keyword.url}}" target="_blank">link to dataset</a></nb-accordion-item-header> + <nb-accordion-item-header ><a href="{{keyword.url}}" target="_blank">{{keyword.title}}</a> + <div><nb-icon icon="checkmark-circle-2-outline" [status]="getStatus(keyword.concepts, autocompleteMap.get(keyword.id))"></nb-icon></div> + </nb-accordion-item-header> <nb-accordion-item-body> - - <nb-card size="small"> + <nb-card *ngIf="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 keyword.keywords"> + {{ k }} + </nb-list-item> + </nb-list> + </nb-card> + <nb-card> <nb-card-body> <mat-form-field class="half-width"> <input @@ -38,52 +56,60 @@ (ngModelChange)="onModelChange($event)" matInput [matAutocomplete]="auto" - (focusin)="setId(keyword.id)"> + (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">iri</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">{{ value.source.document.label }}</td> - <td class="text-center">{{ value.source.document.iri }}</td> + <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> + </tr> + </tbody> + </table> + </div> </nb-card-body> </nb-card> - <table> - <thead style="background-color: #3366ff"> - <th></th> - <th class="text-center">label</th> - <th class="text-center">iri</th> - <th class="text-center">synonyms</th> - <th class="text-center">score</th> - </thead> - <tbody> - <tr *ngFor="let objet of keyword.concepts.results | orderByScore"> - <td class="text-center"> - <nb-checkbox [(checked)]="objet.checked"></nb-checkbox> - </td> - <td class="text-center">{{ objet.source.document.label }}</td> - <td class="text-center">{{ objet.source.document.iri }}</td> - <td class="text-center">{{ objet.source.document.synonyms }}</td> - <td class="text-center">{{ objet.score | number: '2.2-2' }}</td> - </tr> - </tbody> - </table> + <nb-card [size]="'small'" *ngIf="keyword.concepts"> + <table> + <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 objet of keyword.concepts.results | orderByScore"> + <td class="text-center"> + <nb-checkbox [(checked)]="objet.checked"></nb-checkbox> + </td> + <td class="text-center"><a href="{{objet.source.document.iri}}" + target="_blank">{{ objet.source.document.label }}</a></td> + <td class="text-center">{{ objet.source.document.description }}</td> + <td class="text-center">{{ objet.source.document.synonyms }}</td> + <td class="text-center">{{ objet.score | number: '2.2-2' }}</td> + </tr> + </tbody> + </table> + </nb-card> </nb-accordion-item-body> </nb-accordion-item> </nb-accordion> @@ -92,9 +118,10 @@ <div class="row"> - <div class="button-center" > + <div class="button-center"> <button nbButton status="primary" (click)="onSubmit()" [nbSpinner]="loadingPublish" [disabled]="loadingPublish" - nbSpinnerStatus="basic">Publish</button> + nbSpinnerStatus="basic">Publish + </button> </div> </div> </ng-container> diff --git a/src/app/semantic-enrichment/semantic-enrichment.component.ts b/src/app/semantic-enrichment/semantic-enrichment.component.ts index 4f0b86d1df5c37b0ffb6e73f3a96ec5db04f1047..49368abfd8600d180b4d8364aa3e7c070f6ab699 100644 --- a/src/app/semantic-enrichment/semantic-enrichment.component.ts +++ b/src/app/semantic-enrichment/semantic-enrichment.component.ts @@ -1,8 +1,5 @@ import { - AfterContentChecked, AfterContentInit, - AfterViewChecked, - AfterViewInit, ChangeDetectorRef, Component, Input, OnInit, @@ -10,10 +7,10 @@ import { ViewChild } from '@angular/core'; import {Observable, of} from 'rxjs'; -import { delay, map} from 'rxjs/operators'; +import {map} from 'rxjs/operators'; import {PostService} from './services/post.service'; import {KeywordResponse} from '../mapping/class/dataset'; -import { Result} from './ESModel'; +import {ESModel, Result} from './ESModel'; import {ConceptsRequest, DataConcept} from './ConceptsRequest'; import {RequestMapping} from '../mapping/class/requestMapping'; import {NbDialogService} from '@nebular/theme'; @@ -27,7 +24,7 @@ import {MatAutocompleteSelectedEvent} from '@angular/material/autocomplete'; templateUrl: './semantic-enrichment.component.html', styleUrls: ['./semantic-enrichment.component.scss'] }) -export class SemanticEnrichmentComponent implements OnInit, AfterContentChecked { +export class SemanticEnrichmentComponent implements OnInit { @Input() data: RequestMapping; @@ -50,8 +47,7 @@ export class SemanticEnrichmentComponent implements OnInit, AfterContentChecked private id: string; constructor(private service: PostService, private dialogService: NbDialogService, - private mappingService: MappingService, private matDialog: MatDialog, - private cdRef: ChangeDetectorRef) { + private mappingService: MappingService, private matDialog: MatDialog) { } @@ -61,9 +57,7 @@ export class SemanticEnrichmentComponent implements OnInit, AfterContentChecked } - ngAfterContentChecked(): void { - //this.cdRef.detectChanges(); - } + getConceptsByKeywords() { @@ -74,13 +68,15 @@ export class SemanticEnrichmentComponent implements OnInit, AfterContentChecked this.response = response; this.response.forEach((resp: KeywordResponse) => { this.autocompleteMap.set(resp.id, []); - resp.concepts.results.forEach((result: Result) => { - if (resp.concepts.results.indexOf(result) === 0) { - result.checked = true; - } else { - result.checked = false; - } - }); + if (resp.concepts) { + resp.concepts.results.forEach((result: Result) => { + if (resp.concepts.results.indexOf(result) === 0) { + result.checked = true; + } else { + result.checked = false; + } + }); + } }); }, (error) => { @@ -128,11 +124,13 @@ export class SemanticEnrichmentComponent implements OnInit, AfterContentChecked const concepts = new DataConcept(); concepts.id = data.id; concepts.iris = []; - data.concepts.results.forEach((result: Result) => { - if (result.checked) { - concepts.iris.push(result.source.document.iri); - } - }); + if (data.concepts) { + data.concepts.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)); } @@ -191,5 +189,12 @@ export class SemanticEnrichmentComponent implements OnInit, AfterContentChecked results.splice(i, 1); this.autocompleteMap.set(datasetKeyword, results); } + + getStatus(concepts: ESModel, results: Result[]) { + if (!concepts && results.length === 0) { + return 'danger'; + } + return 'success'; + } } diff --git a/src/app/services/parse-xml.service.ts b/src/app/services/parse-xml.service.ts index c2ca678f9b6eccdd73c64a0cd61e96f792d9cab9..ba67cc411ce6cb141dac9508bf286f187df34ce0 100644 --- a/src/app/services/parse-xml.service.ts +++ b/src/app/services/parse-xml.service.ts @@ -3,6 +3,7 @@ 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' @@ -11,6 +12,7 @@ export class ParseXmlService { token: string = this.storageService.getToken(); + FDP_URL = environment.fdpUrl; constructor(private http: HttpClient, private storageService: TokenStorageService) { } @@ -32,4 +34,16 @@ export class ParseXmlService { 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..5f7379a8051e8d51448862e3fb2962ddee088fe6 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;">{{cat.title}}</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 673029ca7a7bec2dfcb01b39bdc86f4bef789fcb..04a0ff8e2256a0dde66fa3f4626f4c70a392f3c2 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({ @@ -14,8 +14,13 @@ export class StatsComponent implements OnInit { 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 { @@ -29,7 +34,7 @@ export class StatsComponent implements OnInit { this.results = []; data.results.bindings.forEach(element => { this.results.push(element); }); - this.stats.push(this.results[0]['triples'].value); + this.stats.push(this.results[0]["triples"].value); const query2 = 'prefix dct: <http://purl.org/dc/terms/> ' + 'SELECT (COUNT(?s) AS ?triples) ' + @@ -42,16 +47,65 @@ export class StatsComponent implements OnInit { data.results.bindings.forEach(element => { this.results.push(element); }); - this.stats.push(this.results[0]['triples'].value); } + this.stats.push(this.results[0]["triples"].value); } }); console.log(this.stats); } }); + this.getCatalogIdByUser(); + } + + 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}); + } + } + ) + ); + }); + Promise.all(titlePromises).finally(() => { + if (this.catalogList.length > 0) { + this.catalogList.sort((a, b) => a.catId.localeCompare(b.catId)); + } + }); + }, + error: (error) => { + if (error.staus === 404) { /* Ignore */ } + } + }); } + getTitleFromFdpApi(fdpApiResponse: MetaModel): string { + if (fdpApiResponse) { + return Object.values(fdpApiResponse.path)[1]['title']; + } + return ''; + } + getCountDatasetByCatalog(fdpApiResponse: MetaModel): number { + if (fdpApiResponse) { + return Object.values(fdpApiResponse.state.children).length; + } + return null; + } }