From 392c99f4a6d112ddb9c89d2f0a013ff7ce806935 Mon Sep 17 00:00:00 2001 From: Baptiste Toulemonde <toulemonde@cines.fr> Date: Tue, 23 Aug 2022 10:38:36 +0200 Subject: [PATCH] WIP --- src/app/app-routing.module.ts | 6 +- src/app/app.module.ts | 88 ++++--- src/app/datasets/datasets.component.ts | 5 +- .../datasets/services/dataset-crud.service.ts | 8 +- src/app/mapping/class/requestMapping.ts | 8 +- src/app/mapping/mapping.component.ts | 240 +++++++++--------- src/app/publishapi/class/keywordRequest.ts | 9 + src/app/publishapi/publishapi.component.html | 15 +- src/app/publishapi/publishapi.component.ts | 79 ++++-- .../semantic-enrichment.component.html | 40 +++ .../semantic-enrichment.component.scss | 1 + .../semantic-enrichment.component.spec.ts | 25 ++ .../semantic-enrichment.component.ts | 57 +++++ .../services/post.service.spec.ts | 16 ++ .../services/post.service.ts | 23 ++ src/environments/environment.ts | 2 +- 16 files changed, 425 insertions(+), 197 deletions(-) create mode 100644 src/app/publishapi/class/keywordRequest.ts create mode 100644 src/app/semantic-enrichment/semantic-enrichment.component.html create mode 100644 src/app/semantic-enrichment/semantic-enrichment.component.scss create mode 100644 src/app/semantic-enrichment/semantic-enrichment.component.spec.ts create mode 100644 src/app/semantic-enrichment/semantic-enrichment.component.ts create mode 100644 src/app/semantic-enrichment/services/post.service.spec.ts create mode 100644 src/app/semantic-enrichment/services/post.service.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 61ed7455d..fb0727e4c 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -11,6 +11,7 @@ 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'; export interface ICustomRoute extends Route { name?: string; @@ -29,11 +30,12 @@ const routes: ICustomRoute[] = [ ] }, {path: 'fdpsignin', component: SignupComponent, canActivate: [AuthGuardService]}, - { path: 'login', component: SigninComponent} + { path: 'login', component: SigninComponent}, + { path: 'semantic', component: SemanticEnrichmentComponent} ] @NgModule({ 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 c95b7f81c..67d2c4c18 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -44,6 +44,9 @@ 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'; @@ -63,47 +66,52 @@ 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, + ], + 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 + ], entryComponents: [DatasetsDialogComponent, FeedbackDialogComponent], providers: [ AppConfiguration, diff --git a/src/app/datasets/datasets.component.ts b/src/app/datasets/datasets.component.ts index c22c9679a..ccd15ad1f 100644 --- a/src/app/datasets/datasets.component.ts +++ b/src/app/datasets/datasets.component.ts @@ -180,7 +180,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); } @@ -407,7 +407,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 +420,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 08181e6e5..b9cb55b1a 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, @@ -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/requestMapping.ts b/src/app/mapping/class/requestMapping.ts index 1ee7bfc81..5bb9c1886 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/mapping.component.ts b/src/app/mapping/mapping.component.ts index 152445461..a25b4f5c4 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'; @@ -25,11 +25,18 @@ import { JSONPath } from 'jsonpath-plus'; }) export class MappingComponent implements OnInit { - addDistribution = false + @Output() + pathsEvent = new EventEmitter<Path[]>(); + + @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,12 +46,12 @@ 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; @@ -59,15 +66,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 +89,15 @@ 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 +113,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,11 +129,11 @@ 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(' : '); + const tab = array[i].split(' : '); !this.isJsonPath ? mappedMetadata.set(i, this.getValueCustom(tab, item)) : mappedMetadata.set(i, this.getValueJsonPath(array[i], item)); } } @@ -135,7 +142,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 +151,23 @@ 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 + ) + ); } } + this.pathsEvent.emit(paths); + const data: RequestMapping = new RequestMapping(this.urls, paths, this.tokenStorage.getFDPToken()); - let data: RequestMapping = new RequestMapping(this.urls, paths, this.tokenStorage.getFDPToken()); - - let postedDatasets = []; - let notPostedDatasets = []; + 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 +188,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 +204,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 +212,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 +235,7 @@ export class MappingComponent implements OnInit { } else { notPostedDatasets.push(this.ids[i]); } - }) + }); requestPromises.push(requestPromise); } Promise.all(requestPromises).finally(() => { @@ -236,15 +247,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 +287,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 +312,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 +332,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 +343,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; @@ -348,12 +359,12 @@ export class MappingComponent implements OnInit { } else { for (let i = 0; i < tab.length; i++) { if (i == 0) { - obj = item[tab[i]] + 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 +372,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 +390,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,13 +414,13 @@ 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) { return item[tab[0]]; } else { if (i == 0) { - obj = item[tab[i]] + obj = item[tab[i]]; } else if (i < tab.length - 1 && obj[tab[i]]) { obj = obj[tab[i]]; } else { @@ -417,17 +428,17 @@ export class MappingComponent implements OnInit { return obj[tab[i]]; } else { if (i == tab.length - 2) { - this.getKeysFromMetadataDataverse(item, ""); - console.log(tab[i] + " " + i) + 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) { return item[tab[0]]; } else { if (i == 0) { - obj = item[table[i]] + obj = item[table[i]]; } else if (i < table.length - 1 && obj[table[i]]) { obj = obj[table[i]]; } else { @@ -440,7 +451,7 @@ export class MappingComponent implements OnInit { } } } - }) + }); } } } @@ -455,44 +466,44 @@ 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 +511,7 @@ 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 +519,7 @@ export class MappingComponent implements OnInit { downloadJson() { - let datasetPathArray = this.createObjectToExport(); + const datasetPathArray = this.createObjectToExport(); @@ -544,57 +555,57 @@ 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 +630,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 +640,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 +652,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,11 +661,11 @@ 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 { @@ -666,17 +678,17 @@ 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 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 +701,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/publishapi/class/keywordRequest.ts b/src/app/publishapi/class/keywordRequest.ts new file mode 100644 index 000000000..1d6d00608 --- /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 d11d5b486..ed5330bb2 100644 --- a/src/app/publishapi/publishapi.component.html +++ b/src/app/publishapi/publishapi.component.html @@ -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']" + (pathsEvent)="pathsEventHandler($event)" (isJsonPathEvent)="isJsonPathEventHandler($event)"> </app-mapping> <button class="prev-button" nbButton nbStepperPrevious - (click)="initLabelThree=false; resetDataset(); datasets.ready = false">prev</button> + (click)="initLabelThree=false; resetDataset(); datasets.ready = false"; getKeywords()>prev</button> <button class="next-button" nbButton disabled nbStepperNext>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> + <h4>Semantic Enrichment</h4> + <app-semantic-enrichment [paths]="paths"></app-semantic-enrichment> <button class="prev-button" nbButton nbStepperPrevious>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 5caf7af56..0e894729c 100644 --- a/src/app/publishapi/publishapi.component.ts +++ b/src/app/publishapi/publishapi.component.ts @@ -5,11 +5,13 @@ 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 {Path as PathReq} from '../mapping/class/requestMapping' ; +import {KeywordRequest} from './class/keywordRequest'; @Component({ selector: 'app-publishapi', @@ -23,6 +25,9 @@ export class PublishApiComponent implements OnInit { @ViewChild('datasets') datasets; @ViewChild('stepper') stepper: NbStepperComponent; + paths: PathReq[]; + isJsonPath: boolean; + constructor( private openApiService: OpenApiService, private catalogService: CatalogService, @@ -34,7 +39,15 @@ 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; @@ -43,12 +56,20 @@ export class PublishApiComponent implements OnInit { 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(); } + pathsEventHandler($event: PathReq[]) { + this.paths = $event; + } + + isJsonPathEventHandler($event: boolean) { + this.isJsonPath = $event; + } + getPrettyJson(): string { return this.openApiService.getPrettyJson(this.openApi); } @@ -59,7 +80,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 +128,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 +197,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 +210,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 +219,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 +228,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 +237,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 +249,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 +305,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 +323,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 +341,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 +351,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 +384,19 @@ export class PublishApiComponent implements OnInit { openDatasetsSelectDialog() { this.datasets.openDatasetsSelectDialog(this.stepper); } + + getKeywords() { + let keywordPaths: string[] = []; + if (this.paths !== null) { + for (const path of this.paths) { + if (path.dcatClass === 'dcat:keyword') { + keywordPaths.push(path.path); + } + } + + const data = new KeywordRequest(this.isJsonPath, keywordPaths); + } + } + } 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 000000000..87756692f --- /dev/null +++ b/src/app/semantic-enrichment/semantic-enrichment.component.html @@ -0,0 +1,40 @@ +<nb-layout> + <nb-layout-header fixed> + <img width="80" alt="Angular Logo" src="assets/images/logo.png"/> + <h3 style="width: 100%;text-align: center;"><strong></strong></h3> + <div style="float: right;"> + <button (click)="login()" status="primary" nbButton>login</button> + </div> + + + </nb-layout-header> + + <nb-layout-column> + + <nb-card size="small"> + <nb-card-body> + <nb-form-field> + <input + type="text" + placeholder="Enter Location" + [formControl]="myControl" + nbInput + [nbAutocomplete]="auto"> + <nb-autocomplete #auto [handleDisplayFn]="viewHandle"> + <nb-option + *ngFor="let option of filteredOptions | async" + [value]="option"> + {{option.name}} + </nb-option> + </nb-autocomplete> + </nb-form-field> + </nb-card-body> + </nb-card> + <p *ngIf="myControl.value !== null">{{ myControl.value | json }}</p> + </nb-layout-column> + + + <nb-layout-footer>Contact us</nb-layout-footer> + + + 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 000000000..8b1378917 --- /dev/null +++ b/src/app/semantic-enrichment/semantic-enrichment.component.scss @@ -0,0 +1 @@ + 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 000000000..f515fc3d7 --- /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 000000000..b15b368f8 --- /dev/null +++ b/src/app/semantic-enrichment/semantic-enrichment.component.ts @@ -0,0 +1,57 @@ + +import {Component, Input, OnInit} from '@angular/core'; +import {Path} from '../mapping/class/requestMapping'; +import {Observable} from 'rxjs'; +import {debounceTime, distinctUntilChanged, map, startWith, switchMap} from 'rxjs/operators'; +import {PostService} from './services/post.service'; +import {FormControl} from '@angular/forms'; + +@Component({ + selector: 'app-semantic-enrichment', + templateUrl: './semantic-enrichment.component.html', + styleUrls: ['./semantic-enrichment.component.scss'] +}) +export class SemanticEnrichmentComponent implements OnInit { + + @Input() + paths: Path[]; + + myControl = new FormControl(); + options = []; + filteredOptions: Observable<any>; + + constructor(private service: PostService) { + + this.filteredOptions = this.myControl.valueChanges.pipe( + startWith(''), + debounceTime(400), + distinctUntilChanged(), + switchMap(val => { + return this.filter(val || ''); + }) + ); + } + + ngOnInit(): void { + /*for ( let path of this.paths) { + if (path.property === 'dcat:keyword') { + + } + }*/ + } + + filter(val: any): Observable<any> { + return this.service.getData() + .pipe( + map(response => response.filter((option: any) => { + return option.name.toLowerCase().includes(val.toLowerCase()); + })) + ); + } + + viewHandle(value: any) { + return value.name; + } + +} + diff --git a/src/app/semantic-enrichment/services/post.service.spec.ts b/src/app/semantic-enrichment/services/post.service.spec.ts new file mode 100644 index 000000000..913642b87 --- /dev/null +++ b/src/app/semantic-enrichment/services/post.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { PostService } from './post.service'; + +describe('PostService', () => { + let service: PostService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(PostService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); 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 000000000..afb0b8d79 --- /dev/null +++ b/src/app/semantic-enrichment/services/post.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {of} from 'rxjs'; +import {tap} from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class PostService { + + constructor(private http: HttpClient) { } + + opt = []; + + + getData() { + return this.opt.length ? + of(this.opt) : + this.http.get('https://jsonplaceholder.typicode.com/users').pipe( + tap((data: any) => this.opt = data) + ); + } +} diff --git a/src/environments/environment.ts b/src/environments/environment.ts index dce50e66a..133ced820 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -5,7 +5,7 @@ export const environment = { production: false, smartharvesterUrl: 'http://localhost:8080', - fdpUrl: 'https://f2ds-dev.eosc-pillar.eu' + fdpUrl: 'http://10.6.10.97:8080' }; /* -- GitLab