From 497900c4ae796e542f1e870f0148f960df019fde Mon Sep 17 00:00:00 2001 From: Tobias Kohr Date: Tue, 19 Dec 2023 15:41:29 +0100 Subject: [PATCH 1/2] feat(external-viewer): enable opening geojson in external viewer also provide a translated fallback value if no layername --- conf/default.toml | 4 +- .../external-viewer-button.component.spec.ts | 187 +++++++++++++----- .../external-viewer-button.component.ts | 17 +- translations/de.json | 1 + translations/en.json | 1 + translations/es.json | 1 + translations/fr.json | 1 + translations/it.json | 1 + translations/nl.json | 1 + translations/pt.json | 1 + translations/sk.json | 1 + 11 files changed, 168 insertions(+), 48 deletions(-) diff --git a/conf/default.toml b/conf/default.toml index e47ec6f9af..08e255a813 100644 --- a/conf/default.toml +++ b/conf/default.toml @@ -143,8 +143,8 @@ background_color = "#fdfbff" # Optional; URL template enabling to open map layers in an external viewer; if set, displays a button next to the map's layer drop down # The template must include the following placeholders, which allow the datahub to inject the correct values when adding a layer to a viewer: -# ${service_url}: URL of the OWS -# ${service_type}: Type of the OWS; currently supported WMS, WFS +# ${service_url}: URL of the OWS or geojson file +# ${service_type}: Type of the OWS or geojson file; currently supported WMS, WFS, GEOJSON # ${layer_name}: Name of the layer # Be careful to use englobing single quotes, if your template syntax includes JSON (with double quotes) # Examples: 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 0f98921ef2..4a26d8f983 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 @@ -43,61 +43,158 @@ describe('ExternalViewerButtonComponent', () => { expect(component.externalViewer).toEqual(false) }) }) - describe('with mapConfig and WMS link', () => { - beforeEach(() => { - component.mapConfig = MAP_CONFIG_FIXTURE - component.link = { - url: new URL( - 'http://example.com/ows?service=wms&request=getcapabilities' - ), - name: 'layername', - type: 'service', - accessServiceProtocol: 'wms', - } - fixture.detectChanges() - }) - it('sets externalViewer to display button to true', () => { - expect(component.externalViewer).toEqual(true) + describe('with mapConfig and valid external links', () => { + let buttonComponent: MockButtonComponent + let componentSpy + let windowSpy + const openMock = jest.fn().mockReturnThis() + const focusMock = jest.fn().mockReturnThis() + describe('with mapConfig and WMS link', () => { + beforeEach(() => { + component.mapConfig = MAP_CONFIG_FIXTURE + component.link = { + url: new URL( + 'http://example.com/ows?service=wms&request=getcapabilities' + ), + name: 'layername', + type: 'service', + accessServiceProtocol: 'wms', + } + fixture.detectChanges() + }) + it('sets externalViewer to display button to true', () => { + expect(component.externalViewer).toEqual(true) + }) + describe('click button', () => { + beforeEach(() => { + buttonComponent = fixture.debugElement.query( + By.directive(MockButtonComponent) + ).componentInstance + componentSpy = jest.spyOn(component, 'openInExternalViewer') + windowSpy = jest + .spyOn(global, 'window', 'get') + .mockImplementation(() => ({ + open: openMock, + focus: focusMock, + })) + buttonComponent.buttonClick.emit() + }) + + afterEach(() => { + componentSpy.mockRestore() + windowSpy.mockRestore() + }) + it('calls openInExternalViewer', () => { + expect(component.openInExternalViewer).toHaveBeenCalled() + }) + it('opens window in new tab with URL including WMS link params', () => { + expect(openMock).toHaveBeenCalledWith( + 'https://example.com/myviewer?url=http%3A%2F%2Fexample.com%2Fows%3Fservice%3Dwms%26request%3Dgetcapabilities&name=layername&type=wms', + '_blank' + ) + }) + it('focuses window', () => { + expect(focusMock).toHaveBeenCalled() + }) + }) }) - describe('click button', () => { - let buttonComponent: MockButtonComponent - let componentSpy - let windowSpy - const openMock = jest.fn().mockReturnThis() - const focusMock = jest.fn().mockReturnThis() + describe('with mapConfig and WFS link', () => { beforeEach(() => { - buttonComponent = fixture.debugElement.query( - By.directive(MockButtonComponent) - ).componentInstance - componentSpy = jest.spyOn(component, 'openInExternalViewer') - windowSpy = jest - .spyOn(global, 'window', 'get') - .mockImplementation(() => ({ - open: openMock, - focus: focusMock, - })) - buttonComponent.buttonClick.emit() + component.mapConfig = MAP_CONFIG_FIXTURE + component.link = { + url: new URL( + 'http://example.com/ows?service=wfs&request=getcapabilities' + ), + name: 'layername', + type: 'service', + accessServiceProtocol: 'wfs', + } + fixture.detectChanges() }) + it('sets externalViewer to display button to true', () => { + expect(component.externalViewer).toEqual(true) + }) + describe('click button', () => { + beforeEach(() => { + buttonComponent = fixture.debugElement.query( + By.directive(MockButtonComponent) + ).componentInstance + componentSpy = jest.spyOn(component, 'openInExternalViewer') + windowSpy = jest + .spyOn(global, 'window', 'get') + .mockImplementation(() => ({ + open: openMock, + focus: focusMock, + })) + buttonComponent.buttonClick.emit() + }) - afterEach(() => { - componentSpy.mockRestore() - windowSpy.mockRestore() + afterEach(() => { + componentSpy.mockRestore() + windowSpy.mockRestore() + }) + it('calls openInExternalViewer', () => { + expect(component.openInExternalViewer).toHaveBeenCalled() + }) + it('opens window in new tab with URL including WFS link params', () => { + expect(openMock).toHaveBeenCalledWith( + 'https://example.com/myviewer?url=http%3A%2F%2Fexample.com%2Fows%3Fservice%3Dwfs%26request%3Dgetcapabilities&name=layername&type=wfs', + '_blank' + ) + }) + it('focuses window', () => { + expect(focusMock).toHaveBeenCalled() + }) }) - it('calls openInExternalViewer', () => { - expect(component.openInExternalViewer).toHaveBeenCalled() + }) + describe('with mapConfig and GEOJSON link', () => { + beforeEach(() => { + component.mapConfig = MAP_CONFIG_FIXTURE + component.link = { + url: new URL('http://example.com/somespatialdata.geojson'), + type: 'download', + mimeType: 'application/vnd.geo+json', + } + fixture.detectChanges() }) - it('opens window in new tab with URL including WMS link params', () => { - expect(openMock).toHaveBeenCalledWith( - 'https://example.com/myviewer?url=http%3A%2F%2Fexample.com%2Fows%3Fservice%3Dwms%26request%3Dgetcapabilities&name=layername&type=wms', - '_blank' - ) + it('sets externalViewer to display button to true', () => { + expect(component.externalViewer).toEqual(true) }) - it('focuses window', () => { - expect(focusMock).toHaveBeenCalled() + describe('click button', () => { + beforeEach(() => { + buttonComponent = fixture.debugElement.query( + By.directive(MockButtonComponent) + ).componentInstance + componentSpy = jest.spyOn(component, 'openInExternalViewer') + windowSpy = jest + .spyOn(global, 'window', 'get') + .mockImplementation(() => ({ + open: openMock, + focus: focusMock, + })) + buttonComponent.buttonClick.emit() + }) + + afterEach(() => { + componentSpy.mockRestore() + windowSpy.mockRestore() + }) + it('calls openInExternalViewer', () => { + expect(component.openInExternalViewer).toHaveBeenCalled() + }) + it('opens window in new tab with URL including link params', () => { + expect(openMock).toHaveBeenCalledWith( + 'https://example.com/myviewer?url=http%3A%2F%2Fexample.com%2Fsomespatialdata.geojson&name=externalviewer.dataset.unnamed&type=geojson', + '_blank' + ) + }) + it('focuses window', () => { + expect(focusMock).toHaveBeenCalled() + }) }) }) }) - describe('with mapConfig and non WMS link', () => { + describe('with mapConfig and invalid external link (non WMS/WFS/GEOJSON)', () => { beforeEach(() => { component.mapConfig = MAP_CONFIG_FIXTURE component.link = { 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 9724ffcd44..4f5f5040a3 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,6 +1,11 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { MapConfig } from '@geonetwork-ui/util/app-config' import { DatasetDistribution } from '@geonetwork-ui/common/domain/model/record' +import { marker } from '@biesbjerg/ngx-translate-extract-marker' +import { TranslateService } from '@ngx-translate/core' +import { getFileFormat } from '@geonetwork-ui/util/shared' + +marker('externalviewer.dataset.unnamed') @Component({ selector: 'gn-ui-external-viewer-button', @@ -31,14 +36,24 @@ export class ExternalViewerButtonComponent { if (this.link.accessServiceProtocol === 'wfs') { return 'wfs' } + } else if ( + this.link.type === 'download' && + getFileFormat(this.link) === 'geojson' + ) { + return 'geojson' } return null } + constructor(private translateService: TranslateService) {} + openInExternalViewer() { const templateUrl = this.mapConfig.EXTERNAL_VIEWER_URL_TEMPLATE + const layerName = this.link.name + ? this.link.name + : this.translateService.instant('externalviewer.dataset.unnamed') const url = templateUrl - .replace('${layer_name}', `${this.link.name}`) + .replace('${layer_name}', `${layerName}`) .replace( '${service_url}', `${encodeURIComponent(this.link.url.toString())}` diff --git a/translations/de.json b/translations/de.json index 6ecb5d6d4a..19b0f084f7 100644 --- a/translations/de.json +++ b/translations/de.json @@ -139,6 +139,7 @@ "downloads.format.unknown": "unbekannt", "downloads.wfs.featuretype.not.found": "Die Schicht wurde nicht gefunden", "dropFile": "Datei ablegen", + "externalviewer.dataset.unnamed": "Datensatz aus dem Datahub", "facets.block.title.OrgForResource": "Organisation", "facets.block.title.availableInServices": "Verfügbar für", "facets.block.title.cl_hierarchyLevel.key": "Ressourcentyp", diff --git a/translations/en.json b/translations/en.json index cce6353f66..8f57c9194b 100644 --- a/translations/en.json +++ b/translations/en.json @@ -139,6 +139,7 @@ "downloads.format.unknown": "unknown", "downloads.wfs.featuretype.not.found": "The layer was not found", "dropFile": "drop file", + "externalviewer.dataset.unnamed": "Datahub layer", "facets.block.title.OrgForResource": "Organisation", "facets.block.title.availableInServices": "Available for", "facets.block.title.cl_hierarchyLevel.key": "Resource type", diff --git a/translations/es.json b/translations/es.json index 9e4bd02a79..15c98afc37 100644 --- a/translations/es.json +++ b/translations/es.json @@ -139,6 +139,7 @@ "downloads.format.unknown": "", "downloads.wfs.featuretype.not.found": "", "dropFile": "", + "externalviewer.dataset.unnamed": "", "facets.block.title.OrgForResource": "", "facets.block.title.availableInServices": "", "facets.block.title.cl_hierarchyLevel.key": "", diff --git a/translations/fr.json b/translations/fr.json index 795fff860d..5effc58ff8 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -139,6 +139,7 @@ "downloads.format.unknown": "inconnu", "downloads.wfs.featuretype.not.found": "La couche n'a pas été retrouvée", "dropFile": "Faites glisser votre fichier", + "externalviewer.dataset.unnamed": "Couche du datahub", "facets.block.title.OrgForResource": "Organisation", "facets.block.title.availableInServices": "Disponible pour", "facets.block.title.cl_hierarchyLevel.key": "Type de ressource", diff --git a/translations/it.json b/translations/it.json index b5c7f49092..8788edc594 100644 --- a/translations/it.json +++ b/translations/it.json @@ -139,6 +139,7 @@ "downloads.format.unknown": "", "downloads.wfs.featuretype.not.found": "", "dropFile": "", + "externalviewer.dataset.unnamed": "", "facets.block.title.OrgForResource": "", "facets.block.title.availableInServices": "", "facets.block.title.cl_hierarchyLevel.key": "", diff --git a/translations/nl.json b/translations/nl.json index 2176b77624..d6d7a915e3 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -139,6 +139,7 @@ "downloads.format.unknown": "", "downloads.wfs.featuretype.not.found": "", "dropFile": "", + "externalviewer.dataset.unnamed": "", "facets.block.title.OrgForResource": "", "facets.block.title.availableInServices": "", "facets.block.title.cl_hierarchyLevel.key": "", diff --git a/translations/pt.json b/translations/pt.json index 4a2bdba25c..3d3533de4b 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -139,6 +139,7 @@ "downloads.format.unknown": "", "downloads.wfs.featuretype.not.found": "", "dropFile": "", + "externalviewer.dataset.unnamed": "", "facets.block.title.OrgForResource": "", "facets.block.title.availableInServices": "", "facets.block.title.cl_hierarchyLevel.key": "", diff --git a/translations/sk.json b/translations/sk.json index c43cce6973..160673ac77 100644 --- a/translations/sk.json +++ b/translations/sk.json @@ -139,6 +139,7 @@ "downloads.format.unknown": "neznámy", "downloads.wfs.featuretype.not.found": "Vrstva nebola nájdená", "dropFile": "nahrať súbor", + "externalviewer.dataset.unnamed": "", "facets.block.title.OrgForResource": "Organizácia", "facets.block.title.availableInServices": "Dostupné pre", "facets.block.title.cl_hierarchyLevel.key": "Typ zdroja", From 387b474d8118ab48d99af51599e8294a3812ec0a Mon Sep 17 00:00:00 2001 From: Tobias Kohr Date: Tue, 19 Dec 2023 15:56:06 +0100 Subject: [PATCH 2/2] chore(external-viewer): use mapstore template syntax in tests --- .../external-viewer-button.component.spec.ts | 6 +++--- libs/util/app-config/src/lib/fixtures.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) 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 4a26d8f983..392d419304 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 @@ -89,7 +89,7 @@ describe('ExternalViewerButtonComponent', () => { }) it('opens window in new tab with URL including WMS link params', () => { expect(openMock).toHaveBeenCalledWith( - 'https://example.com/myviewer?url=http%3A%2F%2Fexample.com%2Fows%3Fservice%3Dwms%26request%3Dgetcapabilities&name=layername&type=wms', + 'https://example.com/myviewer/#/?actions=[{"type":"CATALOG:ADD_LAYERS_FROM_CATALOGS","layers":["layername"],"sources":[{"url":"http%3A%2F%2Fexample.com%2Fows%3Fservice%3Dwms%26request%3Dgetcapabilities","type":"wms"}]}]', '_blank' ) }) @@ -138,7 +138,7 @@ describe('ExternalViewerButtonComponent', () => { }) it('opens window in new tab with URL including WFS link params', () => { expect(openMock).toHaveBeenCalledWith( - 'https://example.com/myviewer?url=http%3A%2F%2Fexample.com%2Fows%3Fservice%3Dwfs%26request%3Dgetcapabilities&name=layername&type=wfs', + 'https://example.com/myviewer/#/?actions=[{"type":"CATALOG:ADD_LAYERS_FROM_CATALOGS","layers":["layername"],"sources":[{"url":"http%3A%2F%2Fexample.com%2Fows%3Fservice%3Dwfs%26request%3Dgetcapabilities","type":"wfs"}]}]', '_blank' ) }) @@ -184,7 +184,7 @@ describe('ExternalViewerButtonComponent', () => { }) it('opens window in new tab with URL including link params', () => { expect(openMock).toHaveBeenCalledWith( - 'https://example.com/myviewer?url=http%3A%2F%2Fexample.com%2Fsomespatialdata.geojson&name=externalviewer.dataset.unnamed&type=geojson', + 'https://example.com/myviewer/#/?actions=[{"type":"CATALOG:ADD_LAYERS_FROM_CATALOGS","layers":["externalviewer.dataset.unnamed"],"sources":[{"url":"http%3A%2F%2Fexample.com%2Fsomespatialdata.geojson","type":"geojson"}]}]', '_blank' ) }) diff --git a/libs/util/app-config/src/lib/fixtures.ts b/libs/util/app-config/src/lib/fixtures.ts index 343595bd67..243a955c65 100644 --- a/libs/util/app-config/src/lib/fixtures.ts +++ b/libs/util/app-config/src/lib/fixtures.ts @@ -82,7 +82,7 @@ export const MAP_CONFIG_FIXTURE: MapConfig = { MAX_EXTENT: [-418263.418776, 5251529.591305, 961272.067714, 6706890.609855], DO_NOT_USE_DEFAULT_BASEMAP: false, EXTERNAL_VIEWER_URL_TEMPLATE: - 'https://example.com/myviewer?url=${service_url}&name=${layer_name}&type=${service_type}', + 'https://example.com/myviewer/#/?actions=[{"type":"CATALOG:ADD_LAYERS_FROM_CATALOGS","layers":["${layer_name}"],"sources":[{"url":"${service_url}","type":"${service_type}"}]}]', EXTERNAL_VIEWER_OPEN_NEW_TAB: true, MAP_LAYERS: [ {