• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geonetwork / geonetwork-ui / 13945367190

19 Mar 2025 11:24AM UTC coverage: 84.846% (+2.9%) from 81.949%
13945367190

Pull #1150

github

web-flow
Merge e860367e6 into e060bf76e
Pull Request #1150: Datahub: Display restricted access info for dataset previews

3402 of 4458 branches covered (76.31%)

Branch coverage included in aggregate %.

36 of 46 new or added lines in 7 files covered. (78.26%)

1 existing line in 1 file now uncovered.

9699 of 10983 relevant lines covered (88.31%)

379.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.04
/libs/feature/record/src/lib/map-view/map-view.component.ts
1
import {
1✔
2
  AfterViewInit,
3
  ChangeDetectionStrategy,
4
  ChangeDetectorRef,
5
  Component,
6
  Input,
7
  ViewChild,
8
} from '@angular/core'
9
import { MapUtilsService } from '@geonetwork-ui/feature/map'
1✔
10
import { getLinkLabel } from '@geonetwork-ui/util/shared'
1✔
11
import {
1✔
12
  BehaviorSubject,
13
  combineLatest,
14
  from,
15
  Observable,
16
  of,
17
  startWith,
18
  throwError,
19
  withLatestFrom,
20
} from 'rxjs'
21
import {
1✔
22
  catchError,
23
  distinctUntilChanged,
24
  finalize,
25
  map,
26
  shareReplay,
27
  switchMap,
28
  tap,
29
} from 'rxjs/operators'
30
import { MdViewFacade } from '../state/mdview.facade'
1✔
31
import { DataService } from '@geonetwork-ui/feature/dataviz'
1✔
32
import { DatasetOnlineResource } from '@geonetwork-ui/common/domain/model/record'
33
import {
1✔
34
  createViewFromLayer,
35
  MapContext,
36
  MapContextLayer,
37
  SourceLoadErrorEvent,
38
} from '@geospatial-sdk/core'
39
import {
1✔
40
  FeatureDetailComponent,
41
  MapContainerComponent,
42
  prioritizePageScroll,
43
  MapLegendComponent,
44
} from '@geonetwork-ui/ui/map'
45
import { Feature } from 'geojson'
46
import { NgIconComponent, provideIcons } from '@ng-icons/core'
1✔
47
import { matClose } from '@ng-icons/material-icons/baseline'
1✔
48
import { CommonModule } from '@angular/common'
1✔
49
import {
1✔
50
  ButtonComponent,
51
  DropdownSelectorComponent,
52
} from '@geonetwork-ui/ui/inputs'
53
import { TranslateModule, TranslateService } from '@ngx-translate/core'
1✔
54
import { ExternalViewerButtonComponent } from '../external-viewer-button/external-viewer-button.component'
1✔
55
import {
1✔
56
  LoadingMaskComponent,
57
  PopupAlertComponent,
58
} from '@geonetwork-ui/ui/widgets'
59
import { marker } from '@biesbjerg/ngx-translate-extract-marker'
1✔
60
import { FetchError } from '@geonetwork-ui/data-fetcher'
1✔
61

62
marker('map.dropdown.placeholder')
1✔
63
marker('wfs.feature.limit')
1✔
64

