diff --git a/libs/feature/record/src/lib/data-downloads/data-downloads.component.html b/libs/feature/record/src/lib/data-downloads/data-downloads.component.html index 3cdb9c3b7b..7ac8543675 100644 --- a/libs/feature/record/src/lib/data-downloads/data-downloads.component.html +++ b/libs/feature/record/src/lib/data-downloads/data-downloads.component.html @@ -1,14 +1,2 @@ -

- record.metadata.download -

-
- -
- - - - + + diff --git a/libs/feature/record/src/lib/data-downloads/data-downloads.component.spec.ts b/libs/feature/record/src/lib/data-downloads/data-downloads.component.spec.ts index 9c2455d15a..6b2dee46b1 100644 --- a/libs/feature/record/src/lib/data-downloads/data-downloads.component.spec.ts +++ b/libs/feature/record/src/lib/data-downloads/data-downloads.component.spec.ts @@ -8,7 +8,7 @@ import { BehaviorSubject, of, throwError } from 'rxjs' import { MdViewFacade } from '../state' import { DataDownloadsComponent } from './data-downloads.component' import { MetadataLink } from '@geonetwork-ui/util/shared' -import { Component, Input } from '@angular/core' +import { Component, Input, NO_ERRORS_SCHEMA } from '@angular/core' import { By } from '@angular/platform-browser' import { DataService } from '../service/data.service' @@ -85,6 +85,7 @@ describe('DataDownloadsComponent', () => { useClass: DataServiceMock, }, ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents() facade = TestBed.inject(MdViewFacade) }) @@ -204,66 +205,138 @@ describe('DataDownloadsComponent', () => { expect(downloadLinks).toEqual([ { description: 'Lieu de surveillance (point)', - name: 'surval_parametre_point.csv', format: 'csv', + name: 'surval_parametre_point.csv', protocol: 'WWW:DOWNLOAD', url: 'https://www.ifremer.fr/surval_parametre_point.csv', }, + { + description: 'Lieu de surveillance (ligne)', + format: 'WFS:csv', + name: 'surval_parametre_ligne', + protocol: 'OGC:WFS', + url: 'https://www.ifremer.fr/services/wfs/surveillance_littorale', + }, + { + description: 'ArcGIS GeoService Wfs', + format: 'WFS:csv', + mediaType: 'application/json', + name: 'mes_hdf', + protocol: 'ESRI:REST', + url: 'https://services8.arcgis.com/rxZzohbySMKHTNcy/arcgis/rest/services/mes_hdf/WFSServer/0', + }, { description: 'Lieu de surveillance (polygone)', - name: 'surval_parametre_polygone.geojson', format: 'geojson', + name: 'surval_parametre_polygone.geojson', protocol: 'WWW:DOWNLOAD', url: 'https://www.ifremer.fr/surval_parametre_polygone.geojson', }, { description: 'Lieu de surveillance (ligne)', - name: 'surval_parametre_ligne', format: 'WFS:geojson', - protocol: 'OGC:WFS', - url: 'https://www.ifremer.fr/services/wfs/surveillance_littorale', - }, - { - description: 'Lieu de surveillance (ligne)', name: 'surval_parametre_ligne', - format: 'WFS:csv', protocol: 'OGC:WFS', url: 'https://www.ifremer.fr/services/wfs/surveillance_littorale', }, { - protocol: 'ESRI:REST', - name: 'mes_hdf', - format: 'WFS:geojson', description: 'ArcGIS GeoService Wfs', + format: 'WFS:geojson', mediaType: 'application/json', - url: 'https://services8.arcgis.com/rxZzohbySMKHTNcy/arcgis/rest/services/mes_hdf/WFSServer/0', - }, - { - protocol: 'ESRI:REST', name: 'mes_hdf', - format: 'WFS:csv', - description: 'ArcGIS GeoService Wfs', - mediaType: 'application/json', + protocol: 'ESRI:REST', url: 'https://services8.arcgis.com/rxZzohbySMKHTNcy/arcgis/rest/services/mes_hdf/WFSServer/0', }, { - protocol: 'ESRI:REST', - name: 'mes_hdf_journalier_poll_princ', - format: 'REST:json', description: 'ArcGIS GeoService', + format: 'REST:json', mediaType: 'application/json', + name: 'mes_hdf_journalier_poll_princ', + protocol: 'ESRI:REST', url: 'https://services8.arcgis.com/rxZzohbySMKHTNcy/arcgis/rest/services/mes_hdf_journalier_poll_princ/FeatureServer/0/query?f=json&where=1=1&outFields=*', }, { - protocol: 'ESRI:REST', - name: 'mes_hdf_journalier_poll_princ', - format: 'REST:geojson', description: 'ArcGIS GeoService', + format: 'REST:geojson', mediaType: 'application/json', + name: 'mes_hdf_journalier_poll_princ', + protocol: 'ESRI:REST', url: 'https://services8.arcgis.com/rxZzohbySMKHTNcy/arcgis/rest/services/mes_hdf_journalier_poll_princ/FeatureServer/0/query?f=geojson&where=1=1&outFields=*', }, ]) })) }) + describe('with sorted links', () => { + beforeEach(() => { + facade.downloadLinks$.next([ + { + description: 'KML Data', + name: 'abc.kml', + format: 'kml', + protocol: 'WWW:DOWNLOAD', + url: 'https://www.ifremer.fr/data.kml', + }, + { + description: 'Lieu de surveillance (point)', + name: 'surval_parametre_point.csv', + format: 'csv', + protocol: 'WWW:DOWNLOAD', + url: 'https://www.ifremer.fr/surval_parametre_point.csv', + }, + { + description: 'pdf file', + name: 'abc.pdf', + format: 'pdf', + protocol: 'WWW:DOWNLOAD', + url: 'https://www.ifremer.fr/file.pdf', + }, + { + description: 'Lieu de surveillance (polygone)', + name: 'surval_parametre_polygone.geojson', + format: 'geojson', + protocol: 'WWW:DOWNLOAD', + url: 'https://www.ifremer.fr/surval_parametre_polygone.geojson', + }, + ]) + fixture.detectChanges() + }) + it('sorts links', fakeAsync(() => { + let downloadLinks = [] + component.links$.subscribe((links: MetadataLink[]) => { + downloadLinks = links + }) + tick(200) + expect(downloadLinks).toEqual([ + { + description: 'Lieu de surveillance (point)', + name: 'surval_parametre_point.csv', + format: 'csv', + protocol: 'WWW:DOWNLOAD', + url: 'https://www.ifremer.fr/surval_parametre_point.csv', + }, + { + description: 'Lieu de surveillance (polygone)', + name: 'surval_parametre_polygone.geojson', + format: 'geojson', + protocol: 'WWW:DOWNLOAD', + url: 'https://www.ifremer.fr/surval_parametre_polygone.geojson', + }, + { + description: 'KML Data', + name: 'abc.kml', + format: 'kml', + protocol: 'WWW:DOWNLOAD', + url: 'https://www.ifremer.fr/data.kml', + }, + { + description: 'pdf file', + name: 'abc.pdf', + format: 'pdf', + protocol: 'WWW:DOWNLOAD', + url: 'https://www.ifremer.fr/file.pdf', + }, + ]) + })) + }) }) }) diff --git a/libs/feature/record/src/lib/data-downloads/data-downloads.component.ts b/libs/feature/record/src/lib/data-downloads/data-downloads.component.ts index b9bb3931ba..a5bb9f4243 100644 --- a/libs/feature/record/src/lib/data-downloads/data-downloads.component.ts +++ b/libs/feature/record/src/lib/data-downloads/data-downloads.component.ts @@ -3,11 +3,13 @@ import { getFileFormat, getWfsFormat, LinkHelperService, -} from '@geonetwork-ui/feature/search' -import { catchError, map, startWith, switchMap } from 'rxjs/operators' -import { MdViewFacade } from '../state' -import { combineLatest } from 'rxjs' + sortPriority, +} from '@geonetwork-ui/util/shared' +import { MetadataLinkValid } from '@geonetwork-ui/util/shared' +import { combineLatest, of } from 'rxjs' +import { catchError, map, switchMap } from 'rxjs/operators' import { DataService } from '../service/data.service' +import { MdViewFacade } from '../state' @Component({ selector: 'gn-ui-data-downloads', @@ -44,12 +46,13 @@ export class DataDownloadsComponent { this.error = null return combineLatest( - wfsLinks.map((link) => this.dataService.getDownloadLinksFromWfs(link)) + wfsLinks.length > 0 + ? wfsLinks.map((link) => + this.dataService.getDownloadLinksFromWfs(link) + ) + : [of([])] ).pipe( - catchError((e) => { - this.error = e.message - return [] - }), + // flaten array map( (wfsDownloadLinks) => wfsDownloadLinks.reduce((prev, curr) => [...prev, ...curr]), @@ -62,6 +65,7 @@ export class DataDownloadsComponent { format: getWfsFormat(link), })) .filter((link) => link.format) + // remove duplicates .filter( (link, i, links) => links.findIndex( @@ -76,7 +80,17 @@ export class DataDownloadsComponent { ...wfsDownloadLinks, ...esriRestLinks, ]), - startWith([...otherLinks, ...esriRestLinks]) + catchError((e) => { + this.error = e.message + return of([...otherLinks, ...esriRestLinks]) + }), + map((allLinks) => + allLinks.sort( + (a: MetadataLinkValid, b: MetadataLinkValid): number => { + return sortPriority(b) - sortPriority(a) + } + ) + ) ) }) ) diff --git a/libs/feature/record/src/lib/data-view-map/data-view-map.component.ts b/libs/feature/record/src/lib/data-view-map/data-view-map.component.ts index f51ad0a48c..eb4c083fdd 100644 --- a/libs/feature/record/src/lib/data-view-map/data-view-map.component.ts +++ b/libs/feature/record/src/lib/data-view-map/data-view-map.component.ts @@ -13,7 +13,7 @@ import { MapStyleService, MapUtilsService, } from '@geonetwork-ui/feature/map' -import { LinkHelperService } from '@geonetwork-ui/feature/search' +import { LinkHelperService } from '@geonetwork-ui/util/shared' import { getMapConfig, MapConfig } from '@geonetwork-ui/util/app-config' import { MetadataLinkValid, ProxyService } from '@geonetwork-ui/util/shared' import { Feature } from 'ol' diff --git a/libs/feature/record/src/lib/data-view-table/data-view-table.component.ts b/libs/feature/record/src/lib/data-view-table/data-view-table.component.ts index 6e8d41f43e..3f9fc666ab 100644 --- a/libs/feature/record/src/lib/data-view-table/data-view-table.component.ts +++ b/libs/feature/record/src/lib/data-view-table/data-view-table.component.ts @@ -17,7 +17,7 @@ import { switchMap, } from 'rxjs/operators' import { MdViewFacade } from '../state' -import { getFileFormat, LinkHelperService } from '@geonetwork-ui/feature/search' +import { getFileFormat, LinkHelperService } from '@geonetwork-ui/util/shared' import { DataService } from '../service/data.service' @Component({ diff --git a/libs/feature/record/src/lib/external-viewer-button/external-viewer-button.component.spec.ts b/libs/feature/record/src/lib/external-viewer-button/external-viewer-button.component.spec.ts index 2e43e01f14..3c5ad72a0a 100644 --- a/libs/feature/record/src/lib/external-viewer-button/external-viewer-button.component.spec.ts +++ b/libs/feature/record/src/lib/external-viewer-button/external-viewer-button.component.spec.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core' import { ComponentFixture, TestBed } from '@angular/core/testing' import { By } from '@angular/platform-browser' -import { LinkHelperService } from '@geonetwork-ui/feature/search' +import { LinkHelperService } from '@geonetwork-ui/util/shared' import { MAP_CONFIG_FIXTURE } from '@geonetwork-ui/util/app-config' import { ExternalViewerButtonComponent } from './external-viewer-button.component' diff --git a/libs/feature/record/src/lib/external-viewer-button/external-viewer-button.component.ts b/libs/feature/record/src/lib/external-viewer-button/external-viewer-button.component.ts index c6cdd40863..3b909be251 100644 --- a/libs/feature/record/src/lib/external-viewer-button/external-viewer-button.component.ts +++ b/libs/feature/record/src/lib/external-viewer-button/external-viewer-button.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { LinkHelperService } from '@geonetwork-ui/feature/search' +import { LinkHelperService } from '@geonetwork-ui/util/shared' import { MapConfig } from '@geonetwork-ui/util/app-config' import { MetadataLinkValid } from '@geonetwork-ui/util/shared' diff --git a/libs/feature/record/src/lib/record-metadata/record-metadata.component.spec.ts b/libs/feature/record/src/lib/record-metadata/record-metadata.component.spec.ts index 9633b62f00..79a09f754b 100644 --- a/libs/feature/record/src/lib/record-metadata/record-metadata.component.spec.ts +++ b/libs/feature/record/src/lib/record-metadata/record-metadata.component.spec.ts @@ -10,7 +10,7 @@ import { MetadataInfoComponent, UiElementsModule, } from '@geonetwork-ui/ui/elements' -import { RECORDS_FULL_FIXTURE } from '@geonetwork-ui/ui/search' +import { RECORDS_FULL_FIXTURE } from '@geonetwork-ui/util/shared' import { TranslateModule } from '@ngx-translate/core' import { BehaviorSubject, of } from 'rxjs' import { MdViewFacade } from '../state/mdview.facade' diff --git a/libs/feature/record/src/lib/service/data.service.ts b/libs/feature/record/src/lib/service/data.service.ts index 268ea21a8d..84cbeafaf6 100644 --- a/libs/feature/record/src/lib/service/data.service.ts +++ b/libs/feature/record/src/lib/service/data.service.ts @@ -1,11 +1,11 @@ import { Injectable } from '@angular/core' -import { from, Observable, throwError } from 'rxjs' +import { marker } from '@biesbjerg/ngx-translate-extract-marker' import { WfsEndpoint } from '@camptocamp/ogc-client' +import { readDataset, SupportedType } from '@geonetwork-ui/data-fetcher' import { MetadataLinkValid, ProxyService } from '@geonetwork-ui/util/shared' -import { catchError, map, pluck, tap } from 'rxjs/operators' import type { FeatureCollection } from 'geojson' -import { readDataset, SupportedType } from '@geonetwork-ui/data-fetcher' -import { marker } from '@biesbjerg/ngx-translate-extract-marker' +import { from, Observable, throwError } from 'rxjs' +import { catchError, map, tap } from 'rxjs/operators' marker('wfs.unreachable.cors') marker('wfs.unreachable.http') diff --git a/libs/feature/record/src/lib/state/mdview.facade.spec.ts b/libs/feature/record/src/lib/state/mdview.facade.spec.ts index 426785251a..6c543200ae 100644 --- a/libs/feature/record/src/lib/state/mdview.facade.spec.ts +++ b/libs/feature/record/src/lib/state/mdview.facade.spec.ts @@ -5,7 +5,7 @@ import { MdViewFacade } from './mdview.facade' import { RECORDS_FULL_FIXTURE, RECORDS_SUMMARY_FIXTURE, -} from '@geonetwork-ui/ui/search' +} from '@geonetwork-ui/util/shared' import * as MdViewActions from './mdview.actions' import { hot } from 'jasmine-marbles' diff --git a/libs/feature/record/src/lib/state/mdview.facade.ts b/libs/feature/record/src/lib/state/mdview.facade.ts index 33130c99e6..74965b32af 100644 --- a/libs/feature/record/src/lib/state/mdview.facade.ts +++ b/libs/feature/record/src/lib/state/mdview.facade.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core' -import { LinkHelperService } from '@geonetwork-ui/feature/search' +import { LinkHelperService } from '@geonetwork-ui/util/shared' import { MetadataLinkValid, MetadataRecord } from '@geonetwork-ui/util/shared' import { select, Store } from '@ngrx/store' diff --git a/libs/feature/record/src/lib/state/mdview.reducer.spec.ts b/libs/feature/record/src/lib/state/mdview.reducer.spec.ts index 4ddea36dd6..790435741a 100644 --- a/libs/feature/record/src/lib/state/mdview.reducer.spec.ts +++ b/libs/feature/record/src/lib/state/mdview.reducer.spec.ts @@ -1,4 +1,4 @@ -import { RECORDS_SUMMARY_FIXTURE } from '@geonetwork-ui/ui/search' +import { RECORDS_SUMMARY_FIXTURE } from '@geonetwork-ui/util/shared' import * as MdViewActions from './mdview.actions' import { initialMdviewState, reducer } from './mdview.reducer' diff --git a/libs/feature/record/tsconfig.lib.json b/libs/feature/record/tsconfig.lib.json index 382013681c..055878cc02 100644 --- a/libs/feature/record/tsconfig.lib.json +++ b/libs/feature/record/tsconfig.lib.json @@ -7,7 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"] + "lib": ["dom", "es2019"] }, "exclude": [ "src/test-setup.ts", diff --git a/libs/feature/search/src/index.ts b/libs/feature/search/src/index.ts index fdbc22d451..a8eacf9258 100644 --- a/libs/feature/search/src/index.ts +++ b/libs/feature/search/src/index.ts @@ -4,6 +4,5 @@ export * from './lib/state/selectors' export * from './lib/state/search.facade' export * from './lib/state/effects' export * from './lib/state/reducer' -export * from './lib/utils/links' export * from './lib/utils/mapper' export * from './lib/utils/service/search.service' diff --git a/libs/feature/search/src/lib/utils/links/link-utils.ts b/libs/feature/search/src/lib/utils/links/link-utils.ts deleted file mode 100644 index 492c09a24f..0000000000 --- a/libs/feature/search/src/lib/utils/links/link-utils.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { MetadataLinkValid } from '@geonetwork-ui/util/shared' -import { marker } from '@biesbjerg/ngx-translate-extract-marker' - -marker('downloads.wfs.featuretype.not.found') - -const FORMATS = { - csv: ['csv'], - geojson: ['geojson'], - json: ['json'], - shp: ['shp', 'shape', 'zipped-shapefile'], - kml: ['kml', 'kmz'], - gpkg: ['gpkg', 'geopackage'], - excel: ['xls', 'xlsx', 'ms-excel', 'openxmlformats-officedocument'], - pdf: ['pdf'], - jpg: ['jpg', 'jpeg', 'jfif', 'pjpeg', 'pjp'], - zip: ['zip'], -} - -export function getWfsFormat(link: MetadataLinkValid): string | void { - for (const format in FORMATS) { - for (const alias of FORMATS[format]) { - if ('format' in link && new RegExp(`${alias}`, 'i').test(link.format)) - return `WFS:${format}` - } - } -} - -export function getFileFormat(link: MetadataLinkValid): string | void { - if (link.format) { - return link.format - } - if ('protocol' in link && /^WWW:DOWNLOAD/.test(link.protocol)) { - // mime types in protocol - const matches = link.protocol.match(/^WWW:DOWNLOAD:(.+\/.+)$/) - if (matches !== null) { - return mimeTypeToFormat(matches[1]) - } - } - for (const format in FORMATS) { - for (const alias of FORMATS[format]) { - if (checkFileFormat(link, alias)) return format - } - } -} - -export function mimeTypeToFormat(mimeType: string): string | void { - switch (mimeType) { - case 'application/json': - return 'json' - case 'application/geo+json': - case 'application/vnd.geo+json': - return 'geojson' - case 'text/csv': - case 'application/csv': - return 'csv' - case 'x-gis/x-shapefile': - return 'shp' - case 'application/vnd.ms-excel': - case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': - return 'excel' - } -} - -export function checkFileFormat( - link: MetadataLinkValid, - format: string -): boolean { - return ( - ('name' in link && new RegExp(`[./]${format}`, 'i').test(link.name)) || - ('url' in link && new RegExp(`[./]${format}`, 'i').test(link.url)) - ) -} diff --git a/libs/feature/search/src/lib/utils/mapper/elasticsearch.field.mapper.ts b/libs/feature/search/src/lib/utils/mapper/elasticsearch.field.mapper.ts index 6c27181c90..07f4aaa1ba 100644 --- a/libs/feature/search/src/lib/utils/mapper/elasticsearch.field.mapper.ts +++ b/libs/feature/search/src/lib/utils/mapper/elasticsearch.field.mapper.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core' -import { LinkHelperService } from '../links/link-helper.service' +import { LinkHelperService } from '@geonetwork-ui/util/shared' import { getAsArray, getAsUrl, diff --git a/libs/ui/elements/src/lib/api-card/api-card.component.spec.ts b/libs/ui/elements/src/lib/api-card/api-card.component.spec.ts index 92729a6d62..9e32611ec7 100644 --- a/libs/ui/elements/src/lib/api-card/api-card.component.spec.ts +++ b/libs/ui/elements/src/lib/api-card/api-card.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core' import { ComponentFixture, TestBed } from '@angular/core/testing' import { MatIconModule } from '@angular/material/icon' import { TranslateModule } from '@ngx-translate/core' @@ -9,6 +10,7 @@ describe('ApiCardComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ + schemas: [NO_ERRORS_SCHEMA], declarations: [ApiCardComponent], imports: [MatIconModule, TranslateModule.forRoot()], }).compileComponents() diff --git a/libs/ui/elements/src/lib/download-item/download-item.component.css b/libs/ui/elements/src/lib/download-item/download-item.component.css index 4aa1e38763..11fc149df2 100644 --- a/libs/ui/elements/src/lib/download-item/download-item.component.css +++ b/libs/ui/elements/src/lib/download-item/download-item.component.css @@ -1,9 +1,11 @@ .root:hover .download-icon { color: var(--color-secondary); } -.root:hover .badge { - background-color: var(--color-primary) !important; -} + .root:not(:hover) .badge { - background-color: var(--color-primary-lightest) !important; + opacity: 0.7 !important; +} + +.root:hover .badge { + opacity: 1 !important; } diff --git a/libs/ui/elements/src/lib/download-item/download-item.component.html b/libs/ui/elements/src/lib/download-item/download-item.component.html index 5d8b659a1e..b6aa287670 100644 --- a/libs/ui/elements/src/lib/download-item/download-item.component.html +++ b/libs/ui/elements/src/lib/download-item/download-item.component.html @@ -1,26 +1,36 @@ -
-
+
- {{ link.name }} + {{ link.label }}
{{ link.format || ('downloads.format.unknown' | translate) }} + datahub.search.filter.generatedByWfs
-
- - cloud_download - +
+ + cloud_download +
-
+ diff --git a/libs/ui/elements/src/lib/download-item/download-item.component.ts b/libs/ui/elements/src/lib/download-item/download-item.component.ts index 0a9a350c66..ef60badce1 100644 --- a/libs/ui/elements/src/lib/download-item/download-item.component.ts +++ b/libs/ui/elements/src/lib/download-item/download-item.component.ts @@ -15,6 +15,7 @@ import { MetadataLinkValid } from '@geonetwork-ui/util/shared' }) export class DownloadItemComponent { @Input() link: MetadataLinkValid + @Input() color: string @Output() exportUrl = new EventEmitter() openUrl() { diff --git a/libs/ui/elements/src/lib/downloads-list/downloads-list.component.css b/libs/ui/elements/src/lib/downloads-list/downloads-list.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/ui/elements/src/lib/downloads-list/downloads-list.component.html b/libs/ui/elements/src/lib/downloads-list/downloads-list.component.html new file mode 100644 index 0000000000..4637bfd7a0 --- /dev/null +++ b/libs/ui/elements/src/lib/downloads-list/downloads-list.component.html @@ -0,0 +1,21 @@ +
+

+ record.metadata.download +

+
+ {{ getFilterFormatTitle(format) }} +
+
+
+ +
diff --git a/libs/ui/elements/src/lib/downloads-list/downloads-list.component.spec.ts b/libs/ui/elements/src/lib/downloads-list/downloads-list.component.spec.ts new file mode 100644 index 0000000000..74bfde2f2a --- /dev/null +++ b/libs/ui/elements/src/lib/downloads-list/downloads-list.component.spec.ts @@ -0,0 +1,37 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core' +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { LinkHelperService } from '@geonetwork-ui/util/shared' +import { TranslateModule } from '@ngx-translate/core' + +import { DownloadsListComponent } from './downloads-list.component' + +const linkHelperServiceMock = {} +describe('DownloadsListComponent', () => { + let component: DownloadsListComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [DownloadsListComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { + provide: LinkHelperService, + useValue: linkHelperServiceMock, + }, + ], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(DownloadsListComponent) + component = fixture.componentInstance + component.links = [] + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/libs/ui/elements/src/lib/downloads-list/downloads-list.component.ts b/libs/ui/elements/src/lib/downloads-list/downloads-list.component.ts new file mode 100644 index 0000000000..9c5985f7b4 --- /dev/null +++ b/libs/ui/elements/src/lib/downloads-list/downloads-list.component.ts @@ -0,0 +1,124 @@ +import { + ChangeDetectionStrategy, + Component, + Input, + OnInit, +} from '@angular/core' +import { MetadataLinkValid } from '@geonetwork-ui/util/shared' +import { getBadgeColor, LinkHelperService } from '@geonetwork-ui/util/shared' +import { TranslateService } from '@ngx-translate/core' + +@Component({ + selector: 'gn-ui-downloads-list', + templateUrl: './downloads-list.component.html', + styleUrls: ['./downloads-list.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DownloadsListComponent implements OnInit { + constructor( + private linkHelper: LinkHelperService, + private translateService: TranslateService + ) {} + + @Input() links: MetadataLinkValid[] + + filterParam = '' + filterFormats: string[] = ['all', 'csv', 'excel', 'json', 'shp', 'others'] + activeFilterFormats: string[] = ['all'] + + processedLinks: MetadataLinkValid[] = [] + filteredLinks: MetadataLinkValid[] = [] + filterButtons: FilterButton[] + + toggleFilterFormat(format: string): void { + if (format === 'all') { + this.activeFilterFormats = ['all'] + } else { + this.activeFilterFormats = this.isFilterActive(format) + ? this.activeFilterFormats.filter((f: string) => format !== f) + : [...this.activeFilterFormats.filter((f) => f !== 'all'), format] + } + this.filteredLinks = this.filterLinks(this.processedLinks) + } + + isFilterActive(filter: string): boolean { + return this.activeFilterFormats.includes(filter) + } + + getFilterFormatTitle(format: string) { + if (format === 'all' || format === 'others') { + return this.translateService.instant(`datahub.search.filter.${format}`) + } + return format + } + + ngOnInit(): void { + this.processedLinks = this.formatLinks(this.links) + this.processedLinks = this.assignColor(this.processedLinks) + this.processedLinks = this.isGeneratedFromWfs(this.processedLinks) + this.filteredLinks = this.filterLinks(this.processedLinks) + + this.filterButtons = this.filterFormats.map((format) => { + return { + format: format, + color: getBadgeColor(format), + } + }) + } + + formatLinks(links) { + return links.map((link) => { + return { + ...link, + format: link.format.split(':').at(-1), + } + }) + } + + filterLinks(links: MetadataLinkValid[]) { + if ( + this.activeFilterFormats.length === 0 || + this.activeFilterFormats.includes('all') + ) { + return links + } + let others: MetadataLinkValid[] = [] + if (this.activeFilterFormats.includes('others')) { + others = links.filter((link) => { + let isOther = true + for (const format of this.filterFormats) { + if (format === link.format) isOther = false + } + return isOther + }) + } + const filteredLinks = links.filter((link: MetadataLinkValid) => { + return this.activeFilterFormats.includes(link.format) + }) + return [...filteredLinks, ...others] + } + + assignColor(links: MetadataLinkValid[]) { + return links.map((link: MetadataLinkValid) => { + return { + ...link, + color: getBadgeColor(link.format), + } + }) + } + + isGeneratedFromWfs(links: MetadataLinkValid[]) { + return links.map((link) => { + if (!this.linkHelper.isWfsLink(link)) return link + return { + ...link, + isWfs: true, + } + }) + } +} + +export type FilterButton = { + format: string + color: string | void +} diff --git a/libs/ui/elements/src/lib/link-card/link-card.component.spec.ts b/libs/ui/elements/src/lib/link-card/link-card.component.spec.ts index ee38c1e27b..0027cc319f 100644 --- a/libs/ui/elements/src/lib/link-card/link-card.component.spec.ts +++ b/libs/ui/elements/src/lib/link-card/link-card.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core' import { ComponentFixture, TestBed } from '@angular/core/testing' import { MatIconModule } from '@angular/material/icon' import { TranslateModule } from '@ngx-translate/core' @@ -11,6 +12,7 @@ describe('LinkCardComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [LinkCardComponent], + schemas: [NO_ERRORS_SCHEMA], imports: [MatIconModule, TranslateModule.forRoot()], }).compileComponents() }) diff --git a/libs/ui/elements/src/lib/metadata-info/metadata-info.component.spec.ts b/libs/ui/elements/src/lib/metadata-info/metadata-info.component.spec.ts index eaea273c04..052a138630 100644 --- a/libs/ui/elements/src/lib/metadata-info/metadata-info.component.spec.ts +++ b/libs/ui/elements/src/lib/metadata-info/metadata-info.component.spec.ts @@ -1,9 +1,11 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' -import { UtilSharedModule } from '@geonetwork-ui/util/shared' -import { MetadataInfoComponent } from './metadata-info.component' -import { ContentGhostComponent } from '../content-ghost/content-ghost.component' -import { RECORDS_FULL_FIXTURE } from '@geonetwork-ui/ui/search' +import { + RECORDS_FULL_FIXTURE, + UtilSharedModule, +} from '@geonetwork-ui/util/shared' import { TranslateModule } from '@ngx-translate/core' +import { ContentGhostComponent } from '../content-ghost/content-ghost.component' +import { MetadataInfoComponent } from './metadata-info.component' describe('MetadataInfoComponent', () => { let component: MetadataInfoComponent diff --git a/libs/ui/elements/src/lib/related-record-card/related-record-card.component.spec.ts b/libs/ui/elements/src/lib/related-record-card/related-record-card.component.spec.ts index 08e47fef18..b0f79c0f65 100644 --- a/libs/ui/elements/src/lib/related-record-card/related-record-card.component.spec.ts +++ b/libs/ui/elements/src/lib/related-record-card/related-record-card.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core' import { ComponentFixture, TestBed } from '@angular/core/testing' import { MatIconModule } from '@angular/material/icon' import { UiSearchModule } from '@geonetwork-ui/ui/search' @@ -17,6 +18,7 @@ describe('RelatedRecordCardComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [RelatedRecordCardComponent], + schemas: [NO_ERRORS_SCHEMA], imports: [MatIconModule, UiSearchModule, TranslateModule.forRoot()], }).compileComponents() }) diff --git a/libs/ui/elements/src/lib/ui-elements.module.ts b/libs/ui/elements/src/lib/ui-elements.module.ts index bce525f24b..af5e00d4f4 100644 --- a/libs/ui/elements/src/lib/ui-elements.module.ts +++ b/libs/ui/elements/src/lib/ui-elements.module.ts @@ -7,6 +7,7 @@ import { UtilSharedModule } from '@geonetwork-ui/util/shared' import { MetadataInfoComponent } from './metadata-info/metadata-info.component' import { ContentGhostComponent } from './content-ghost/content-ghost.component' import { DownloadItemComponent } from './download-item/download-item.component' +import { DownloadsListComponent } from './downloads-list/downloads-list.component' import { ApiCardComponent } from './api-card/api-card.component' import { UiWidgetsModule } from '@geonetwork-ui/ui/widgets' import { UiLayoutModule } from '@geonetwork-ui/ui/layout' @@ -34,6 +35,7 @@ import { SearchResultsErrorComponent } from './search-results-error/search-resul MetadataInfoComponent, ContentGhostComponent, DownloadItemComponent, + DownloadsListComponent, ApiCardComponent, LinkCardComponent, RelatedRecordCardComponent, @@ -45,6 +47,7 @@ import { SearchResultsErrorComponent } from './search-results-error/search-resul MetadataInfoComponent, ContentGhostComponent, DownloadItemComponent, + DownloadsListComponent, ApiCardComponent, LinkCardComponent, RelatedRecordCardComponent, diff --git a/libs/ui/search/src/lib/facets/fixtures/index.ts b/libs/ui/search/src/lib/facets/fixtures/index.ts index ff2b62eb6d..653125bed3 100644 --- a/libs/ui/search/src/lib/facets/fixtures/index.ts +++ b/libs/ui/search/src/lib/facets/fixtures/index.ts @@ -1,2 +1 @@ export * from './aggregations-model-response' -export * from './records' diff --git a/libs/util/shared/src/index.ts b/libs/util/shared/src/index.ts index 290f4748ef..bb52fe708a 100644 --- a/libs/util/shared/src/index.ts +++ b/libs/util/shared/src/index.ts @@ -4,3 +4,4 @@ export * from './lib/models/' export * from './lib/utils/' export * from './lib/fixtures/' export * from './lib/elasticsearch/' +export * from './lib/links' diff --git a/libs/util/shared/src/lib/fixtures/index.ts b/libs/util/shared/src/lib/fixtures/index.ts index e322695745..cc6e0c18ee 100644 --- a/libs/util/shared/src/lib/fixtures/index.ts +++ b/libs/util/shared/src/lib/fixtures/index.ts @@ -1,3 +1,4 @@ export * from './geojson.fixtures' export * from './ol-feature.fixture' export * from './record-link.fixtures' +export * from './records' diff --git a/libs/ui/search/src/lib/facets/fixtures/records.ts b/libs/util/shared/src/lib/fixtures/records.ts similarity index 100% rename from libs/ui/search/src/lib/facets/fixtures/records.ts rename to libs/util/shared/src/lib/fixtures/records.ts diff --git a/libs/feature/search/src/lib/utils/links/index.ts b/libs/util/shared/src/lib/links/index.ts similarity index 100% rename from libs/feature/search/src/lib/utils/links/index.ts rename to libs/util/shared/src/lib/links/index.ts diff --git a/libs/feature/search/src/lib/utils/links/link-classifier.service.spec.ts b/libs/util/shared/src/lib/links/link-classifier.service.spec.ts similarity index 100% rename from libs/feature/search/src/lib/utils/links/link-classifier.service.spec.ts rename to libs/util/shared/src/lib/links/link-classifier.service.spec.ts diff --git a/libs/feature/search/src/lib/utils/links/link-classifier.service.ts b/libs/util/shared/src/lib/links/link-classifier.service.ts similarity index 97% rename from libs/feature/search/src/lib/utils/links/link-classifier.service.ts rename to libs/util/shared/src/lib/links/link-classifier.service.ts index 373b9bd87a..6a6f7c6060 100644 --- a/libs/feature/search/src/lib/utils/links/link-classifier.service.ts +++ b/libs/util/shared/src/lib/links/link-classifier.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core' -import { MetadataLink } from '@geonetwork-ui/util/shared' +import { MetadataLink } from '../models' export enum LinkUsage { API = 'api', diff --git a/libs/feature/search/src/lib/utils/links/link-helper.service.spec.ts b/libs/util/shared/src/lib/links/link-helper.service.spec.ts similarity index 98% rename from libs/feature/search/src/lib/utils/links/link-helper.service.spec.ts rename to libs/util/shared/src/lib/links/link-helper.service.spec.ts index 468098774b..0c2bed2eb6 100644 --- a/libs/feature/search/src/lib/utils/links/link-helper.service.spec.ts +++ b/libs/util/shared/src/lib/links/link-helper.service.spec.ts @@ -1,13 +1,7 @@ import { TestBed } from '@angular/core/testing' -import { - RECORDS_FULL_FIXTURE, - RECORDS_SUMMARY_FIXTURE, -} from '@geonetwork-ui/ui/search' -import { - MetadataLink, - MetadataRecord, - RECORD_LINK_FIXTURE_WMS, -} from '@geonetwork-ui/util/shared' +import { RECORDS_FULL_FIXTURE } from '../fixtures' +import { RECORD_LINK_FIXTURE_WMS, RECORDS_SUMMARY_FIXTURE } from '../fixtures' +import { MetadataLink, MetadataRecord } from '../models' import { LinkClassifierService, LinkUsage } from './link-classifier.service' import { LinkHelperService } from './link-helper.service' diff --git a/libs/feature/search/src/lib/utils/links/link-helper.service.ts b/libs/util/shared/src/lib/links/link-helper.service.ts similarity index 96% rename from libs/feature/search/src/lib/utils/links/link-helper.service.ts rename to libs/util/shared/src/lib/links/link-helper.service.ts index f3c48d7e77..7c2b3a624c 100644 --- a/libs/feature/search/src/lib/utils/links/link-helper.service.ts +++ b/libs/util/shared/src/lib/links/link-helper.service.ts @@ -1,9 +1,5 @@ import { Injectable } from '@angular/core' -import { - MetadataLink, - MetadataLinkValid, - MetadataRecord, -} from '@geonetwork-ui/util/shared' +import { MetadataLink, MetadataLinkValid, MetadataRecord } from '../models' import { LinkClassifierService, LinkUsage } from './link-classifier.service' import { getFileFormat } from './link-utils' diff --git a/libs/feature/search/src/lib/utils/links/link-utils.spec.ts b/libs/util/shared/src/lib/links/link-utils.spec.ts similarity index 84% rename from libs/feature/search/src/lib/utils/links/link-utils.spec.ts rename to libs/util/shared/src/lib/links/link-utils.spec.ts index d3cae7f431..93e7a61889 100644 --- a/libs/feature/search/src/lib/utils/links/link-utils.spec.ts +++ b/libs/util/shared/src/lib/links/link-utils.spec.ts @@ -3,6 +3,9 @@ import { getWfsFormat, checkFileFormat, mimeTypeToFormat, + getBadgeColor, + sortPriority, + FORMATS, } from './link-utils' import { LINK_FIXTURES } from './link.fixtures' @@ -138,6 +141,41 @@ describe('link utils', () => { ).toEqual('WFS:geojson') }) }) + describe('#getBadgeColor for format', () => { + it('returns #1e5180', () => { + expect(getBadgeColor('json')).toEqual('#1e5180') + }) + it('returns #559d7f', () => { + expect(getBadgeColor('csv')).toEqual('#559d7f') + }) + }) + describe('#sortPriority from formats object', () => { + const nFormats = Object.keys(FORMATS).length + it(`returns ${nFormats - 1}`, () => { + expect( + sortPriority({ + protocol: 'WWW:DOWNLOAD', + description: 'Data in CSV format', + label: 'Data in CSV format', + format: 'csv', + name: 'abc.csv', + url: 'https://my.server/files/abc.csv', + }) + ).toEqual(nFormats - 1) + }) + it(`returns ${nFormats - 5}`, () => { + expect( + sortPriority({ + protocol: 'WWW:DOWNLOAD', + description: 'Data in KML format', + label: 'Data in KML format', + format: 'kml', + name: 'abc.kml', + url: 'https://my.server/files/abc.kml', + }) + ).toEqual(nFormats - 5) + }) + }) describe('#checkFileFormat', () => { describe('in link name and url', () => { it('returns true for file format', () => { diff --git a/libs/util/shared/src/lib/links/link-utils.ts b/libs/util/shared/src/lib/links/link-utils.ts new file mode 100644 index 0000000000..0244cad518 --- /dev/null +++ b/libs/util/shared/src/lib/links/link-utils.ts @@ -0,0 +1,142 @@ +import { marker } from '@biesbjerg/ngx-translate-extract-marker' +import { MetadataLinkValid } from '../models' + +marker('downloads.wfs.featuretype.not.found') + +export const FORMATS = { + csv: { + extensions: ['csv'], + priority: 1, + color: '#559d7f', + mimeTypes: ['text/csv', 'application/csv'], + }, + geojson: { + extensions: ['geojson'], + priority: 3, + color: '#1e5180', + mimeTypes: ['application/geo+json', 'application/vnd.geo+json'], + }, + json: { + extensions: ['json'], + priority: 3, + color: '#1e5180', + mimeTypes: ['application/json'], + }, + shp: { + extensions: ['shp', 'shape', 'zipped-shapefile'], + priority: 4, + color: '#328556', + mimeTypes: ['x-gis/x-shapefile'], + }, + kml: { + extensions: ['kml', 'kmz'], + priority: 5, + color: '#348009', + mimeTypes: [ + 'application/vnd.google-earth.kml+xml', + 'application/vnd.google-earth.kmz', + ], + }, + gpkg: { + extensions: ['gpkg', 'geopackage'], + priority: 0, + color: 'var(--color-primary)', + mimeTypes: ['application/geopackage+sqlite3'], + }, + excel: { + extensions: ['xls', 'xlsx', 'ms-excel', 'openxmlformats-officedocument'], + priority: 2, + color: 'var(--color-primary)', + mimeTypes: [ + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ], + }, + pdf: { + extensions: ['pdf'], + priority: 0, + color: 'var(--color-primary)', + mimeTypes: ['application/vnd.ms-excel'], + }, + jpg: { + extensions: ['jpg', 'jpeg', 'jfif', 'pjpeg', 'pjp'], + priority: 0, + color: 'var(--color-primary)', + mimeTypes: ['image/jpg'], + }, + zip: { + extensions: ['zip'], + priority: 0, + color: 'var(--color-primary)', + mimeTypes: ['application/zip'], + }, +} + +export function sortPriority(link: MetadataLinkValid): number { + for (const format in FORMATS) { + for (const ext of FORMATS[format].extensions) { + if ('format' in link && new RegExp(`${ext}`, 'i').test(link.format)) { + if (FORMATS[format].priority === 0) return 0 + return Object.keys(FORMATS).length - FORMATS[format].priority + } + } + } + return 0 +} + +export function getWfsFormat(link: MetadataLinkValid): string { + for (const format in FORMATS) { + for (const alias of FORMATS[format].extensions) { + if ('format' in link && new RegExp(`${alias}`, 'i').test(link.format)) + return `WFS:${format}` + } + } + return undefined +} + +export function getFileFormat(link: MetadataLinkValid): string | void { + if (link.format) { + return link.format + } + if ('protocol' in link && /^WWW:DOWNLOAD/.test(link.protocol)) { + // mime types in protocol + const matches = link.protocol.match(/^WWW:DOWNLOAD:(.+\/.+)$/) + if (matches !== null) { + return mimeTypeToFormat(matches[1]) + } + } + for (const format in FORMATS) { + for (const alias of FORMATS[format].extensions) { + if (checkFileFormat(link, alias)) return format + } + } +} + +export function mimeTypeToFormat(mimeType: string): string { + for (const format in FORMATS) { + for (const mt of FORMATS[format].mimeTypes) { + if (mimeType === mt) return format + } + } + return undefined +} + +export function checkFileFormat( + link: MetadataLinkValid, + format: string +): boolean { + return ( + ('name' in link && new RegExp(`[./]${format}`, 'i').test(link.name)) || + ('url' in link && new RegExp(`[./]${format}`, 'i').test(link.url)) + ) +} + +export function getBadgeColor(linkFormat: string): string | void { + for (const format in FORMATS) { + for (const alias of FORMATS[format].extensions) { + if (new RegExp(`${alias}`, 'i').test(linkFormat)) + return FORMATS[format].color + } + } + return 'var(--color-primary)' // Default color ? +} diff --git a/libs/feature/search/src/lib/utils/links/link.fixtures.ts b/libs/util/shared/src/lib/links/link.fixtures.ts similarity index 100% rename from libs/feature/search/src/lib/utils/links/link.fixtures.ts rename to libs/util/shared/src/lib/links/link.fixtures.ts diff --git a/libs/util/shared/src/lib/models/search.model.ts b/libs/util/shared/src/lib/models/search.model.ts index d71efcc280..93889ef5e7 100644 --- a/libs/util/shared/src/lib/models/search.model.ts +++ b/libs/util/shared/src/lib/models/search.model.ts @@ -54,6 +54,7 @@ export interface MetadataLinkValid { format?: string description?: string label?: string + isWfs?: boolean } export type MetadataLink = MetadataLinkValid | { invalid: true; reason: string } diff --git a/translations/de.json b/translations/de.json index de8ebdb1c7..03031ffe72 100644 --- a/translations/de.json +++ b/translations/de.json @@ -78,6 +78,9 @@ "datahub.header.popularRecords": "", "datahub.header.title.html": "", "datahub.search.back": "", + "datahub.search.filter.all": "", + "datahub.search.filter.others": "", + "datahub.search.filter.generatedByWfs": "", "dataset.error.http": "", "dataset.error.network": "", "dataset.error.parse": "", diff --git a/translations/en.json b/translations/en.json index 2698a36d05..e302e600a0 100644 --- a/translations/en.json +++ b/translations/en.json @@ -78,6 +78,9 @@ "datahub.header.popularRecords": "The most popular", "datahub.header.title.html": "
Discover open
data from my Organization
", "datahub.search.back": "Back to results", + "datahub.search.filter.all": "All", + "datahub.search.filter.others": "Others", + "datahub.search.filter.generatedByWfs": "generated by API (WFS)", "dataset.error.http": "The data could not be loaded because of an HTTP error", "dataset.error.network": "The data could not be loaded because of a network error or CORS limitations", "dataset.error.parse": "The data was loaded but could not be parsed", diff --git a/translations/es.json b/translations/es.json index de8ebdb1c7..03031ffe72 100644 --- a/translations/es.json +++ b/translations/es.json @@ -78,6 +78,9 @@ "datahub.header.popularRecords": "", "datahub.header.title.html": "", "datahub.search.back": "", + "datahub.search.filter.all": "", + "datahub.search.filter.others": "", + "datahub.search.filter.generatedByWfs": "", "dataset.error.http": "", "dataset.error.network": "", "dataset.error.parse": "", diff --git a/translations/fr.json b/translations/fr.json index fd4d32674d..8f103233b8 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -78,6 +78,9 @@ "datahub.header.popularRecords": "les plus appréciées", "datahub.header.title.html": "
Toutes les données
publiques de mon organisation
", "datahub.search.back": "Retour aux résultats", + "datahub.search.filter.all": "Tous", + "datahub.search.filter.others": "Autres", + "datahub.search.filter.generatedByWfs": "généré par API (WFS)", "dataset.error.http": "Le chargement des données a échoué en raison d'une erreur HTTP", "dataset.error.network": "Le chargement des données a échoué en raison d'une erreur réseau ou de limitations CORS", "dataset.error.parse": "Les données ont été chargées mais leur décodage a échoué", diff --git a/translations/it.json b/translations/it.json index de8ebdb1c7..f3cb2882dc 100644 --- a/translations/it.json +++ b/translations/it.json @@ -78,6 +78,9 @@ "datahub.header.popularRecords": "", "datahub.header.title.html": "", "datahub.search.back": "", + "datahub.search.filter.all": "", + "datahub.search.filter.others": "", + "datahub.search.filter.generatedByWfs": "name", "dataset.error.http": "", "dataset.error.network": "", "dataset.error.parse": "", diff --git a/translations/nl.json b/translations/nl.json index de8ebdb1c7..03031ffe72 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -78,6 +78,9 @@ "datahub.header.popularRecords": "", "datahub.header.title.html": "", "datahub.search.back": "", + "datahub.search.filter.all": "", + "datahub.search.filter.others": "", + "datahub.search.filter.generatedByWfs": "", "dataset.error.http": "", "dataset.error.network": "", "dataset.error.parse": "", diff --git a/translations/pt.json b/translations/pt.json index de8ebdb1c7..03031ffe72 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -78,6 +78,9 @@ "datahub.header.popularRecords": "", "datahub.header.title.html": "", "datahub.search.back": "", + "datahub.search.filter.all": "", + "datahub.search.filter.others": "", + "datahub.search.filter.generatedByWfs": "", "dataset.error.http": "", "dataset.error.network": "", "dataset.error.parse": "",