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

geonetwork / geonetwork-ui / 16420495384

21 Jul 2025 02:57PM UTC coverage: 83.87%. First build
16420495384

Pull #1278

github

web-flow
Merge b98469ba2 into d500b08cc
Pull Request #1278: [Datahub] : Read preview configuration

3517 of 4711 branches covered (74.66%)

Branch coverage included in aggregate %.

103 of 108 new or added lines in 7 files covered. (95.37%)

10059 of 11476 relevant lines covered (87.65%)

272.55 hits per line

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

83.69
/apps/datahub/src/app/record/record-data-preview/record-data-preview.component.ts
1
import { CommonModule } from '@angular/common'
4✔
2
import {
4✔
3
  ChangeDetectionStrategy,
4
  ChangeDetectorRef,
5
  Component,
6
  HostListener,
7
  Inject,
8
  InjectionToken,
9
  Input,
10
  OnDestroy,
11
  OnInit,
12
  Optional,
13
} from '@angular/core'
14
import { MatTabsModule } from '@angular/material/tabs'
4✔
15
import { DatavizConfigModel } from '@geonetwork-ui/common/domain/model/dataviz/dataviz-configuration.model'
16
import {
17
  DatasetOnlineResource,
18
  DatasetServiceDistribution,
19
} from '@geonetwork-ui/common/domain/model/record'
20
import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface'
4✔
21
import { DataService } from '@geonetwork-ui/feature/dataviz'
4✔
22
import {
4✔
23
  DataViewComponent,
24
  DataViewShareComponent,
25
  MapViewComponent,
26
  MdViewFacade,
27
} from '@geonetwork-ui/feature/record'
28
import { ButtonComponent } from '@geonetwork-ui/ui/inputs'
4✔
29
import { TranslateDirective } from '@ngx-translate/core'
4✔
30
import {
4✔
31
  BehaviorSubject,
32
  combineLatest,
33
  map,
34
  of,
35
  skip,
36
  startWith,
37
  Subscription,
38
  switchMap,
39
  take,
40
} from 'rxjs'
41

42
export const MAX_FEATURE_COUNT = new InjectionToken<string>('maxFeatureCount')
4✔
43

44
@Component({
45
  selector: 'datahub-record-data-preview',
46
  templateUrl: './record-data-preview.component.html',
47
  styleUrls: ['./record-data-preview.component.css'],
48
  changeDetection: ChangeDetectionStrategy.OnPush,
49
  standalone: true,
50
  imports: [
51
    CommonModule,
52
    MatTabsModule,
53
    TranslateDirective,
54
    DataViewShareComponent,
55
    DataViewComponent,
56
    MapViewComponent,
57
    ButtonComponent,
58
  ],
59
})
60
export class RecordDataPreviewComponent implements OnDestroy, OnInit {
4✔
61
  @Input() recordUuid: string
62
  sub = new Subscription()
41✔
63
  hasConfig = false
41✔
64
  savingStatus: 'idle' | 'saving' | 'saved' | 'error' = 'idle'
41✔
65
  views = ['map', 'table', 'chart']
41✔
66
  displayMap$ = combineLatest([
41✔
67
    this.metadataViewFacade.mapApiLinks$,
68
    this.metadataViewFacade.geoDataLinksWithGeometry$,
69
  ]).pipe(
70
    map(([mapApiLinks, geoDataLinksWithGeometry]) => {
71
      const display =
72
        mapApiLinks?.length > 0 || geoDataLinksWithGeometry?.length > 0
179✔
73
      this.selectedIndex$.next(display ? 1 : 2)
179✔
74
      this.selectedView$.next(display ? 'map' : 'table')
179✔
75
      return display
179✔
76
    }),
77
    startWith(false)
78
  )
79

80
  displayData$ = combineLatest([
41✔
81
    this.metadataViewFacade.dataLinks$,
82
    this.metadataViewFacade.geoDataLinks$,
83
  ]).pipe(
84
    map(
85
      ([dataLinks, geoDataLinks]) =>
86
        dataLinks?.length > 0 || geoDataLinks?.length > 0
206✔
87
    )
88
  )
89

90
  selectedLink$ = new BehaviorSubject<DatasetOnlineResource>(null)
41✔
91

92
  exceedsMaxFeatureCount$ = combineLatest([
41✔
93
    this.metadataViewFacade.geoDataLinksWithGeometry$,
94
    this.selectedLink$,
95
  ]).pipe(
96
    map(([links, selectedLink]) =>
97
      selectedLink != null ? selectedLink : links[0]
104✔
98
    ),
99
    switchMap((link) => {
100
      return link && link.accessServiceProtocol === 'wfs'
94✔
101
        ? this.dataService
102
            .getWfsFeatureCount(link.url.toString(), link.name)
103
            .pipe(map((count) => count > this.maxFeatureCount))
5✔
104
        : of(false)
105
    })
106
  )
107

108
  selectedView$ = new BehaviorSubject(null)
41✔
109
  datavizConfig = null
41✔
110

111
  selectedIndex$ = new BehaviorSubject(0)
41✔
112
  selectedTMSStyle$ = new BehaviorSubject(0)
41✔
113

114
  displayViewShare$ = combineLatest([
41✔
115
    this.displayMap$,
116
    this.displayData$,
117
    this.selectedView$,
118
    this.exceedsMaxFeatureCount$,
119
  ]).pipe(
120
    map(
121
      ([displayMap, displayData, selectedView, exceedsMaxFeatureCount]) =>
122
        (displayData || displayMap) &&
182✔
123
        !(selectedView === 'chart' && exceedsMaxFeatureCount)
96✔
124
    )
125
  )
126

127
  displayDatavizConfig$ = combineLatest([
41✔
128
    this.platformService.getMe(),
129
    this.metadataViewFacade.metadata$,
130
  ]).pipe(
131
    map(([userInfo, metadata]) => {
132
      const isAdmin =
133
        userInfo?.profile === 'Administrator' ||
4✔
134
        userInfo?.username ===
135
          (metadata?.extras?.ownerInfo as string).split('|')[0]
136
      const isPublished = metadata?.extras?.isPublishedToAll
4✔
137
      return isAdmin && isPublished
4✔
138
    })
139
  )
140

141
  constructor(
142
    public metadataViewFacade: MdViewFacade,
41!
143
    private platformService: PlatformServiceInterface,
41!
144
    private dataService: DataService,
41!
145
    @Inject(MAX_FEATURE_COUNT)
146
    @Optional()
147
    protected maxFeatureCount: number,
41✔
148
    private platformServiceInterface: PlatformServiceInterface,
41!
149
    private cdr: ChangeDetectorRef
41✔
150
  ) {}
151

152
  ngOnInit(): void {
153
    this.platformServiceInterface
44✔
154
      .getRecordAttachments(this.recordUuid)
155
      .pipe(
156
        map((attachments) =>
157
          attachments.find((att) => att.fileName === 'datavizConfig.json')
44✔
158
        ),
159
        switchMap((configAttachment) =>
160
          (configAttachment
44✔
161
            ? this.platformServiceInterface.getFileContent(configAttachment.url)
162
            : of(null)
163
          ).pipe(
164
            switchMap((config: DatavizConfigModel) =>
165
              this.displayMap$.pipe(
3✔
166
                skip(1),
167
                take(1),
168
                map((displayMap) => ({ config, displayMap }))
3✔
169
              )
170
            )
171
          )
172
        )
173
      )
174
      .subscribe(({ config, displayMap }) => {
175
        let view
176
        if (config) {
3✔
177
          view =
2✔
178
            window.innerWidth < 640
2!
179
              ? config.view === 'chart'
×
180
                ? 'chart'
181
                : 'map'
182
              : config.view
183

184
          if (!displayMap && view === 'map') {
2✔
185
            view = 'table'
1✔
186
          }
187

188
          const tab = this.views.indexOf(view) + 1 || 3
2!
189

190
          this.datavizConfig = {
2✔
191
            ...config,
192
            view,
193
          }
194
          this.selectedIndex$.next(tab)
2✔
195
          this.selectedView$.next(view)
2✔
196
          this.selectedLink$.next(config.source)
2✔
197
        } else {
198
          this.datavizConfig = {
1✔
199
            link: this.selectedLink$.value,
200
            view: this.selectedView$.value,
201
          }
202
        }
203
      })
204
  }
205

206
  ngOnDestroy() {
207
    this.sub.unsubscribe()
41✔
208
  }
209

210
  saveDatavizConfig() {
211
    this.savingStatus = 'saving'
2✔
212
    this.sub.add(
2✔
213
      combineLatest([
214
        this.selectedView$,
215
        this.selectedLink$,
216
        this.metadataViewFacade.chartConfig$,
217
        this.selectedTMSStyle$,
218
      ])
219
        .pipe(
220
          take(1),
221
          map(([selectedView, selectedLink, chartConfig, selectedTMSStyle]) => {
222
            return this.dataService.writeConfigAsJSON({
2✔
223
              view: selectedView,
224
              source: selectedLink,
225
              chartConfig: selectedView === 'chart' ? chartConfig : null,
2✔
226
              styleTMSIndex: selectedView === 'map' ? selectedTMSStyle : null,
2✔
227
            })
228
          }),
229
          switchMap((config) =>
230
            this.platformServiceInterface.attachFileToRecord(
2✔
231
              this.recordUuid,
232
              config,
233
              true
234
            )
235
          )
236
        )
237
        .subscribe({
238
          next: () => {
239
            this.savingStatus = 'saved'
2✔
240
            this.cdr.detectChanges()
2✔
241
            setTimeout(() => {
2✔
242
              this.savingStatus = 'idle'
×
243
              this.cdr.detectChanges()
×
244
            }, 2000)
245
          },
246
          error: () => {
247
            this.savingStatus = 'error'
×
248
            this.cdr.detectChanges()
×
249
            setTimeout(() => {
×
250
              this.savingStatus = 'idle'
×
251
              this.cdr.detectChanges()
×
252
            }, 3000)
253
          },
254
        })
255
    )
256
  }
257

258
  onTabIndexChange(index: number): void {
NEW
259
    const view = this.views[index - 1] ?? 'chart'
×
260
    this.selectedView$.next(view)
×
261
    setTimeout(() => {
×
262
      window.dispatchEvent(new Event('resize'))
×
263
    }, 0)
264
  }
265
  onSelectedLinkChange(link: DatasetOnlineResource) {
266
    this.selectedLink$.next(link)
1✔
267
  }
268
  onSelectedTMSStyleChange(index: number) {
NEW
269
    this.selectedTMSStyle$.next(index)
×
270
  }
271
}
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