diff --git a/libs/ui/elements/src/lib/api-card/api-card.component.ts b/libs/ui/elements/src/lib/api-card/api-card.component.ts index 3b47fc48a5..90cdaae443 100644 --- a/libs/ui/elements/src/lib/api-card/api-card.component.ts +++ b/libs/ui/elements/src/lib/api-card/api-card.component.ts @@ -26,7 +26,8 @@ export class ApiCardComponent implements OnInit, OnChanges { ngOnInit() { this.displayApiFormButton = - this.link.accessServiceProtocol === 'ogcFeatures' ? true : false + this.link.accessServiceProtocol === 'ogcFeatures' || + this.link.accessServiceProtocol === 'wfs' } ngOnChanges(changes: SimpleChanges) { 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 2c9bd0881f..81e72b4daa 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 @@ -37,15 +37,31 @@ -
record.metadata.api.form.offset
-+ record.metadata.api.form.offset +
+record.metadata.api.form.type
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 53b718f21c..65b193e11b 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 @@ -29,6 +29,30 @@ jest.mock('@camptocamp/ogc-client', () => ({ }) } }, + WfsEndpoint: class { + constructor(private url) {} + async isReady() { + return Promise.resolve(true) + } + getFeatureUrl(featureType, options) { + return `${this.url}?type=${featureType}&options=${JSON.stringify( + options + )}` + } + getServiceInfo() { + return Promise.resolve({ + outputFormats: [ + 'application/geo+json', + 'application/json', + 'text/csv', + 'application/json', + ], + }) + } + supportsStartIndex() { + return true + } + }, })) describe('RecordApFormComponent', () => { @@ -122,6 +146,29 @@ describe('RecordApFormComponent', () => { ]) }) }) + + describe('When panel is opened and accessServiceProtocol is wfs', () => { + beforeEach(() => { + component.apiLink = { + ...mockDatasetServiceDistribution, + accessServiceProtocol: 'wfs', + } + fixture.detectChanges() + }) + + it('should set the links and initial values correctly', async () => { + expect(component.apiBaseUrl).toBe('https://api.example.com/data') + expect(component.accessServiceProtocol).toBe('wfs') + expect(component.offset$.getValue()).toBe('') + expect(component.limit$.getValue()).toBe('-1') + expect(component.format$.getValue()).toBe('json') + const url = await firstValueFrom(component.apiQueryUrl$) + expect(url).toBe( + 'https://api.example.com/data?type=undefined&options={"outputFormat":"json","startIndex":0}' + ) + }) + }) + describe('When apiLink input is undefined', () => { it('should not call parseOutputFormats()', () => { const spy = jest.spyOn(component, 'parseOutputFormats') 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 2377c20495..15178539a1 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,8 +1,11 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { OgcApiEndpoint } from '@camptocamp/ogc-client' -import { DatasetServiceDistribution } from '@geonetwork-ui/common/domain/model/record' +import { OgcApiEndpoint, WfsEndpoint } from '@camptocamp/ogc-client' +import { + DatasetServiceDistribution, + ServiceProtocol, +} from '@geonetwork-ui/common/domain/model/record' import { mimeTypeToFormat } from '@geonetwork-ui/util/shared' -import { BehaviorSubject, combineLatest, map } from 'rxjs' +import { BehaviorSubject, combineLatest, map, switchMap } from 'rxjs' const DEFAULT_PARAMS = { OFFSET: '', @@ -18,19 +21,26 @@ const DEFAULT_PARAMS = { export class RecordApiFormComponent { @Input() set apiLink(value: DatasetServiceDistribution) { this.outputFormats = [{ value: 'json', label: 'JSON' }] + this.accessServiceProtocol = value ? value.accessServiceProtocol : undefined + this.apiFeatureType = value ? value.name : undefined if (value) { this.apiBaseUrl = value.url.href this.parseOutputFormats() } this.resetUrl() } + offset$ = new BehaviorSubject('') limit$ = new BehaviorSubject('') format$ = new BehaviorSubject('') apiBaseUrl: string + apiFeatureType: string + supportOffset = true + accessServiceProtocol: ServiceProtocol | undefined outputFormats = [{ value: 'json', label: 'JSON' }] + apiQueryUrl$ = combineLatest([this.offset$, this.limit$, this.format$]).pipe( - map(([offset, limit, format]) => { + switchMap(async ([offset, limit, format]) => { let outputUrl if (this.apiBaseUrl) { const url = new URL(this.apiBaseUrl) @@ -44,6 +54,20 @@ export class RecordApiFormComponent { } outputUrl = url.toString() } + + if (this.accessServiceProtocol === 'wfs') { + const wfsEndpoint = new WfsEndpoint(this.apiBaseUrl) + if (await wfsEndpoint.isReady()) { + const options = { + outputFormat: format, + startIndex: Number(offset), + } + if (limit !== '-1') { + options['maxFeatures'] = Number(limit) + } + outputUrl = wfsEndpoint.getFeatureUrl(this.apiFeatureType, options) + } + } return outputUrl }) ) @@ -80,32 +104,49 @@ export class RecordApiFormComponent { ? 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, - } + this.getOutputFormats(apiUrl, this.accessServiceProtocol).then( + (outputFormats) => { + let formatsList = [] + if ('itemFormats' in outputFormats) { + formatsList = this.mapFormats(outputFormats.itemFormats) + } else if ('outputFormats' in outputFormats) { + formatsList = this.mapFormats(outputFormats.outputFormats) } - 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) + this.outputFormats = this.outputFormats.concat( + formatsList.filter(Boolean) ) - .sort((a, b) => a.label.localeCompare(b.label)) + this.outputFormats = this.outputFormats + .filter( + (format, index, self) => + index === self.findIndex((t) => t.value === format.value) + ) + .sort((a, b) => a.label.localeCompare(b.label)) + } + ) + } + + mapFormats(formats: any[]) { + return formats.map((format) => { + const normalizedFormat = mimeTypeToFormat(format) + if (normalizedFormat) { + return { + label: normalizedFormat.toUpperCase(), + value: normalizedFormat, + } + } + return null }) } - async getOutputFormats(url) { - const endpoint = await new OgcApiEndpoint(url) - const firstCollection = (await endpoint.featureCollections)[0] - return endpoint.getCollectionInfo(firstCollection) + async getOutputFormats(url: string, accessServiceProtocol: string) { + if (accessServiceProtocol === 'wfs') { + const endpoint = await new WfsEndpoint(url).isReady() + this.supportOffset = endpoint.supportsStartIndex() + return endpoint.getServiceInfo() + } else { + const endpoint = await new OgcApiEndpoint(url) + const firstCollection = (await endpoint.featureCollections)[0] + return endpoint.getCollectionInfo(firstCollection) + } } } diff --git a/libs/ui/inputs/src/lib/text-input/text-input.component.html b/libs/ui/inputs/src/lib/text-input/text-input.component.html index df92d6bbb3..4aace20e25 100644 --- a/libs/ui/inputs/src/lib/text-input/text-input.component.html +++ b/libs/ui/inputs/src/lib/text-input/text-input.component.html @@ -8,4 +8,5 @@ [placeholder]="hint" [attr.aria-label]="hint" [attr.required]="required || null" + [disabled]="disabled" /> diff --git a/libs/ui/inputs/src/lib/text-input/text-input.component.stories.ts b/libs/ui/inputs/src/lib/text-input/text-input.component.stories.ts index c615b67a76..c7c51d4283 100644 --- a/libs/ui/inputs/src/lib/text-input/text-input.component.stories.ts +++ b/libs/ui/inputs/src/lib/text-input/text-input.component.stories.ts @@ -12,6 +12,7 @@ export const Primary: StoryObj