geonetwork-ui 2.11.0-dev.fb3fea50c → 2.11.0-dev.fb48cfeee
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/geonetwork-ui.mjs +1675 -1322
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +205 -111
- package/index.d.ts.map +1 -1
- package/package.json +6 -5
- package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +2 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/mel.records.dataset+featurecatalog.ts +395 -0
- package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +35 -8
- package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +19 -0
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +17 -6
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +110 -0
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +131 -0
- package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +9 -0
- package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +2 -1
- package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +67 -10
- package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +83 -35
- package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +11 -0
- package/src/libs/common/domain/src/lib/model/record/flags.model.ts +1 -0
- package/src/libs/common/domain/src/lib/model/record/index.ts +1 -0
- package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +14 -18
- package/src/libs/common/domain/src/lib/model/search/aggregation.model.ts +2 -0
- package/src/libs/common/domain/src/lib/model/search/filter.model.ts +12 -2
- package/src/libs/common/domain/src/lib/platform.service.interface.ts +1 -0
- package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +20 -1
- package/src/libs/common/fixtures/src/lib/records.fixtures.ts +42 -62
- package/src/libs/feature/dataviz/src/lib/service/data.service.ts +32 -19
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints/form-field-constraints.component.ts +3 -5
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +2 -2
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-record-flags/form-field-record-flag-component.html +18 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-record-flags/form-field-record-flag-component.ts +86 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +100 -88
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +11 -4
- package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +18 -4
- package/src/libs/feature/editor/src/lib/expressions.ts +67 -0
- package/src/libs/feature/editor/src/lib/fields.config.ts +13 -0
- package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +10 -4
- package/src/libs/feature/map/src/index.ts +0 -2
- package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
- package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +8 -3
- package/src/libs/feature/record/src/lib/state/mdview.effects.ts +3 -1
- package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -1
- package/src/libs/feature/search/src/index.ts +2 -0
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +7 -1
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +26 -4
- package/src/libs/feature/search/src/lib/geocoding/geocoding.service.ts +105 -0
- package/src/libs/feature/search/src/lib/location-search/location-search.component.html +18 -0
- package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +114 -0
- package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +1 -0
- package/src/libs/feature/search/src/lib/state/reducer.ts +1 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +56 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.ts +58 -16
- package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.html +3 -3
- package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.ts +2 -2
- package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
- package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.css +0 -4
- package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +12 -3
- package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +11 -3
- package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
- package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
- package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
- package/src/libs/ui/inputs/src/lib/search-feature-catalog/search-feature-catalog.component.ts +2 -1
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +8 -2
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +34 -5
- package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
- package/src/libs/util/app-config/src/lib/app-config.ts +34 -0
- package/src/libs/util/app-config/src/lib/fixtures.ts +37 -1
- package/src/libs/util/app-config/src/lib/model.ts +24 -1
- package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
- package/src/libs/util/shared/src/lib/utils/geojson.ts +16 -0
- package/translations/de.json +12 -2
- package/translations/en.json +14 -4
- package/translations/es.json +12 -2
- package/translations/fr.json +19 -9
- package/translations/it.json +12 -2
- package/translations/nl.json +12 -2
- package/translations/pt.json +12 -2
- package/translations/sk.json +12 -2
- package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
- package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
- package/src/libs/feature/map/src/lib/geocoding.service.ts +0 -57
- package/src/libs/feature/map/src/lib/style/index.ts +0 -2
- package/src/libs/feature/map/src/lib/style/map-style.fixtures.ts +0 -47
- package/src/libs/feature/map/src/lib/style/map-style.service.ts +0 -108
- /package/src/libs/feature/{map/src/lib/geocoding/geocoding.component.css → editor/src/lib/components/record-form/form-field/form-field-record-flags/form-field-record-flag-component.css} +0 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CatalogRecord,
|
|
3
|
+
DatasetFeatureAttribute,
|
|
4
|
+
DatasetFeatureAttributeValue,
|
|
5
|
+
DatasetFeatureType,
|
|
3
6
|
DatasetRecord,
|
|
4
7
|
Individual,
|
|
5
8
|
LanguageCode,
|
|
@@ -616,3 +619,131 @@ export function writeAssociatedRecords(
|
|
|
616
619
|
)
|
|
617
620
|
)(rootEl)
|
|
618
621
|
}
|
|
622
|
+
|
|
623
|
+
function createNilElement(name: string, nilReason: string) {
|
|
624
|
+
return pipe(createElement(name), writeAttribute('gco:nilReason', nilReason))
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function createFeatureCatalogueShell() {
|
|
628
|
+
return pipe(
|
|
629
|
+
createNestedElement(
|
|
630
|
+
'mdb:contentInfo',
|
|
631
|
+
'mrc:MD_FeatureCatalogue',
|
|
632
|
+
'mrc:featureCatalogue',
|
|
633
|
+
'gfc:FC_FeatureCatalogue'
|
|
634
|
+
),
|
|
635
|
+
// create mandatory elements which are not part of the model yet
|
|
636
|
+
appendChildren(
|
|
637
|
+
createNilElement('cat:name', 'missing'),
|
|
638
|
+
createNilElement('cat:scope', 'missing'),
|
|
639
|
+
createNilElement('cat:versionNumber', 'missing'),
|
|
640
|
+
createNilElement('cat:versionDate', 'missing'),
|
|
641
|
+
createNilElement('gfc:producer', 'missing')
|
|
642
|
+
)
|
|
643
|
+
)
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function createListedValue(value: DatasetFeatureAttributeValue) {
|
|
647
|
+
return pipe(
|
|
648
|
+
createNestedElement('gfc:listedValue', 'gfc:FC_ListedValue'),
|
|
649
|
+
appendChildren(
|
|
650
|
+
value.label
|
|
651
|
+
? pipe(createElement('gfc:label'), writeCharacterString(value.label))
|
|
652
|
+
: createNilElement('gfc:label', 'missing'),
|
|
653
|
+
value.value
|
|
654
|
+
? pipe(createElement('gfc:code'), writeCharacterString(value.value))
|
|
655
|
+
: null,
|
|
656
|
+
value.description
|
|
657
|
+
? pipe(
|
|
658
|
+
createElement('gfc:definition'),
|
|
659
|
+
writeCharacterString(value.description)
|
|
660
|
+
)
|
|
661
|
+
: null
|
|
662
|
+
)
|
|
663
|
+
)
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function createFeatureAttribute(attribute: DatasetFeatureAttribute) {
|
|
667
|
+
return pipe(
|
|
668
|
+
createNestedElement(
|
|
669
|
+
'gfc:carrierOfCharacteristics',
|
|
670
|
+
'gfc:FC_FeatureAttribute'
|
|
671
|
+
),
|
|
672
|
+
appendChildren(
|
|
673
|
+
pipe(createElement('gfc:memberName'), setTextContent(attribute.name)),
|
|
674
|
+
attribute.description
|
|
675
|
+
? pipe(
|
|
676
|
+
createElement('gfc:definition'),
|
|
677
|
+
writeCharacterString(attribute.description)
|
|
678
|
+
)
|
|
679
|
+
: null,
|
|
680
|
+
createNilElement('gfc:cardinality', 'missing'),
|
|
681
|
+
attribute.label
|
|
682
|
+
? pipe(
|
|
683
|
+
createElement('gfc:designation'),
|
|
684
|
+
writeCharacterString(attribute.label)
|
|
685
|
+
)
|
|
686
|
+
: null,
|
|
687
|
+
attribute.code
|
|
688
|
+
? pipe(createElement('gfc:code'), writeCharacterString(attribute.code))
|
|
689
|
+
: null,
|
|
690
|
+
attribute.type
|
|
691
|
+
? pipe(
|
|
692
|
+
createNestedElement('gfc:valueType', 'gco:TypeName', 'gco:aName'),
|
|
693
|
+
writeCharacterString(attribute.type)
|
|
694
|
+
)
|
|
695
|
+
: null,
|
|
696
|
+
...(attribute.values ?? [])
|
|
697
|
+
.filter((value) => value.value || value.label)
|
|
698
|
+
.map(createListedValue)
|
|
699
|
+
)
|
|
700
|
+
)
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function createFeatureType(featureType: DatasetFeatureType) {
|
|
704
|
+
return pipe(
|
|
705
|
+
createNestedElement('gfc:featureType', 'gfc:FC_FeatureType'),
|
|
706
|
+
appendChildren(
|
|
707
|
+
pipe(createElement('gfc:typeName'), setTextContent(featureType.name)),
|
|
708
|
+
featureType.description
|
|
709
|
+
? pipe(
|
|
710
|
+
createElement('gfc:definition'),
|
|
711
|
+
writeCharacterString(featureType.description)
|
|
712
|
+
)
|
|
713
|
+
: null,
|
|
714
|
+
pipe(
|
|
715
|
+
createNestedElement('gfc:isAbstract', 'gco:Boolean'),
|
|
716
|
+
setTextContent('false')
|
|
717
|
+
),
|
|
718
|
+
featureType.label
|
|
719
|
+
? pipe(
|
|
720
|
+
createElement('gfc:designation'),
|
|
721
|
+
writeCharacterString(featureType.label)
|
|
722
|
+
)
|
|
723
|
+
: null,
|
|
724
|
+
...featureType.attributes.map(createFeatureAttribute),
|
|
725
|
+
createNilElement('gfc:featureCatalogue', 'missing')
|
|
726
|
+
)
|
|
727
|
+
)
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
export function writeFeatureTypeDescriptions(
|
|
731
|
+
record: DatasetRecord,
|
|
732
|
+
rootEl: XmlElement
|
|
733
|
+
) {
|
|
734
|
+
const featureTypes = record.featureTypeDescriptions ?? []
|
|
735
|
+
const catalogues = findNestedElements(
|
|
736
|
+
'mdb:contentInfo',
|
|
737
|
+
'mrc:MD_FeatureCatalogue',
|
|
738
|
+
'mrc:featureCatalogue',
|
|
739
|
+
'gfc:FC_FeatureCatalogue'
|
|
740
|
+
)(rootEl)
|
|
741
|
+
catalogues.forEach((catalogue) =>
|
|
742
|
+
removeChildrenByName('gfc:featureType')(catalogue)
|
|
743
|
+
)
|
|
744
|
+
if (!featureTypes.length) return
|
|
745
|
+
|
|
746
|
+
const catalogue =
|
|
747
|
+
catalogues[0] ?? appendChildTree(createFeatureCatalogueShell())(rootEl)
|
|
748
|
+
appendChildren(...featureTypes.map(createFeatureType))(catalogue)
|
|
749
|
+
}
|
|
@@ -118,6 +118,7 @@ export class Iso19139Converter extends BaseConverter<string> {
|
|
|
118
118
|
lineage: readLineage,
|
|
119
119
|
sourceRecords: readSourceRecords,
|
|
120
120
|
associatedRecords: readAssociatedRecords,
|
|
121
|
+
featureTypeDescriptions: () => undefined,
|
|
121
122
|
onlineResources: readOnlineResources,
|
|
122
123
|
temporalExtents: readTemporalExtents,
|
|
123
124
|
spatialExtents: readSpatialExtents,
|
|
@@ -161,6 +162,7 @@ export class Iso19139Converter extends BaseConverter<string> {
|
|
|
161
162
|
lineage: writeLineage,
|
|
162
163
|
sourceRecords: writeSourceRecords,
|
|
163
164
|
associatedRecords: writeAssociatedRecords,
|
|
165
|
+
featureTypeDescriptions: () => undefined,
|
|
164
166
|
onlineResources: writeOnlineResources,
|
|
165
167
|
temporalExtents: writeTemporalExtents,
|
|
166
168
|
spatialExtents: writeSpatialExtents,
|
|
@@ -306,6 +308,10 @@ export class Iso19139Converter extends BaseConverter<string> {
|
|
|
306
308
|
const sourceRecords = this.readers['sourceRecords'](rootEl, tr)
|
|
307
309
|
const associatedRecords = this.readers['associatedRecords'](rootEl, tr)
|
|
308
310
|
const updateFrequency = this.readers['updateFrequency'](rootEl, tr)
|
|
311
|
+
const featureTypeDescriptions = this.readers['featureTypeDescriptions'](
|
|
312
|
+
rootEl,
|
|
313
|
+
tr
|
|
314
|
+
) as DatasetRecord['featureTypeDescriptions']
|
|
309
315
|
|
|
310
316
|
return this.afterRecordRead({
|
|
311
317
|
...this.readBaseRecord(rootEl, tr),
|
|
@@ -317,6 +323,7 @@ export class Iso19139Converter extends BaseConverter<string> {
|
|
|
317
323
|
...(spatialRepresentation && { spatialRepresentation }),
|
|
318
324
|
temporalExtents,
|
|
319
325
|
updateFrequency,
|
|
326
|
+
...(featureTypeDescriptions?.length && { featureTypeDescriptions }),
|
|
320
327
|
translations: tr,
|
|
321
328
|
} as DatasetRecord)
|
|
322
329
|
} else if (kind === 'reuse') {
|
|
@@ -416,6 +423,8 @@ export class Iso19139Converter extends BaseConverter<string> {
|
|
|
416
423
|
|
|
417
424
|
if (record.kind === 'dataset') {
|
|
418
425
|
fieldChanged('status') && this.writers['status'](record, rootEl)
|
|
426
|
+
fieldChanged('featureTypeDescriptions') &&
|
|
427
|
+
this.writers['featureTypeDescriptions'](record, rootEl)
|
|
419
428
|
}
|
|
420
429
|
if (record.kind === 'dataset' || record.kind === 'reuse') {
|
|
421
430
|
fieldChanged('updateFrequency') &&
|
|
@@ -69,7 +69,8 @@ export function extractCharacterString(): ChainableFunction<
|
|
|
69
69
|
fallback(
|
|
70
70
|
findChildElement('gco:CharacterString', false),
|
|
71
71
|
findChildElement('gmx:Anchor', false),
|
|
72
|
-
findChildElement('gmx:MimeFileType', false)
|
|
72
|
+
findChildElement('gmx:MimeFileType', false),
|
|
73
|
+
findChildElement('gco:LocalName', false)
|
|
73
74
|
),
|
|
74
75
|
readText()
|
|
75
76
|
)
|
|
@@ -59,6 +59,8 @@ export class ElasticsearchService {
|
|
|
59
59
|
{ script: string; type: 'keyword' | 'date' }
|
|
60
60
|
> = {}
|
|
61
61
|
|
|
62
|
+
private fieldAliases: Record<string, string> = {}
|
|
63
|
+
|
|
62
64
|
// we're using getters in case the defined languages change over time
|
|
63
65
|
private get metadataLang(): LanguageCode {
|
|
64
66
|
const mdLangValue = this.injector.get(METADATA_LANGUAGE, null)
|
|
@@ -155,6 +157,10 @@ export class ElasticsearchService {
|
|
|
155
157
|
this.runtimeFields[fieldName] = { script: expression, type }
|
|
156
158
|
}
|
|
157
159
|
|
|
160
|
+
registerFieldAlias(fieldIdentifier: string, esFieldName: string) {
|
|
161
|
+
this.fieldAliases[fieldIdentifier] = esFieldName
|
|
162
|
+
}
|
|
163
|
+
|
|
158
164
|
getMetadataByIdsPayload(uuids: string[]): EsSearchParams {
|
|
159
165
|
return {
|
|
160
166
|
query: {
|
|
@@ -293,6 +299,56 @@ export class ElasticsearchService {
|
|
|
293
299
|
return Object.values(filters).find(isBoundingBox)
|
|
294
300
|
}
|
|
295
301
|
|
|
302
|
+
// builds a query matching records whose dates intersect the filter range
|
|
303
|
+
private buildDateRangeQuery(searchField: string, dateRange: DateRange) {
|
|
304
|
+
// fields ending with 'DateRange' maps as ES range type
|
|
305
|
+
if (searchField.endsWith('DateRange')) {
|
|
306
|
+
return {
|
|
307
|
+
range: {
|
|
308
|
+
[searchField]: {
|
|
309
|
+
...(dateRange.start && { gte: formatDate(dateRange.start) }),
|
|
310
|
+
...(dateRange.end && { lte: formatDate(dateRange.end) }),
|
|
311
|
+
format: 'yyyy-MM-dd',
|
|
312
|
+
relation: 'intersects',
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const minField = `${searchField}Min`
|
|
318
|
+
const maxField = `${searchField}Max`
|
|
319
|
+
if (minField in this.runtimeFields && maxField in this.runtimeFields) {
|
|
320
|
+
// min/max runtime fields form an interval per record; match if it intersects the filter range
|
|
321
|
+
const filter = [
|
|
322
|
+
dateRange.start && {
|
|
323
|
+
range: {
|
|
324
|
+
[maxField]: {
|
|
325
|
+
gte: formatDate(dateRange.start),
|
|
326
|
+
format: 'yyyy-MM-dd',
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
dateRange.end && {
|
|
331
|
+
range: {
|
|
332
|
+
[minField]: {
|
|
333
|
+
lte: formatDate(dateRange.end),
|
|
334
|
+
format: 'yyyy-MM-dd',
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
].filter(Boolean)
|
|
339
|
+
return { bool: { filter } }
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
range: {
|
|
343
|
+
[searchField]: {
|
|
344
|
+
...(dateRange.start && { gte: formatDate(dateRange.start) }),
|
|
345
|
+
...(dateRange.end && { lte: formatDate(dateRange.end) }),
|
|
346
|
+
format: 'yyyy-MM-dd',
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
296
352
|
private filtersToQuery(
|
|
297
353
|
filters: FieldFilters | FiltersAggregationParams | string,
|
|
298
354
|
spatialFilterExtent = this.findSpatialFilterExtent(filters)
|
|
@@ -325,7 +381,8 @@ export class ElasticsearchService {
|
|
|
325
381
|
JSON.stringify(filters[fieldname]) !== '{}'
|
|
326
382
|
)
|
|
327
383
|
.map(
|
|
328
|
-
(fieldname) =>
|
|
384
|
+
(fieldname) =>
|
|
385
|
+
`${this.fieldAliases[fieldname] ?? fieldname}:(${makeQuery(filters[fieldname])})`
|
|
329
386
|
)
|
|
330
387
|
.join(' AND ')
|
|
331
388
|
if (filters['gn-ui-crossFieldFilter']) {
|
|
@@ -340,15 +397,9 @@ export class ElasticsearchService {
|
|
|
340
397
|
query: queryString,
|
|
341
398
|
},
|
|
342
399
|
},
|
|
343
|
-
...queryRanges.map(([searchField, dateRange]) =>
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
...(dateRange.start && { gte: formatDate(dateRange.start) }),
|
|
347
|
-
...(dateRange.end && { lte: formatDate(dateRange.end) }),
|
|
348
|
-
format: 'yyyy-MM-dd',
|
|
349
|
-
},
|
|
350
|
-
},
|
|
351
|
-
})),
|
|
400
|
+
...queryRanges.map(([searchField, dateRange]) =>
|
|
401
|
+
this.buildDateRangeQuery(searchField, dateRange)
|
|
402
|
+
),
|
|
352
403
|
spatialFilterExtent && {
|
|
353
404
|
geo_shape: {
|
|
354
405
|
geom: {
|
|
@@ -677,6 +728,12 @@ export class ElasticsearchService {
|
|
|
677
728
|
order: {
|
|
678
729
|
[`_${aggregation.sort[1]}`]: aggregation.sort[0],
|
|
679
730
|
},
|
|
731
|
+
...(aggregation.includeValues && {
|
|
732
|
+
include: aggregation.includeValues,
|
|
733
|
+
}),
|
|
734
|
+
...(aggregation.excludeValues && {
|
|
735
|
+
exclude: aggregation.excludeValues,
|
|
736
|
+
}),
|
|
680
737
|
},
|
|
681
738
|
}
|
|
682
739
|
case 'histogram':
|
|
@@ -17,9 +17,9 @@ import {
|
|
|
17
17
|
AssociatedRecord,
|
|
18
18
|
CatalogRecord,
|
|
19
19
|
DatasetFeatureCatalog,
|
|
20
|
-
DatasetFeatureType,
|
|
21
20
|
LanguageCode,
|
|
22
21
|
LinkedRecord,
|
|
22
|
+
RecordFlag,
|
|
23
23
|
RecordRelation,
|
|
24
24
|
} from '../../../../../../libs/common/domain/src/lib/model/record'
|
|
25
25
|
import {
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
LanguagesApiService,
|
|
38
38
|
RecordsApiService,
|
|
39
39
|
SearchApiService,
|
|
40
|
+
TagsApiService,
|
|
40
41
|
} from '../../../../../../libs/data-access/gn4/src'
|
|
41
42
|
import {
|
|
42
43
|
combineLatest,
|
|
@@ -49,7 +50,13 @@ import {
|
|
|
49
50
|
switchMap,
|
|
50
51
|
throwError,
|
|
51
52
|
} from 'rxjs'
|
|
52
|
-
import {
|
|
53
|
+
import {
|
|
54
|
+
catchError,
|
|
55
|
+
map,
|
|
56
|
+
shareReplay,
|
|
57
|
+
tap,
|
|
58
|
+
withLatestFrom,
|
|
59
|
+
} from 'rxjs/operators'
|
|
53
60
|
import { lt } from 'semver'
|
|
54
61
|
import { ElasticsearchService } from './elasticsearch'
|
|
55
62
|
import { toLang2 } from '../../../../../../libs/util/i18n/src'
|
|
@@ -81,6 +88,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
|
|
|
81
88
|
private platformService = inject(PlatformServiceInterface)
|
|
82
89
|
private gn4LanguagesApi = inject(LanguagesApiService)
|
|
83
90
|
private settingsService = inject(Gn4SettingsService)
|
|
91
|
+
private gn4TagsApi = inject(TagsApiService)
|
|
84
92
|
private disableDraft = inject(DISABLE_DRAFT, { optional: true }) ?? false
|
|
85
93
|
private defaultConverter = inject(DEFAULT_RECORD_CONVERTER)
|
|
86
94
|
|
|
@@ -182,43 +190,15 @@ export class Gn4Repository implements RecordsRepositoryInterface {
|
|
|
182
190
|
)
|
|
183
191
|
}
|
|
184
192
|
|
|
185
|
-
|
|
186
|
-
featureTypes: Array<DatasetFeatureType>
|
|
187
|
-
): DatasetFeatureCatalog {
|
|
188
|
-
return {
|
|
189
|
-
featureTypes: featureTypes.map((featureType) => ({
|
|
190
|
-
name: featureType.typeName || '',
|
|
191
|
-
definition: featureType.definition || '',
|
|
192
|
-
attributes: Array.isArray(featureType.attributeTable)
|
|
193
|
-
? featureType.attributeTable.map((attr) => {
|
|
194
|
-
const values = attr.values
|
|
195
|
-
?.filter((v) => v.code || v.label)
|
|
196
|
-
.map((v) => ({
|
|
197
|
-
code: v.code,
|
|
198
|
-
label: v.label,
|
|
199
|
-
}))
|
|
200
|
-
return {
|
|
201
|
-
name: attr.name,
|
|
202
|
-
code: attr.code,
|
|
203
|
-
definition: attr.definition,
|
|
204
|
-
type: attr.type,
|
|
205
|
-
...(values?.length > 0 ? { values } : {}),
|
|
206
|
-
}
|
|
207
|
-
})
|
|
208
|
-
: [],
|
|
209
|
-
})),
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
getFeatureCatalog(
|
|
193
|
+
queryLinkedFeatureCatalog(
|
|
213
194
|
record: CatalogRecord,
|
|
214
195
|
visited: Set<string> = new Set() // prevent looping
|
|
215
196
|
): Observable<DatasetFeatureCatalog | null> {
|
|
216
197
|
if (
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
record.extras['featureTypes'].length > 0
|
|
198
|
+
'featureTypeDescriptions' in record &&
|
|
199
|
+
record.featureTypeDescriptions?.length > 0
|
|
220
200
|
) {
|
|
221
|
-
return of(
|
|
201
|
+
return of({ featureTypes: record.featureTypeDescriptions })
|
|
222
202
|
}
|
|
223
203
|
|
|
224
204
|
const featureCatalogIdentifier = record.extras[
|
|
@@ -228,7 +208,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
|
|
|
228
208
|
visited.add(featureCatalogIdentifier)
|
|
229
209
|
return this.getRecord(featureCatalogIdentifier).pipe(
|
|
230
210
|
switchMap((record) =>
|
|
231
|
-
record ? this.
|
|
211
|
+
record ? this.queryLinkedFeatureCatalog(record, visited) : of(null)
|
|
232
212
|
)
|
|
233
213
|
)
|
|
234
214
|
}
|
|
@@ -704,4 +684,72 @@ export class Gn4Repository implements RecordsRepositoryInterface {
|
|
|
704
684
|
private removeRecordFromLocalStorage(recordId: string): void {
|
|
705
685
|
window.localStorage.removeItem(this.getLocalStorageKeyForRecord(recordId))
|
|
706
686
|
}
|
|
687
|
+
|
|
688
|
+
/*
|
|
689
|
+
* FLAGS
|
|
690
|
+
* Note: in GN4 API, categories are named "tags"
|
|
691
|
+
*/
|
|
692
|
+
|
|
693
|
+
categoriesIdentifierForFlags: Observable<Record<RecordFlag, number>> =
|
|
694
|
+
this.gn4TagsApi.getTags().pipe(
|
|
695
|
+
map((categories) =>
|
|
696
|
+
categories.reduce(
|
|
697
|
+
(acc, category) => {
|
|
698
|
+
if (category.name.startsWith('geonetwork-ui-flag:')) {
|
|
699
|
+
acc[
|
|
700
|
+
category.name.replace(/^geonetwork-ui-flag:/, '') as RecordFlag
|
|
701
|
+
] = category.id
|
|
702
|
+
}
|
|
703
|
+
return acc
|
|
704
|
+
},
|
|
705
|
+
{} as Record<RecordFlag, number>
|
|
706
|
+
)
|
|
707
|
+
),
|
|
708
|
+
shareReplay(1)
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
private getCategoryIdForFlag(
|
|
712
|
+
flagName: RecordFlag
|
|
713
|
+
): Observable<number | null> {
|
|
714
|
+
return this.categoriesIdentifierForFlags.pipe(
|
|
715
|
+
map((categories) => {
|
|
716
|
+
const categoryId = categories[flagName]
|
|
717
|
+
if (categoryId === undefined) {
|
|
718
|
+
console.warn(
|
|
719
|
+
`The record flag ${flagName} is not created on GeoNetwork backend; please create a category with the name geonetwork-ui-flag:${flagName} for this to work.`
|
|
720
|
+
)
|
|
721
|
+
return null
|
|
722
|
+
}
|
|
723
|
+
return categoryId
|
|
724
|
+
})
|
|
725
|
+
)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
setRecordFlag(
|
|
729
|
+
uniqueIdentifier: string,
|
|
730
|
+
flagName: RecordFlag,
|
|
731
|
+
value: boolean
|
|
732
|
+
): Observable<void> {
|
|
733
|
+
return this.getCategoryIdForFlag(flagName).pipe(
|
|
734
|
+
switchMap((categoryId) => {
|
|
735
|
+
if (categoryId === null) return of()
|
|
736
|
+
return value
|
|
737
|
+
? this.gn4RecordsApi.tagRecord(uniqueIdentifier, [categoryId], false)
|
|
738
|
+
: this.gn4RecordsApi.deleteTags(uniqueIdentifier, [categoryId])
|
|
739
|
+
})
|
|
740
|
+
)
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
getRecordFlag(
|
|
744
|
+
uniqueIdentifier: string,
|
|
745
|
+
flagName: RecordFlag
|
|
746
|
+
): Observable<boolean> {
|
|
747
|
+
return this.gn4RecordsApi.getRecordTags(uniqueIdentifier).pipe(
|
|
748
|
+
withLatestFrom(this.getCategoryIdForFlag(flagName)),
|
|
749
|
+
map(([tags, categoryId]) => {
|
|
750
|
+
if (categoryId === null) return false
|
|
751
|
+
return Array.from(tags).some((tag) => tag.id === categoryId)
|
|
752
|
+
})
|
|
753
|
+
)
|
|
754
|
+
}
|
|
707
755
|
}
|
|
@@ -92,6 +92,13 @@ export class Gn4PlatformService implements PlatformServiceInterface {
|
|
|
92
92
|
switchMap((apiUser) => this.mapper.userFromMeApi(apiUser)),
|
|
93
93
|
shareReplay({ bufferSize: 1, refCount: true })
|
|
94
94
|
)
|
|
95
|
+
private isAdmin$ = this.disableAuth
|
|
96
|
+
? of(false)
|
|
97
|
+
: of(true).pipe(
|
|
98
|
+
switchMap(() => this.meApi.getMe()),
|
|
99
|
+
map((apiUser) => !!apiUser?.admin),
|
|
100
|
+
shareReplay({ bufferSize: 1, refCount: true })
|
|
101
|
+
)
|
|
95
102
|
|
|
96
103
|
/**
|
|
97
104
|
* A map of already loaded thesauri (groups of keywords); the key is a URI
|
|
@@ -146,6 +153,10 @@ export class Gn4PlatformService implements PlatformServiceInterface {
|
|
|
146
153
|
return this.isUserAnonymous$
|
|
147
154
|
}
|
|
148
155
|
|
|
156
|
+
isAdministrator(): Observable<boolean> {
|
|
157
|
+
return this.isAdmin$
|
|
158
|
+
}
|
|
159
|
+
|
|
149
160
|
getOrganizations(): Observable<Organization[]> {
|
|
150
161
|
return undefined
|
|
151
162
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type RecordFlag = 'IS_REFERENCE_DATASET'
|
|
@@ -316,6 +316,7 @@ export interface DatasetRecord extends BaseRecord {
|
|
|
316
316
|
spatialExtents: Array<DatasetSpatialExtent>
|
|
317
317
|
temporalExtents: Array<DatasetTemporalExtent>
|
|
318
318
|
spatialRepresentation?: SpatialRepresentationType
|
|
319
|
+
featureTypeDescriptions?: Array<DatasetFeatureType>
|
|
319
320
|
}
|
|
320
321
|
|
|
321
322
|
export interface ServiceEndpoint {
|
|
@@ -347,33 +348,28 @@ export interface ReuseRecord extends BaseRecord {
|
|
|
347
348
|
temporalExtents: Array<DatasetTemporalExtent>
|
|
348
349
|
}
|
|
349
350
|
|
|
351
|
+
export interface DatasetFeatureType {
|
|
352
|
+
name: string
|
|
353
|
+
label?: string
|
|
354
|
+
description?: string
|
|
355
|
+
attributes: DatasetFeatureAttribute[]
|
|
356
|
+
}
|
|
350
357
|
export type DatasetFeatureCatalog = {
|
|
351
|
-
featureTypes: Array<
|
|
352
|
-
name: string
|
|
353
|
-
definition: string
|
|
354
|
-
attributes: DatasetFeatureAttribute[]
|
|
355
|
-
}>
|
|
358
|
+
featureTypes: Array<DatasetFeatureType>
|
|
356
359
|
}
|
|
357
360
|
export interface DatasetFeatureAttributeValue {
|
|
358
|
-
|
|
359
|
-
definition?: string
|
|
361
|
+
value: string // empty string when the value has no code
|
|
360
362
|
label?: string
|
|
363
|
+
description?: string
|
|
361
364
|
}
|
|
362
365
|
export interface DatasetFeatureAttribute {
|
|
363
366
|
name: string
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
+
label?: string
|
|
368
|
+
description?: string
|
|
369
|
+
type?: string
|
|
370
|
+
code?: string
|
|
367
371
|
values?: Array<DatasetFeatureAttributeValue>
|
|
368
372
|
}
|
|
369
|
-
export interface DatasetFeatureType {
|
|
370
|
-
aliases: string
|
|
371
|
-
code: string
|
|
372
|
-
isAbstract: string
|
|
373
|
-
typeName: string
|
|
374
|
-
definition: string
|
|
375
|
-
attributeTable: Array<DatasetFeatureAttribute>
|
|
376
|
-
}
|
|
377
373
|
|
|
378
374
|
export type ReuseType = 'application' | 'map' | 'other'
|
|
379
375
|
|
|
@@ -21,7 +21,10 @@ export type QueryString = {
|
|
|
21
21
|
query_string: string
|
|
22
22
|
}
|
|
23
23
|
export type QueryRange = {
|
|
24
|
-
range: Record<
|
|
24
|
+
range: Record<
|
|
25
|
+
string,
|
|
26
|
+
{ format: string; gte?: string; lte?: string; relation?: string }
|
|
27
|
+
>
|
|
25
28
|
}
|
|
26
29
|
export type QueryGeoShape = {
|
|
27
30
|
geo_shape: {
|
|
@@ -31,4 +34,11 @@ export type QueryGeoShape = {
|
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
|
-
export type
|
|
37
|
+
export type QueryBool = {
|
|
38
|
+
bool: {
|
|
39
|
+
filter: QueryRange[]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export type FilterQuery = Array<
|
|
43
|
+
QueryString | QueryRange | QueryGeoShape | QueryBool
|
|
44
|
+
>
|
|
@@ -28,6 +28,7 @@ export abstract class PlatformServiceInterface {
|
|
|
28
28
|
abstract supportsAuthentication(): boolean
|
|
29
29
|
abstract getMe(): Observable<UserModel>
|
|
30
30
|
abstract isAnonymous(): Observable<boolean>
|
|
31
|
+
abstract isAdministrator(): Observable<boolean>
|
|
31
32
|
abstract getUsers(): Observable<UserModel[]>
|
|
32
33
|
abstract getUserPermissionsByGroup(): Observable<GroupModel[]>
|
|
33
34
|
abstract getUsersByOrganization(
|
|
@@ -11,13 +11,14 @@ import {
|
|
|
11
11
|
DatasetFeatureCatalog,
|
|
12
12
|
LanguageCode,
|
|
13
13
|
LinkedRecord,
|
|
14
|
+
RecordFlag,
|
|
14
15
|
} from '../model/record'
|
|
15
16
|
|
|
16
17
|
export abstract class RecordsRepositoryInterface {
|
|
17
18
|
abstract search(params: SearchParams): Observable<SearchResults>
|
|
18
19
|
abstract getMatchesCount(filters: FieldFilters): Observable<number>
|
|
19
20
|
abstract getRecord(uniqueIdentifier: string): Observable<CatalogRecord | null>
|
|
20
|
-
abstract
|
|
21
|
+
abstract queryLinkedFeatureCatalog(
|
|
21
22
|
record: CatalogRecord
|
|
22
23
|
): Observable<DatasetFeatureCatalog | null>
|
|
23
24
|
abstract aggregate(params: AggregationsParams): Observable<Aggregations>
|
|
@@ -106,4 +107,22 @@ export abstract class RecordsRepositoryInterface {
|
|
|
106
107
|
): Observable<{ user: string; date: Date }>
|
|
107
108
|
abstract getRecordPublicationStatus(uuid: string): Observable<boolean>
|
|
108
109
|
abstract getApplicationLanguages(): Observable<LanguageCode[]>
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Sets a given flag on a record. Flags are not part of the metadata document, which is why they are
|
|
113
|
+
* not part of the Record model.
|
|
114
|
+
*/
|
|
115
|
+
abstract setRecordFlag(
|
|
116
|
+
uniqueIdentifier: string,
|
|
117
|
+
flagName: RecordFlag,
|
|
118
|
+
value: boolean
|
|
119
|
+
): Observable<void>
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Checks whether a given flag is enabled for a record.
|
|
123
|
+
*/
|
|
124
|
+
abstract getRecordFlag(
|
|
125
|
+
uniqueIdentifier: string,
|
|
126
|
+
flagName: RecordFlag
|
|
127
|
+
): Observable<boolean>
|
|
109
128
|
}
|