geonetwork-ui 2.10.0-dev.fc6515c0b → 2.11.0-dev.2478042b2

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.
Files changed (185) hide show
  1. package/fesm2022/geonetwork-ui.mjs +2326 -6755
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +309 -219
  4. package/index.d.ts.map +1 -1
  5. package/package.json +32 -42
  6. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +20 -0
  7. package/src/libs/api/metadata-converter/src/lib/fixtures/eu.dcat-ap.records.ts +4 -0
  8. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +23 -0
  9. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+ongules.ts +8 -0
  10. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+roilaye.ts +2 -0
  11. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +2 -0
  12. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +2 -0
  13. package/src/libs/api/metadata-converter/src/lib/fixtures/georhena.records.ts +2 -0
  14. package/src/libs/api/metadata-converter/src/lib/fixtures/metadata-for-i18n.records.ts +4 -0
  15. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +23 -0
  16. package/src/libs/api/metadata-converter/src/lib/fixtures/opendataswiss.records.ts +2 -0
  17. package/src/libs/api/metadata-converter/src/lib/fixtures/sextant.records.ts +4 -0
  18. package/src/libs/api/metadata-converter/src/lib/fixtures/vlaanderen.dcat-ap.records.ts +2 -0
  19. package/src/libs/api/metadata-converter/src/lib/fixtures/wallonie.records.reuse.ts +9 -0
  20. package/src/libs/api/metadata-converter/src/lib/gn4/atomic-operations.ts +48 -7
  21. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.converter.ts +2 -0
  22. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +49 -18
  23. package/src/libs/api/metadata-converter/src/lib/gn4/metadata-url.service.ts +2 -2
  24. package/src/libs/api/metadata-converter/src/lib/gn4/types/elasticsearch.model.ts +13 -1
  25. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
  26. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +11 -0
  27. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +21 -0
  28. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +52 -0
  29. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +24 -1
  30. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +77 -0
  31. package/src/libs/api/metadata-converter/src/lib/iso19139/utils/association-type.mapper.ts +12 -0
  32. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +79 -0
  33. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +8 -4
  34. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +25 -5
  35. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +106 -56
  36. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +1 -1
  37. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
  38. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +37 -2
  39. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +61 -5
  40. package/src/libs/common/domain/src/lib/model/search/sort-by.model.ts +1 -5
  41. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  42. package/src/libs/common/domain/src/lib/model/user/group.model.ts +8 -0
  43. package/src/libs/common/domain/src/lib/model/user/index.ts +1 -0
  44. package/src/libs/common/domain/src/lib/platform.service.interface.ts +2 -0
  45. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +4 -3
  46. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +62 -0
  47. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  48. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  49. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  50. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  51. package/src/libs/feature/editor/src/index.ts +1 -0
  52. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +4 -1
  53. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -19
  54. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +5 -1
  55. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  56. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.css +0 -0
  57. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +68 -0
  58. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +41 -0
  59. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.html +5 -14
  60. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.ts +3 -28
  61. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +5 -5
  62. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +2 -1
  63. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +4 -1
  64. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.css +0 -0
  65. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.html +33 -0
  66. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.ts +94 -0
  67. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +7 -7
  68. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +9 -2
  69. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +12 -0
  70. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts +1 -1
  71. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  72. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  73. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  74. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.html +16 -12
  75. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  76. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +6 -2
  77. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  78. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +0 -34
  79. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +34 -16
  80. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +17 -0
  81. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  82. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +18 -5
  83. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +76 -19
  84. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  85. package/src/libs/feature/editor/src/lib/fields.config.ts +359 -337
  86. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +10 -3
  87. package/src/libs/feature/editor/src/lib/services/editor.service.ts +9 -13
  88. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  89. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  90. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.css +0 -0
  91. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.html +23 -0
  92. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.ts +114 -0
  93. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +111 -1
  94. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +219 -8
  95. package/src/libs/feature/notify-reuse/src/lib/utils/url.ts +16 -0
  96. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  97. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  98. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  99. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  100. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  101. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  102. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  103. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  104. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  105. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  106. package/src/libs/ui/dataviz/src/index.ts +0 -1
  107. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  108. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  109. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  110. package/src/libs/ui/elements/src/index.ts +1 -0
  111. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +48 -56
  112. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +7 -3
  113. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.html +2 -1
  114. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.ts +2 -2
  115. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  116. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  117. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  118. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.html +14 -0
  119. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.ts +85 -0
  120. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  121. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +5 -0
  122. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +5 -8
  123. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +2 -2
  124. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  125. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  126. package/src/libs/ui/inputs/src/lib/badge/badge.component.html +33 -26
  127. package/src/libs/ui/inputs/src/lib/badge/badge.component.ts +9 -0
  128. package/src/libs/ui/inputs/src/lib/button/button.component.ts +4 -0
  129. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  130. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  131. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  132. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.css +0 -3
  133. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.html +11 -7
  134. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.ts +56 -30
  135. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  136. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  137. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  138. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  139. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  140. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  141. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +4 -54
  142. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  143. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  144. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +12 -8
  145. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  146. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  147. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  148. package/src/libs/ui/widgets/src/lib/spinning-loader/spinning-loader.component.html +1 -1
  149. package/src/libs/util/app-config/src/lib/app-config.ts +35 -10
  150. package/src/libs/util/app-config/src/lib/model.ts +3 -0
  151. package/src/libs/util/app-config/src/lib/parse-utils.ts +27 -0
  152. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  153. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  154. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  155. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  156. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  157. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -39
  158. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  159. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  160. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  161. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  162. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  163. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  164. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +183 -124
  165. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  166. package/src/libs/util/i18n/src/index.ts +0 -1
  167. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  168. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  169. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  170. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  171. package/src/libs/util/shared/src/lib/links/link-utils.ts +23 -1
  172. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  173. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  174. package/style.css +0 -1
  175. package/tailwind.base.css +15 -0
  176. package/translations/de.json +124 -51
  177. package/translations/en.json +129 -56
  178. package/translations/es.json +97 -24
  179. package/translations/fr.json +126 -53
  180. package/translations/it.json +125 -52
  181. package/translations/nl.json +96 -23
  182. package/translations/pt.json +97 -24
  183. package/translations/sk.json +98 -25
  184. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.css +0 -3
  185. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
@@ -21,6 +21,7 @@ import {
21
21
  } from '../xml-utils'
22
22
  import {
23
23
  readAbstract,
24
+ readAssociatedRecords,
24
25
  readContacts,
25
26
  readContactsForResource,
26
27
  readDefaultLanguage,
@@ -30,6 +31,7 @@ import {
30
31
  readLegalConstraints,
31
32
  readLicenses,
32
33
  readLineage,
34
+ readSourceRecords,
33
35
  readOnlineResources,
34
36
  readOtherConstraints,
35
37
  readOtherLanguages,
@@ -52,6 +54,7 @@ import {
52
54
  } from './read-parts'
53
55
  import {
54
56
  writeAbstract,
57
+ writeAssociatedRecords,
55
58
  writeContacts,
56
59
  writeContactsForResource,
57
60
  writeDefaultLanguage,
@@ -62,6 +65,7 @@ import {
62
65
  writeLegalConstraints,
63
66
  writeLicenses,
64
67
  writeLineage,
68
+ writeSourceRecords,
65
69
  writeOnlineResources,
66
70
  writeOtherConstraints,
67
71
  writeRecordUpdated,
@@ -112,6 +116,8 @@ export class Iso19139Converter extends BaseConverter<string> {
112
116
  spatialRepresentation: readSpatialRepresentation,
113
117
  overviews: readOverviews,
114
118
  lineage: readLineage,
119
+ sourceRecords: readSourceRecords,
120
+ associatedRecords: readAssociatedRecords,
115
121
  onlineResources: readOnlineResources,
116
122
  temporalExtents: readTemporalExtents,
117
123
  spatialExtents: readSpatialExtents,
@@ -153,6 +159,8 @@ export class Iso19139Converter extends BaseConverter<string> {
153
159
  spatialRepresentation: writeSpatialRepresentation,
154
160
  overviews: writeGraphicOverviews,
155
161
  lineage: writeLineage,
162
+ sourceRecords: writeSourceRecords,
163
+ associatedRecords: writeAssociatedRecords,
156
164
  onlineResources: writeOnlineResources,
157
165
  temporalExtents: writeTemporalExtents,
158
166
  spatialExtents: writeSpatialExtents,
@@ -201,7 +209,7 @@ export class Iso19139Converter extends BaseConverter<string> {
201
209
  record.contacts.map((c) => fixLanguages(c.organization))
202
210
  record.contactsForResource.map((c) => fixLanguages(c.organization))
203
211
  fixLanguages(record.ownerOrganization)
204
- if (record.kind === 'dataset') {
212
+ if (record.kind === 'dataset' || record.kind === 'reuse') {
205
213
  record.spatialExtents.map(fixLanguages)
206
214
  }
207
215
 
@@ -295,6 +303,8 @@ export class Iso19139Converter extends BaseConverter<string> {
295
303
  )
296
304
  const temporalExtents = this.readers['temporalExtents'](rootEl, tr)
297
305
  const lineage = this.readers['lineage'](rootEl, tr)
306
+ const sourceRecords = this.readers['sourceRecords'](rootEl, tr)
307
+ const associatedRecords = this.readers['associatedRecords'](rootEl, tr)
298
308
  const updateFrequency = this.readers['updateFrequency'](rootEl, tr)
299
309
 
300
310
  return this.afterRecordRead({
@@ -302,6 +312,8 @@ export class Iso19139Converter extends BaseConverter<string> {
302
312
  kind,
303
313
  status,
304
314
  lineage,
315
+ ...(sourceRecords && { sourceRecords }),
316
+ associatedRecords,
305
317
  ...(spatialRepresentation && { spatialRepresentation }),
306
318
  temporalExtents,
307
319
  updateFrequency,
@@ -309,6 +321,8 @@ export class Iso19139Converter extends BaseConverter<string> {
309
321
  } as DatasetRecord)
310
322
  } else if (kind === 'reuse') {
311
323
  const lineage = this.readers['lineage'](rootEl, tr)
324
+ const sourceRecords = this.readers['sourceRecords'](rootEl, tr)
325
+ const associatedRecords = this.readers['associatedRecords'](rootEl, tr)
312
326
  const temporalExtents = this.readers['temporalExtents'](rootEl, tr)
313
327
  const reuseType = this.readers['reuseType'](rootEl, tr)
314
328
 
@@ -316,6 +330,8 @@ export class Iso19139Converter extends BaseConverter<string> {
316
330
  ...this.readBaseRecord(rootEl, tr),
317
331
  kind,
318
332
  lineage,
333
+ ...(sourceRecords && { sourceRecords }),
334
+ associatedRecords,
319
335
  temporalExtents,
320
336
  reuseType,
321
337
  } as ReuseRecord)
@@ -400,6 +416,8 @@ export class Iso19139Converter extends BaseConverter<string> {
400
416
 
401
417
  if (record.kind === 'dataset') {
402
418
  fieldChanged('status') && this.writers['status'](record, rootEl)
419
+ }
420
+ if (record.kind === 'dataset' || record.kind === 'reuse') {
403
421
  fieldChanged('updateFrequency') &&
404
422
  this.writers['updateFrequency'](record, rootEl)
405
423
  fieldChanged('spatialRepresentation') &&
@@ -411,7 +429,12 @@ export class Iso19139Converter extends BaseConverter<string> {
411
429
  this.writers['spatialExtents'](record, rootEl)
412
430
  ;(fieldChanged('lineage') || fieldChanged('translations')) &&
413
431
  this.writers['lineage'](record, rootEl)
432
+ fieldChanged('sourceRecords') &&
433
+ this.writers['sourceRecords'](record, rootEl)
434
+ fieldChanged('associatedRecords') &&
435
+ this.writers['associatedRecords'](record, rootEl)
414
436
  }
437
+
415
438
  fieldChanged('otherLanguages') &&
416
439
  this.writers['otherLanguages'](record, rootEl)
417
440
 
@@ -1,4 +1,5 @@
1
1
  import {
2
+ AssociatedRecord,
2
3
  Constraint,
3
4
  ConstraintTranslations,
4
5
  DatasetOnlineResource,
@@ -8,6 +9,7 @@ import {
8
9
  Keyword,
9
10
  KeywordTranslations,
10
11
  LanguageCode,
12
+ SourceRecord,
11
13
  ModelTranslations,
12
14
  OnlineLinkResource,
13
15
  OnlineResource,
@@ -49,6 +51,7 @@ import {
49
51
  readText,
50
52
  XmlElement,
51
53
  } from '../xml-utils'
54
+ import { getAssociationTypeFromCode } from './utils/association-type.mapper'
52
55
  import { readGeometry } from './utils/geometry'
53
56
  import { fullNameToParts } from './utils/individual-name'
54
57
  import { getKeywordTypeFromKeywordTypeCode } from './utils/keyword.mapper'
@@ -894,6 +897,80 @@ export function readLineage(
894
897
  )(rootEl)
895
898
  }
896
899
 
900
+ export function extractSourceRecords(liLineageEl: XmlElement): SourceRecord[] {
901
+ if (!liLineageEl) return []
902
+ return pipe(
903
+ findChildrenElement('gmd:source', false),
904
+ mapArray((el) => {
905
+ const uuid = readAttribute('uuidref')(el)
906
+ const title = readAttribute('xlink:title')(el)
907
+ const href = readAttribute('xlink:href')(el)
908
+ if (!uuid && !title && !href) return null
909
+ return {
910
+ ...(uuid ? { uuid } : {}),
911
+ ...(title ? { title } : {}),
912
+ ...(href ? { href } : {}),
913
+ } as SourceRecord
914
+ }),
915
+ filterArray((s): s is SourceRecord => s !== null)
916
+ )(liLineageEl)
917
+ }
918
+
919
+ export function readSourceRecords(rootEl: XmlElement): SourceRecord[] {
920
+ return extractSourceRecords(
921
+ pipe(
922
+ findNestedElement(
923
+ 'gmd:dataQualityInfo',
924
+ 'gmd:DQ_DataQuality',
925
+ 'gmd:lineage',
926
+ 'gmd:LI_Lineage'
927
+ )
928
+ )(rootEl)
929
+ )
930
+ }
931
+
932
+ export function extractAssociatedRecords(
933
+ containerName: string,
934
+ aggregateName: string,
935
+ readIdentifier: ChainableFunction<XmlElement, string>
936
+ ): ChainableFunction<XmlElement, AssociatedRecord[]> {
937
+ return pipe(
938
+ findChildrenElement(containerName, false),
939
+ mapArray((el) => {
940
+ const aggregateEl = findChildElement(aggregateName, false)(el)
941
+ const uniqueIdentifier = readIdentifier(aggregateEl)
942
+ const associationTypeCode = pipe(
943
+ findNestedElement('gmd:associationType', 'gmd:DS_AssociationTypeCode'),
944
+ readAttribute('codeListValue')
945
+ )(aggregateEl)
946
+ if (!uniqueIdentifier || !associationTypeCode) return null
947
+ return {
948
+ uniqueIdentifier,
949
+ associationType: getAssociationTypeFromCode(associationTypeCode),
950
+ }
951
+ }),
952
+ filterArray((r): r is AssociatedRecord => r !== null)
953
+ )
954
+ }
955
+
956
+ export function readAssociatedRecords(rootEl: XmlElement): AssociatedRecord[] {
957
+ return pipe(
958
+ findIdentification(),
959
+ extractAssociatedRecords(
960
+ 'gmd:aggregationInfo',
961
+ 'gmd:MD_AggregateInformation',
962
+ pipe(
963
+ findNestedElement(
964
+ 'gmd:aggregateDataSetIdentifier',
965
+ 'gmd:MD_Identifier',
966
+ 'gmd:code'
967
+ ),
968
+ extractCharacterString()
969
+ )
970
+ )
971
+ )(rootEl)
972
+ }
973
+
897
974
  export function readUpdateFrequency(rootEl: XmlElement): UpdateFrequency {
898
975
  return pipe(
899
976
  findIdentification(),
@@ -0,0 +1,12 @@
1
+ import {
2
+ AssociationType,
3
+ associationTypeValues,
4
+ } from '../../../../../../../libs/common/domain/src/lib/model/record'
5
+
6
+ export function getAssociationTypeFromCode(
7
+ associationTypeCode: string
8
+ ): AssociationType {
9
+ return associationTypeValues.includes(associationTypeCode as AssociationType)
10
+ ? (associationTypeCode as AssociationType)
11
+ : 'crossReference'
12
+ }
@@ -8,6 +8,7 @@ import {
8
8
  Individual,
9
9
  Keyword,
10
10
  LanguageCode,
11
+ SourceRecord,
11
12
  RecordStatus,
12
13
  RecordTranslations,
13
14
  ReuseRecord,
@@ -1231,6 +1232,84 @@ export function writeLineage(record: DatasetRecord, rootEl: XmlElement) {
1231
1232
  )(rootEl)
1232
1233
  }
1233
1234
 
1235
+ export function appendSourceRecords(
1236
+ sources: SourceRecord[]
1237
+ ): ChainableFunction<XmlElement, XmlElement> {
1238
+ return pipe(
1239
+ removeChildrenByName('gmd:source'),
1240
+ appendChildren(
1241
+ ...sources
1242
+ .filter((source) => source.uuid || source.title || source.href)
1243
+ .map((source) =>
1244
+ pipe(
1245
+ createElement('gmd:source'),
1246
+ source.uuid ? writeAttribute('uuidref', source.uuid) : noop,
1247
+ source.title ? writeAttribute('xlink:title', source.title) : noop,
1248
+ source.href ? writeAttribute('xlink:href', source.href) : noop
1249
+ )
1250
+ )
1251
+ )
1252
+ )
1253
+ }
1254
+
1255
+ export function writeSourceRecords(
1256
+ record: DatasetRecord | ReuseRecord,
1257
+ rootEl: XmlElement
1258
+ ) {
1259
+ pipe(
1260
+ findNestedChildOrCreate(
1261
+ 'gmd:dataQualityInfo',
1262
+ 'gmd:DQ_DataQuality',
1263
+ 'gmd:lineage',
1264
+ 'gmd:LI_Lineage'
1265
+ ),
1266
+ appendSourceRecords(record.sourceRecords)
1267
+ )(rootEl)
1268
+ }
1269
+
1270
+ export function writeAssociatedRecords(
1271
+ record: DatasetRecord | ReuseRecord,
1272
+ rootEl: XmlElement
1273
+ ) {
1274
+ pipe(
1275
+ findOrCreateIdentification(),
1276
+ removeChildrenByName('gmd:aggregationInfo'),
1277
+ appendChildren(
1278
+ ...record.associatedRecords
1279
+ .filter((assoc) => assoc.uniqueIdentifier && assoc.associationType)
1280
+ .map((assoc) =>
1281
+ pipe(
1282
+ createNestedElement(
1283
+ 'gmd:aggregationInfo',
1284
+ 'gmd:MD_AggregateInformation'
1285
+ ),
1286
+ appendChildren(
1287
+ pipe(
1288
+ createNestedElement(
1289
+ 'gmd:aggregateDataSetIdentifier',
1290
+ 'gmd:MD_Identifier',
1291
+ 'gmd:code'
1292
+ ),
1293
+ writeCharacterString(assoc.uniqueIdentifier)
1294
+ ),
1295
+ pipe(
1296
+ createNestedElement(
1297
+ 'gmd:associationType',
1298
+ 'gmd:DS_AssociationTypeCode'
1299
+ ),
1300
+ writeAttribute(
1301
+ 'codeList',
1302
+ 'http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#DS_AssociationTypeCode'
1303
+ ),
1304
+ writeAttribute('codeListValue', assoc.associationType)
1305
+ )
1306
+ )
1307
+ )
1308
+ )
1309
+ )
1310
+ )(rootEl)
1311
+ }
1312
+
1234
1313
  export function getServiceEndpointProtocol(endpoint: ServiceEndpoint): string {
1235
1314
  switch (endpoint.accessServiceProtocol.toLowerCase()) {
1236
1315
  case 'wfs':
@@ -1,4 +1,4 @@
1
- import { Injectable, InjectionToken, inject } from '@angular/core'
1
+ import { inject, Injectable, InjectionToken } from '@angular/core'
2
2
  import { toLang2, toLang3 } from '../../../../../../../libs/util/i18n/src'
3
3
  import { TranslateService } from '@ngx-translate/core'
4
4
  import { Location } from '@angular/common'
@@ -44,8 +44,12 @@ export class AuthService {
44
44
  window.location.href
45
45
  ).toString()
46
46
  )
47
- .replace('${lang2}', toLang2(this.translateService.currentLang))
48
- .replace('${lang3}', toLang3(this.translateService.currentLang))
47
+ .replace(
48
+ '${current_path}',
49
+ this.location.prepareExternalUrl(this.location.path())
50
+ )
51
+ .replace('${lang2}', toLang2(this.translateService.getCurrentLang()))
52
+ .replace('${lang3}', toLang3(this.translateService.getCurrentLang()))
49
53
  }
50
54
 
51
55
  get logoutUrl() {
@@ -55,7 +59,7 @@ export class AuthService {
55
59
  get settingsUrl() {
56
60
  return this.baseSettingsUrl.replace(
57
61
  '${lang3}',
58
- toLang3(this.translateService.currentLang)
62
+ toLang3(this.translateService.getCurrentLang())
59
63
  )
60
64
  }
61
65
  }
@@ -1,4 +1,4 @@
1
- import { Injectable, Injector, inject } from '@angular/core'
1
+ import { inject, Injectable, Injector } from '@angular/core'
2
2
  import type { Geometry } from 'geojson'
3
3
  import {
4
4
  ES_QUERY_FIELDS_PRIORITY,
@@ -11,6 +11,7 @@ import {
11
11
  AggregationsParams,
12
12
  FieldFilter,
13
13
  FieldFilters,
14
+ FieldSort,
14
15
  FilterQuery,
15
16
  FiltersAggregationParams,
16
17
  SortByField,
@@ -141,6 +142,7 @@ export class ElasticsearchService {
141
142
  values: uuids,
142
143
  },
143
144
  },
145
+ size: uuids.length,
144
146
  }
145
147
  }
146
148
 
@@ -199,9 +201,27 @@ export class ElasticsearchService {
199
201
  }
200
202
 
201
203
  private buildPayloadSort(sortBy: SortByField): SortParams {
202
- if (sortBy === null) return undefined
203
- const fields = Array.isArray(sortBy[0]) ? sortBy : [sortBy]
204
- return fields.map((field) => ({ [field[1]]: field[0] }))
204
+ if (!sortBy || sortBy.length === 0) return undefined
205
+ const fields = Array.isArray(sortBy[0])
206
+ ? (sortBy as FieldSort[])
207
+ : [sortBy as FieldSort]
208
+ return fields.map((field) => {
209
+ // Sort by nested array of dates only works with the explicit syntax
210
+ if (field[1].endsWith('.date')) {
211
+ const nestedPath = field[1].slice(0, field[1].lastIndexOf('.date'))
212
+ return {
213
+ [field[1]]: {
214
+ order: field[0] as 'desc' | 'asc',
215
+ mode: field[0] === 'desc' ? 'max' : 'min',
216
+ missing: '_last',
217
+ nested: {
218
+ path: nestedPath,
219
+ },
220
+ },
221
+ }
222
+ }
223
+ return { [field[1]]: field[0] }
224
+ })
205
225
  }
206
226
 
207
227
  private injectLangInQueryStringFields(
@@ -234,7 +254,7 @@ export class ElasticsearchService {
234
254
  if (this.metadataLang) {
235
255
  const lang3 = toLang3(
236
256
  this.isCurrentSearchLang()
237
- ? this.translateService.currentLang
257
+ ? this.translateService.getCurrentLang()
238
258
  : this.metadataLang
239
259
  )
240
260
  return `lang${lang3}`
@@ -3,9 +3,10 @@ import {
3
3
  HttpErrorResponse,
4
4
  HttpHeaders,
5
5
  } from '@angular/common/http'
6
- import { Injectable, InjectionToken, inject } from '@angular/core'
6
+ import { inject, Injectable, InjectionToken } from '@angular/core'
7
7
  import {
8
8
  assertValidXml,
9
+ BaseConverter,
9
10
  findConverterForDocument,
10
11
  Gn4Converter,
11
12
  Gn4SearchResults,
@@ -13,10 +14,13 @@ import {
13
14
  } from '../../../../../../libs/api/metadata-converter/src'
14
15
  import { PublicationVersionError } from '../../../../../../libs/common/domain/src/lib/model/error'
15
16
  import {
17
+ AssociatedRecord,
16
18
  CatalogRecord,
17
19
  DatasetFeatureCatalog,
18
20
  DatasetFeatureType,
19
21
  LanguageCode,
22
+ LinkedRecord,
23
+ RecordRelation,
20
24
  } from '../../../../../../libs/common/domain/src/lib/model/record'
21
25
  import {
22
26
  Aggregations,
@@ -61,6 +65,12 @@ export const DISABLE_DRAFT = new InjectionToken<boolean>('gnDisableDraft', {
61
65
  factory: () => false,
62
66
  })
63
67
 
68
+ export const DEFAULT_RECORD_CONVERTER = new InjectionToken<
69
+ BaseConverter<string>
70
+ >('defaultRecordConverter', {
71
+ factory: () => new Iso19139Converter(),
72
+ })
73
+
64
74
  @Injectable()
65
75
  export class Gn4Repository implements RecordsRepositoryInterface {
66
76
  private httpClient = inject(HttpClient)
@@ -72,6 +82,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
72
82
  private gn4LanguagesApi = inject(LanguagesApiService)
73
83
  private settingsService = inject(Gn4SettingsService)
74
84
  private disableDraft = inject(DISABLE_DRAFT, { optional: true }) ?? false
85
+ private defaultConverter = inject(DEFAULT_RECORD_CONVERTER)
75
86
 
76
87
  _draftsChanged = new Subject<void>()
77
88
  draftsChanged$ = this._draftsChanged.asObservable()
@@ -137,7 +148,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
137
148
  return this.gn4SearchApi
138
149
  .search(
139
150
  'bucket',
140
- ['fcats', 'hassources'],
151
+ ['fcats', 'hassources', 'siblings', 'associated'],
141
152
  JSON.stringify(
142
153
  this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])
143
154
  )
@@ -241,22 +252,43 @@ export class Gn4Repository implements RecordsRepositoryInterface {
241
252
  )
242
253
  }
243
254
 
244
- getSources(record: CatalogRecord): Observable<CatalogRecord[]> {
245
- const sourcesIdentifiers = record.extras?.['sourcesIdentifiers'] as string[]
246
- if (sourcesIdentifiers && sourcesIdentifiers.length > 0) {
247
- return this.getMultipleRecords(sourcesIdentifiers)
248
- }
249
- return of(null)
250
- }
251
-
252
- getSourceOf(record: CatalogRecord): Observable<CatalogRecord[]> {
253
- const sourceOfIdentifiers = record.extras?.[
254
- 'sourceOfIdentifiers'
255
- ] as string[]
256
- if (sourceOfIdentifiers && sourceOfIdentifiers.length > 0) {
257
- return this.getMultipleRecords(sourceOfIdentifiers)
255
+ getLinkedRecords(record: CatalogRecord): Observable<LinkedRecord[]> {
256
+ const siblings = (record.extras?.['siblings'] ?? []) as AssociatedRecord[]
257
+ const relations: Array<[RecordRelation, string[]]> = [
258
+ ['source', (record.extras?.['sourcesIdentifiers'] ?? []) as string[]],
259
+ ['sourceOf', (record.extras?.['sourceOfIdentifiers'] ?? []) as string[]],
260
+ ['sibling', siblings.map(({ uniqueIdentifier }) => uniqueIdentifier)],
261
+ [
262
+ 'associated',
263
+ (record.extras?.['associatedIdentifiers'] ?? []) as string[],
264
+ ],
265
+ ]
266
+ const requested = relations.filter(
267
+ ([, identifiers]) => identifiers.length > 0
268
+ )
269
+ if (requested.length === 0) {
270
+ return of([])
258
271
  }
259
- return of(null)
272
+ return forkJoin(
273
+ requested.map(([relation, identifiers]) =>
274
+ this.getMultipleRecords(identifiers).pipe(
275
+ map((records) =>
276
+ (records ?? []).map((record) => ({
277
+ record,
278
+ relation,
279
+ associationType:
280
+ relation === 'sibling'
281
+ ? siblings.find(
282
+ ({ uniqueIdentifier }) =>
283
+ uniqueIdentifier === record.uniqueIdentifier
284
+ )?.associationType
285
+ : undefined,
286
+ }))
287
+ ),
288
+ catchError(() => of([]))
289
+ )
290
+ )
291
+ ).pipe(map((groups) => groups.flat()))
260
292
  }
261
293
 
262
294
  aggregate(params: AggregationsParams): Observable<Aggregations> {
@@ -319,8 +351,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
319
351
  return this.settingsService.allowEditHarvested$.pipe(
320
352
  map((allowEditHarvested) => {
321
353
  return (
322
- record.extras['edit'] &&
323
- (!record.extras['isHarvested'] || allowEditHarvested)
354
+ (this.platformService.supportsAuthentication() &&
355
+ record.extras?.['edit'] &&
356
+ (!record.extras?.['isHarvested'] || allowEditHarvested)) ??
357
+ false
324
358
  )
325
359
  })
326
360
  )
@@ -328,10 +362,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
328
362
 
329
363
  private canEdit(record: CatalogRecord, allowEditHarvested: boolean): boolean {
330
364
  return (
331
- this.platformService.supportsAuthentication() &&
332
- record.kind === 'dataset' &&
333
- record.extras['edit'] &&
334
- (!record.extras['isHarvested'] || allowEditHarvested)
365
+ (this.platformService.supportsAuthentication() &&
366
+ record.extras?.['edit'] &&
367
+ (!record.extras?.['isHarvested'] || allowEditHarvested)) ??
368
+ false
335
369
  )
336
370
  }
337
371
 
@@ -353,8 +387,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
353
387
  }
354
388
 
355
389
  openRecordForEdition(
356
- uniqueIdentifier: string
390
+ uniqueIdentifier: string,
391
+ disableDraft?: boolean
357
392
  ): Observable<[CatalogRecord, string, boolean] | null> {
393
+ this.disableDraft = disableDraft ?? this.disableDraft
358
394
  const draft$ = this.disableDraft
359
395
  ? of(null)
360
396
  : of(this.getRecordFromLocalStorage(uniqueIdentifier))
@@ -377,38 +413,52 @@ export class Gn4Repository implements RecordsRepositoryInterface {
377
413
  openRecordForDuplication(
378
414
  uniqueIdentifier: string
379
415
  ): Observable<[CatalogRecord, string, true] | null> {
380
- return this.gn4RecordsApi
381
- .create(
382
- uniqueIdentifier,
383
- '2',
384
- 'METADATA',
385
- '',
386
- false,
387
- undefined,
388
- true,
389
- false,
390
- undefined,
391
- 'body',
392
- false,
393
- {
394
- httpHeaderAccept: 'application/json',
395
- httpContentTypeSelected: 'application/json;charset=UTF-8',
396
- }
397
- )
398
- .pipe(
399
- switchMap((uniqueIdentifier) => {
400
- return this.getRecordAsXml(uniqueIdentifier)
401
- }),
402
- switchMap((xml) => {
403
- return from(
404
- findConverterForDocument(xml)
405
- .readRecord(xml)
406
- .then((record) => {
407
- return [record, xml, true] as [CatalogRecord, string, true]
408
- })
416
+ return this.platformService.getUserPermissionsByGroup().pipe(
417
+ map((permissions) => {
418
+ const groupId =
419
+ permissions.find((p) => p.canApprove)?.groupId?.toString() ??
420
+ permissions.find((p) => p.canEdit)?.groupId?.toString()
421
+ if (!groupId)
422
+ throw new Error(
423
+ 'Current user has no writable group to duplicate into'
424
+ )
425
+ return groupId
426
+ }),
427
+ switchMap((groupId) =>
428
+ this.gn4RecordsApi
429
+ .create(
430
+ uniqueIdentifier,
431
+ groupId,
432
+ 'METADATA',
433
+ '',
434
+ false,
435
+ undefined,
436
+ true,
437
+ false,
438
+ undefined,
439
+ 'body',
440
+ false,
441
+ {
442
+ httpHeaderAccept: 'application/json',
443
+ httpContentTypeSelected: 'application/json;charset=UTF-8',
444
+ }
445
+ )
446
+ .pipe(
447
+ switchMap((uniqueIdentifier) => {
448
+ return this.getRecordAsXml(uniqueIdentifier)
449
+ }),
450
+ switchMap((xml) => {
451
+ return from(
452
+ findConverterForDocument(xml)
453
+ .readRecord(xml)
454
+ .then((record) => {
455
+ return [record, xml, true] as [CatalogRecord, string, true]
456
+ })
457
+ )
458
+ })
409
459
  )
410
- })
411
460
  )
461
+ )
412
462
  }
413
463
 
414
464
  saveRecord(
@@ -605,10 +655,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
605
655
  record: CatalogRecord,
606
656
  referenceRecordSource?: string
607
657
  ): Observable<string> {
608
- // if there's a reference record, use that standard; otherwise, use iso19139
658
+ // if there's a reference record, use that standard; otherwise, use standard based on configuration or default
609
659
  const converter = referenceRecordSource
610
660
  ? findConverterForDocument(referenceRecordSource)
611
- : new Iso19139Converter()
661
+ : this.defaultConverter
612
662
  return from(converter.writeRecord(record, referenceRecordSource))
613
663
  }
614
664
 
@@ -52,7 +52,7 @@ export class OrganizationsFromGroupsService
52
52
  organisationsCount$ = this.organisations$.pipe(map((orgs) => orgs.length))
53
53
 
54
54
  private get lang3() {
55
- return toLang3(this.translateService.currentLang)
55
+ return toLang3(this.translateService.getCurrentLang())
56
56
  }
57
57
 
58
58
  constructor(
@@ -128,7 +128,7 @@ export class OrganizationsFromMetadataService
128
128
  ) {}
129
129
 
130
130
  private get langIndex() {
131
- return `lang${toLang3(this.translateService.currentLang)}`
131
+ return `lang${toLang3(this.translateService.getCurrentLang())}`
132
132
  }
133
133
 
134
134
  equalsNormalizedStrings(