From 456281cb8caf5fc9bc7ae167e5f7c0b0ef6437eb Mon Sep 17 00:00:00 2001 From: ronitjadhav Date: Tue, 14 May 2024 14:31:45 +0200 Subject: [PATCH 1/2] Allow building custom URL for WFS service --- .../src/lib/api-card/api-card.component.ts | 3 +- .../record-api-form.component.html | 40 ++++++-- .../record-api-form.component.spec.ts | 45 +++++++++ .../record-api-form.component.ts | 96 +++++++++++++------ package-lock.json | 8 +- package.json | 2 +- 6 files changed, 152 insertions(+), 42 deletions(-) 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..fd72c253f4 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,37 @@ -
-

record.metadata.api.form.offset

- - +
+

+ record.metadata.api.form.offset +

+
+ + +
+ + warning + +
+
+

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 d49207bf23..254dc496f7 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,4 +146,25 @@ 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}' + ) + }) + }) }) 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 96f8f375ff..c84d81b9ef 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: '', @@ -19,16 +22,24 @@ export class RecordApiFormComponent { @Input() set apiLink(value: DatasetServiceDistribution) { this.apiBaseUrl = value ? value.url.href : undefined this.outputFormats = [{ value: 'json', label: 'JSON' }] - this.parseOutputFormats() + this.accessServiceProtocol = value ? value.accessServiceProtocol : undefined + this.apiFeatureType = value ? value.name : undefined + if (this.apiBaseUrl) { + 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) @@ -42,6 +53,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 }) ) @@ -78,32 +103,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/package-lock.json b/package-lock.json index 31dbb3b45a..1fb333e55d 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", + "@camptocamp/ogc-client": "^1.1.1-dev.ddbb5b0", "@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", - "resolved": "https://registry.npmjs.org/@camptocamp/ogc-client/-/ogc-client-1.1.0.tgz", - "integrity": "sha512-+Vj4G1D6YNxqRsKtdCA6fWHlFjNJxdK8xRbnXlgJwfRNtFxK78qkPeAuN82hxjgZrEmAOQzPZWgELDAjDq2UAQ==", + "version": "1.1.1-dev.ddbb5b0", + "resolved": "https://registry.npmjs.org/@camptocamp/ogc-client/-/ogc-client-1.1.1-dev.ddbb5b0.tgz", + "integrity": "sha512-XBunVVWEh/e/CXpAx9QxysT2v0uNDOKXz4VsFINt5uFXZimovl6wKzgI1pt7lU6gxLuAeWXHnwnLm3XfUHmKag==", "dependencies": { "@rgrove/parse-xml": "^4.1.0" }, diff --git a/package.json b/package.json index 49058af098..3355674655 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", + "@camptocamp/ogc-client": "^1.1.1-dev.ddbb5b0", "@geospatial-sdk/geocoding": "^0.0.5-alpha.2", "@ltd/j-toml": "~1.35.2", "@messageformat/core": "^3.0.1", From 4420fc5536de548111aa55427fe52aa746222b70 Mon Sep 17 00:00:00 2001 From: ronitjadhav Date: Tue, 21 May 2024 14:00:26 +0200 Subject: [PATCH 2/2] Added support for disable on text-input component --- .../lib/record-api-form/record-api-form.component.html | 8 +------- .../inputs/src/lib/text-input/text-input.component.html | 1 + .../src/lib/text-input/text-input.component.stories.ts | 1 + libs/ui/inputs/src/lib/text-input/text-input.component.ts | 1 + 4 files changed, 4 insertions(+), 7 deletions(-) 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 4da868412e..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 @@ -45,9 +45,7 @@ @@ -64,10 +62,6 @@
-

record.metadata.api.form.type

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 = { value: '', hint: 'Put something here!', required: false, + disabled: false, }, argTypes: { valueChange: { diff --git a/libs/ui/inputs/src/lib/text-input/text-input.component.ts b/libs/ui/inputs/src/lib/text-input/text-input.component.ts index cb75c50e76..03560b15fa 100644 --- a/libs/ui/inputs/src/lib/text-input/text-input.component.ts +++ b/libs/ui/inputs/src/lib/text-input/text-input.component.ts @@ -29,6 +29,7 @@ export class TextInputComponent implements AfterViewInit { @Input() extraClass = '' @Input() hint: string @Input() required = false + @Input() disabled: boolean rawChange = new Subject() @Output() valueChange = this.rawChange.pipe(distinctUntilChanged()) @ViewChild('input') input