65
@Component({
66
  selector: 'gn-ui-map-view',
67
  templateUrl: './map-view.component.html',
68
  styleUrls: ['./map-view.component.css'],
69
  changeDetection: ChangeDetectionStrategy.OnPush,
70
  standalone: true,
71
  imports: [
72
    CommonModule,
73
    DropdownSelectorComponent,
74
    MapContainerComponent,
75
    FeatureDetailComponent,
76
    PopupAlertComponent,
77
    TranslateModule,
78
    LoadingMaskComponent,
79
    NgIconComponent,
80
    ExternalViewerButtonComponent,
81
    ButtonComponent,
82
    MapLegendComponent,
83
  ],
84
  viewProviders: [provideIcons({ matClose })],
85
})
86
export class MapViewComponent implements AfterViewInit {
1✔
87
  @Input() set exceedsLimit(value: boolean) {
88
    this.excludeWfs$.next(value)
×
89
  }
90
  @Input() displaySource = true
49✔
91
  @ViewChild('mapContainer') mapContainer: MapContainerComponent
92

93
  excludeWfs$ = new BehaviorSubject(false)
49✔
94
  hidePreview = false
49✔
95
  selection: Feature
96
  showLegend = true
49✔
97
  legendExists = false
49✔
98

99
  toggleLegend() {
100
    this.showLegend = !this.showLegend
×
101
  }
102

103
  onLegendStatusChange(status: boolean) {
104
    this.legendExists = status
1✔
105
    if (!status) {
1!
106
      this.showLegend = false
×
107
    }
108
  }
109

110
  compatibleMapLinks$ = combineLatest([
49✔
111
    this.mdViewFacade.mapApiLinks$,
112
    this.mdViewFacade.geoDataLinksWithGeometry$,
113
  ]).pipe(
114
    map(([mapApiLinks, geoDataLinksWithGeometry]) => {
115
      return [...mapApiLinks, ...geoDataLinksWithGeometry]
123✔
116
    })
117
  )
118

119
  dropdownChoices$ = this.compatibleMapLinks$.pipe(
49✔
120
    map((links) =>
121
      links.length
41✔
122
        ? links.map((link, index) => ({
66✔
123
            label: getLinkLabel(link),
124
            value: index,
125
          }))
126
        : [{ label: 'map.dropdown.placeholder', value: 0 }]
127
    )
128
  )
129
  selectedLinkIndex$ = new BehaviorSubject(0)
49✔
130

131
  loading = false
49✔
132
  error = null
49✔
133

134
  selectedLink$ = combineLatest([
49✔
135
    this.compatibleMapLinks$,
136
    this.selectedLinkIndex$.pipe(distinctUntilChanged()),
137
  ]).pipe(map(([links, index]) => links[index]))
104✔
138

139
  currentLayers$ = combineLatest([this.selectedLink$, this.excludeWfs$]).pipe(
49✔
140
    switchMap(([link, excludeWfs]) => {
141
      if (!link) {
54✔
142
        return of([])
4✔
143
      }
144
      if (excludeWfs && link.accessServiceProtocol === 'wfs') {
50✔
145
        this.hidePreview = true
2✔
146
        return of([])
2✔
147
      }
148
      this.hidePreview = false
48✔
149
      this.loading = true
48✔
150
      this.error = null
48✔
151
      return this.getLayerFromLink(link).pipe(
48✔
152
        map((layer) => [layer]),
38✔
153
        catchError((e) => {
154
          this.handleError(e)
3✔
155
          return of([])
3✔
156
        }),
157
        finalize(() => (this.loading = false))
46✔
158
      )
159
    })
160
  )
161

162
  mapContext$: Observable<MapContext> = this.currentLayers$.pipe(
49✔
163
    switchMap((layers) =>
164
      from(createViewFromLayer(layers[0])).pipe(
47✔
165
        catchError(() => of(null)), // could not zoom on the layer: use the record extent
2✔
166
        map((view) => ({
34✔
167
          layers,
168
          view,
169
        })),
170
        tap(() => {
171
          this.resetSelection()
34✔
172
        })
173
      )
174
    ),
175
    startWith({
176
      layers: [],
177
      view: null,
178
    }),
179
    withLatestFrom(this.mdViewFacade.metadata$),
180
    map(([context, metadata]) => {
181
      if (context.view) return context
83✔
182
      const extent = this.mapUtils.getRecordExtent(metadata)
80✔
183
      const view = extent ? { extent } : null
80✔
184
      return {
80✔
185
        ...context,
186
        view,
187
      }
188
    }),
189
    shareReplay(1)
190
  )
191

192
  constructor(
193
    private mdViewFacade: MdViewFacade,
49!
194
    private mapUtils: MapUtilsService,
49!
195
    private dataService: DataService,
49!
196
    private changeRef: ChangeDetectorRef,
49!
197
    private translateService: TranslateService
49✔
198
  ) {}
199

200
  async ngAfterViewInit() {
201
    const map = await this.mapContainer.openlayersMap
49✔
202
    prioritizePageScroll(map.getInteractions())
49✔
203
  }
204

205
  onMapFeatureSelect(features: Feature[]): void {
206
    this.resetSelection()
3✔
207
    this.selection = features?.length > 0 && features[0]
3✔
208
    if (this.selection) {
3✔
209
      // FIXME: restore styling of selected feature
210
      // this.selection.setStyle(this.selectionStyle)
211
    }
212
    this.changeRef.detectChanges()
3✔
213
  }
214

215
  onSourceLoadError(error: SourceLoadErrorEvent) {
216
    if (error.httpStatus === 403 || error.httpStatus === 401) {
3✔
217
      this.error = this.translateService.instant(`dataset.error.forbidden`)
2✔
218
    } else {
219
      this.error = this.translateService.instant(`dataset.error.http`, {
1✔
220
        info: error.httpStatus,
221
      })
222
    }
223
  }
224

225
  resetSelection(): void {
226
    if (this.selection) {
38✔
227
      // FIXME: restore styling of selected feature
228
      // this.selection.setStyle(null)
229
    }
230
    this.selection = null
38✔
231
  }
232

233
  getLayerFromLink(link: DatasetOnlineResource): Observable<MapContextLayer> {
234
    if (link.type === 'service' && link.accessServiceProtocol === 'wms') {
48✔
235
      return of({
31✔
236
        url: link.url.toString(),
237
        type: 'wms',
238
        name: link.name,
239
      })
240
    } else if (
17✔
241
      link.type === 'service' &&
27✔
242
      link.accessServiceProtocol === 'wmts'
243
    ) {
244
      return of({
1✔
245
        url: link.url.toString(),
246
        type: 'wmts',
247
        name: link.name,
248
      })
249
    } else if (
16✔
250
      (link.type === 'service' &&
37✔
251
        (link.accessServiceProtocol === 'wfs' ||
252
          link.accessServiceProtocol === 'esriRest' ||
253
          link.accessServiceProtocol === 'ogcFeatures')) ||
254
      link.type === 'download'
255
    ) {
256
      const cacheActive = true // TODO implement whether should be true or false
16✔
257
      return this.dataService.readAsGeoJson(link, cacheActive).pipe(
16✔
258
        map((data) => ({
6✔
259
          type: 'geojson',
260
          data,
261
        }))
262
      )
263
    }
UNCOV
264
    return throwError(() => 'protocol not supported')
×
265
  }
266

267
  selectLinkToDisplay(link: number) {
268
    this.selectedLinkIndex$.next(link)
11✔
269
  }
270

271
  handleError(error: FetchError | Error | string) {
272
    if (error instanceof FetchError) {
6✔
273
      this.error = this.translateService.instant(
1✔
274
        `dataset.error.${error.type}`,
275
        {
276
          info: error.info,
277
        }
278
      )
279
      console.warn(error.message)
1✔
280
    } else if (error instanceof Error) {
5✔
281
      this.error = this.translateService.instant(error.message)
4✔
282
      console.warn(error.stack || error)
4!
283
    } else {
284
      this.error = this.translateService.instant(error)
1✔
285
      console.warn(error)
1✔
286
    }
287
    this.loading = false
6✔
288
    this.changeRef.detectChanges()
6✔
289
  }
290
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc