diff --git a/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts b/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts index 8c0f0dda1c..fab1ab8650 100644 --- a/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts +++ b/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts @@ -641,14 +641,16 @@ describe('api form', () => { cy.get('@secondInput').clear() cy.get('@secondInput').type('87') - cy.get('@apiForm').find('gn-ui-dropdown-selector').click() - cy.get('button[data-cy-value="csv"]').click() + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(3000) + cy.get('@apiForm').find('gn-ui-dropdown-selector').as('dropdown') + cy.get('@dropdown').eq(0).selectDropdownOption('geojson') cy.get('@apiForm') .find('gn-ui-copy-text-button') .find('input') .invoke('val') - .should('include', 'offset=87&limit=54&f=csv') + .should('include', 'offset=87&limit=54&f=geojson') cy.get('@apiForm').children('div').first().find('button').first().click() diff --git a/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html b/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html index 10685f6e26..2c9bd0881f 100644 --- a/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +++ b/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html @@ -55,7 +55,7 @@ extraBtnClass="secondary min-w-full !w-40 !text-black" [showTitle]="false" class="text-black" - [choices]="formatsList" + [choices]="outputFormats" (selectValue)="setFormat($event)" [selected]="format$ | async" > diff --git a/libs/ui/elements/src/lib/record-api-form/record-api-form.component.spec.ts b/libs/ui/elements/src/lib/record-api-form/record-api-form.component.spec.ts index 6bbafbd1ae..d49207bf23 100644 --- a/libs/ui/elements/src/lib/record-api-form/record-api-form.component.spec.ts +++ b/libs/ui/elements/src/lib/record-api-form/record-api-form.component.spec.ts @@ -11,6 +11,26 @@ const mockDatasetServiceDistribution: DatasetServiceDistribution = { accessServiceProtocol: 'ogcFeatures', } +jest.mock('@camptocamp/ogc-client', () => ({ + OgcApiEndpoint: class { + constructor(private url) {} + get featureCollections() { + return Promise.resolve(['feature1']) + } + getCollectionInfo(collectionId) { + return Promise.resolve({ + id: collectionId, + itemFormats: [ + 'application/geo+json', + 'application/json', + 'text/csv', + 'application/json', + ], + }) + } + }, +})) + describe('RecordApFormComponent', () => { let component: RecordApiFormComponent let fixture: ComponentFixture @@ -87,4 +107,19 @@ describe('RecordApFormComponent', () => { expect(component.format$.getValue()).toBe('json') }) }) + + describe('#parseOutputFormats', () => { + beforeEach(() => { + const url = 'https://api.example.com/data?' + component.apiBaseUrl = url + }) + it('should parse the returned formats', () => { + component.parseOutputFormats() + expect(component.outputFormats).toEqual([ + { value: 'csv', label: 'CSV' }, + { value: 'geojson', label: 'GEOJSON' }, + { value: 'json', label: 'JSON' }, + ]) + }) + }) }) diff --git a/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts b/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts index 3874b834d7..96f8f375ff 100644 --- a/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +++ b/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts @@ -1,5 +1,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { OgcApiEndpoint } from '@camptocamp/ogc-client' import { DatasetServiceDistribution } from '@geonetwork-ui/common/domain/model/record' +import { mimeTypeToFormat } from '@geonetwork-ui/util/shared' import { BehaviorSubject, combineLatest, map } from 'rxjs' const DEFAULT_PARAMS = { @@ -16,16 +18,15 @@ const DEFAULT_PARAMS = { export class RecordApiFormComponent { @Input() set apiLink(value: DatasetServiceDistribution) { this.apiBaseUrl = value ? value.url.href : undefined + this.outputFormats = [{ value: 'json', label: 'JSON' }] + this.parseOutputFormats() this.resetUrl() } offset$ = new BehaviorSubject('') limit$ = new BehaviorSubject('') format$ = new BehaviorSubject('') apiBaseUrl: string - formatsList = [ - { label: 'JSON', value: 'json' }, - { label: 'CSV', value: 'csv' }, - ] + outputFormats = [{ value: 'json', label: 'JSON' }] apiQueryUrl$ = combineLatest([this.offset$, this.limit$, this.format$]).pipe( map(([offset, limit, format]) => { let outputUrl @@ -70,4 +71,39 @@ export class RecordApiFormComponent { this.limit$.next(DEFAULT_PARAMS.LIMIT) this.format$.next(DEFAULT_PARAMS.FORMAT) } + + parseOutputFormats() { + const apiUrl = + this.apiBaseUrl.slice(-1) === '?' + ? this.apiBaseUrl.slice(0, -1) + : this.apiBaseUrl + + this.getOutputFormats(apiUrl).then((outputFormats) => { + const formatsList = outputFormats.itemFormats.map((format) => { + const normalizedFormat = mimeTypeToFormat(format) + if (normalizedFormat) { + return { + label: normalizedFormat?.toUpperCase(), + value: normalizedFormat, + } + } + return null + }) + this.outputFormats = this.outputFormats.concat( + formatsList.filter(Boolean) + ) + this.outputFormats = this.outputFormats + .filter( + (format, index, self) => + index === self.findIndex((t) => t.value === format.value) + ) + .sort((a, b) => a.label.localeCompare(b.label)) + }) + } + + async getOutputFormats(url) { + const endpoint = await new OgcApiEndpoint(url) + const firstCollection = (await endpoint.featureCollections)[0] + return endpoint.getCollectionInfo(firstCollection) + } } diff --git a/libs/util/shared/src/lib/links/link-utils.ts b/libs/util/shared/src/lib/links/link-utils.ts index beb551b61a..4c246e8606 100644 --- a/libs/util/shared/src/lib/links/link-utils.ts +++ b/libs/util/shared/src/lib/links/link-utils.ts @@ -94,6 +94,27 @@ export const FORMATS = { color: '#de630b', mimeTypes: ['application/x-dxf', 'image/x-dxf'], }, + html: { + extensions: ['html', 'htm'], + priority: 12, + color: '#f2bb3a', + mimeTypes: ['text/html'], + }, + fgb: { + extensions: ['fgb', 'flatgeobuf'], + priority: 13, + color: '#f2bb3a', + mimeTypes: ['application/flatgeobuf'], + }, + jsonfg: { + extensions: ['jsonfg', 'jsonfgc'], + priority: 14, + color: '#f2bb3a', + mimeTypes: [ + 'application/vnd.ogc.fg+json', + 'application/vnd.ogc.fg+json;compatibility=geojson', + ], + }, } as const export type FileFormat = keyof typeof FORMATS diff --git a/package-lock.json b/package-lock.json index cd02022ee7..31dbb3b45a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "@angular/router": "16.1.7", "@bartholomej/ngx-translate-extract": "^8.0.2", "@biesbjerg/ngx-translate-extract-marker": "^1.0.0", - "@camptocamp/ogc-client": "^1.1.0-RC.3", + "@camptocamp/ogc-client": "^1.1.0", "@geospatial-sdk/geocoding": "^0.0.5-alpha.2", "@ltd/j-toml": "~1.35.2", "@messageformat/core": "^3.0.1", @@ -3652,9 +3652,9 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@camptocamp/ogc-client": { - "version": "1.1.0-RC.3", - "resolved": "https://registry.npmjs.org/@camptocamp/ogc-client/-/ogc-client-1.1.0-RC.3.tgz", - "integrity": "sha512-XZJwp0vxTQGtJD3t4GdTHJDLTidlPmv0sBvXskEt0A0cmrdaGUgBqr8KPeDfhjZfq99WFcXv/Gb3+hQXA0+LmQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@camptocamp/ogc-client/-/ogc-client-1.1.0.tgz", + "integrity": "sha512-+Vj4G1D6YNxqRsKtdCA6fWHlFjNJxdK8xRbnXlgJwfRNtFxK78qkPeAuN82hxjgZrEmAOQzPZWgELDAjDq2UAQ==", "dependencies": { "@rgrove/parse-xml": "^4.1.0" }, diff --git a/package.json b/package.json index dabc795ef1..49058af098 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "@angular/router": "16.1.7", "@bartholomej/ngx-translate-extract": "^8.0.2", "@biesbjerg/ngx-translate-extract-marker": "^1.0.0", - "@camptocamp/ogc-client": "^1.1.0-RC.3", + "@camptocamp/ogc-client": "^1.1.0", "@geospatial-sdk/geocoding": "^0.0.5-alpha.2", "@ltd/j-toml": "~1.35.2", "@messageformat/core": "^3.0.1", diff --git a/support-services/docker-entrypoint-initdb.d/dump b/support-services/docker-entrypoint-initdb.d/dump index 2778ff3395..6843f2b4b3 100644 Binary files a/support-services/docker-entrypoint-initdb.d/dump and b/support-services/docker-entrypoint-initdb.d/dump differ