dataset-detail.service.ts 14.1 KB
Newer Older
1
import { Injectable } from '@angular/core';
FORESTIER Fabien's avatar
FORESTIER Fabien committed
2
3
import { Observable, of, Subject } from 'rxjs';
import { catchError, finalize, flatMap, map, tap } from 'rxjs/operators';
4
import { notificationMessages } from '../../../i18n/traductions';
FORESTIER Fabien's avatar
FORESTIER Fabien committed
5
import { ErrorService, MatomoService } from '../../core/services';
FORESTIER Fabien's avatar
FORESTIER Fabien committed
6
7
8
import { Reuse } from '../../editorialisation/models';
import { CMSContent } from '../../editorialisation/models/cms-content.model';
import { ReusesService } from '../../editorialisation/services';
9
import { ElasticsearchOptions, IElasticsearchResponse } from '../../elasticsearch/models';
FORESTIER Fabien's avatar
FORESTIER Fabien committed
10
import { ElasticsearchService } from '../../elasticsearch/services/elasticsearch.service';
11
12
// tslint:disable-next-line: max-line-length
import { Data, Dataset, EditorialMetadata, Metadata } from '../../shared/models';
FORESTIER Fabien's avatar
FORESTIER Fabien committed
13
import { DatasetChild, IDatasetRawChild } from '../models';
14
15
16
17
18

@Injectable()
export class DatasetDetailService {

  private _dataset = new Dataset();
19
20
21
22
  private _children: {
    total: number,
    data: DatasetChild[],
  } = { total: null, data: [] };
23
  private _datasetSubject = new Subject<any>();
FORESTIER Fabien's avatar
FORESTIER Fabien committed
24
  private _datasetDataSearchChanged = new Subject<string>();
25
  private _sampleDataLoaded = new Subject<Data[]>();
26
  private _initialScrollOptions = {
FORESTIER Fabien's avatar
FORESTIER Fabien committed
27
    from: 0,
28
    pageSize: 30,
29
    sortOptions: {
30
      value: null,
31
      order: 'asc',
32
    },
FORESTIER Fabien's avatar
FORESTIER Fabien committed
33
    searchString: '',
34
  };
FORESTIER Fabien's avatar
FORESTIER Fabien committed
35
  private _researchMaxResult: number;
36
  // Copy the initial value to be able to reinitialize at any moment
37
  private _elasticSearchOptions: ElasticsearchOptions;
38

39
40
  // Used to avoid to construct the map multiple times for the same datasetId
  private _lastDatasetIdForMap: string;
FORESTIER Fabien's avatar
FORESTIER Fabien committed
41
  private _pendingRequests: number = 0;
42

CARRON Guilhem's avatar
CARRON Guilhem committed
43

44
  constructor(
45
    private _errorService: ErrorService,
46
    private _elasticsearchService: ElasticsearchService,
47
    private _matomoService: MatomoService,
CARRON Guilhem's avatar
CARRON Guilhem committed
48
    private _reusesService: ReusesService,
49
50
  ) { }

51
  initializeDataset(slugOrUuid: string): Observable<Dataset> {
52
    this._dataset.data = [];
53
    this._dataset.nbViews = null;
54
    this._elasticSearchOptions = new ElasticsearchOptions(this._initialScrollOptions);
FORESTIER Fabien's avatar
FORESTIER Fabien committed
55

FORESTIER Fabien's avatar
FORESTIER Fabien committed
56
    this.newAsyncRequest();
57

58
    this._matomoService.getPageMetadataPageMetrics(slugOrUuid).pipe(
59
60
61
62
      catchError(() => of(null)),
    ).subscribe((nbViews) => {
      this._dataset.nbViews = nbViews;
    });
63
    // Get the metadata
64
    return this._elasticsearchService.getDatasetMetadata(slugOrUuid).pipe(
65
      map((e) => {
66
67
68
        if (e.hits.hits.length > 0) {
          const metadata = new Metadata(e.hits.hits[0]._source['metadata-fr']);
          this._dataset.uuid = metadata.geonet.uuid;
69
          this._dataset.slug = e.hits.hits[0]._source['slug'];
70
71
          this._dataset.editorialMetadata = e.hits.hits[0]._source['editorial-metadata'];

72
73
74
75
          this._dataset.fields = e.hits.hits[0]._source['fields'] ? e.hits.hits[0]._source['fields'] : {
            list: [],
            types: {},
          };
76
77
78
79
          metadata.dataset_id = e.hits.hits[0]._id;
          const bbox = e.hits.hits[0]._source['metadata-fr']['bbox'];
          if (bbox !== undefined && bbox['coordinates'] !== undefined) {
            const coordinates = e.hits.hits[0]._source['metadata-fr']['bbox']['coordinates'][0];
80
            metadata.max_west = Math.min.apply(Math, coordinates.map((point) => {
81
82
              return point[0];
            }));
83
            metadata.max_east = Math.max.apply(Math, coordinates.map((point) => {
84
85
86
87
88
89
90
91
              return point[0];
            }));
            metadata.max_north = Math.max.apply(Math, coordinates.map((point) => {
              return point[1];
            }));
            metadata.max_south = Math.min.apply(Math, coordinates.map((point) => {
              return point[1];
            }));
92
          }
93

94
95
96
97
98
99
          metadata.dataset_index = e.hits.hits[0]._index;

          this._dataset.totalData = e.hits.total;

          this._dataset.metadata = metadata;
          this._dataset.editorialMetadata = new EditorialMetadata(e.hits.hits[0]._source['editorial-metadata']);
100
101
          // Disable cache for real time dataset
          this._elasticSearchOptions.useCache = this.datasetEditorialMetadata.isRealTime ? false : true;
102
103
104
105
106
        } else {
          this._dataset.metadata = new Metadata();
        }
        return this._dataset;
      }),
107
108
109
110
111
      catchError((err) => {
        throw this._errorService.handleError(err, {
          message: notificationMessages.geosource.getDatasetById, redirect: true,
        });
      }),
FORESTIER Fabien's avatar
FORESTIER Fabien committed
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
      flatMap((dataset) => {
        // Get reuses
        return this._reusesService.getDataSetReuses(dataset.slug).pipe(
          map((reuses) => {
            dataset.reuses = reuses;
            return dataset;
          }),
          catchError(() => {
            return of(dataset);
          }),
        );
      }),
      flatMap((dataset) => {
        // Get related articles
        return this.retrieveRelatedPosts(dataset.slug).pipe(
          map((posts) => {
            dataset.relatedNews = posts;
            return dataset;
          }),
          catchError(() => {
            return of(dataset);
          }),
        );
      }),
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
      // Get the parent dataset if there is one
      flatMap((dataset) => {
        if (dataset.metadata.parentDataset.id) {
          return this._elasticsearchService.getDatasetParentInfo(dataset.metadata.parentDataset.id).pipe(
            map((parent) => {
              dataset.metadata.parentDataset.title = parent.hits.hits[0]._source['metadata-fr'].title;
              dataset.metadata.parentDataset.slug = parent.hits.hits[0]._source['slug'];
              // Pick the thumbnail image among the parent dataset images
              const thumbnail = parent.hits.hits[0]._source['metadata-fr'].image.find(
                image => image.type === 'thumbnail',
              );
              if (thumbnail) {
                dataset.metadata.parentDataset.imageUrl = thumbnail.url;
              }
              return dataset;
            }),
            catchError(() => {
              return of(dataset);
            }),
          );
          // tslint:disable-next-line:no-else-after-return
        } else {
          return of(dataset);
        }
      }),
FORESTIER Fabien's avatar
FORESTIER Fabien committed
161
      flatMap((dataset) => {
162
163
164
165
166
167
168
169
170
171
172
173
        // Get the dataset's children
        return this._elasticsearchService.getDatasetChildren(this._dataset.uuid).pipe(
          map((res) => {
            this._children.total = res.aggregations.children_number.value;
            // Reinit the array ini case the dataset doesn't have children
            this._children.data = [];
            res.hits.hits.forEach((child) => {
              const source = child._source as IDatasetRawChild;
              const datasetChild = new DatasetChild(source);
              datasetChild.slug = child._source['slug'];
              this._children.data.push(datasetChild);
            });
174
175
176
177
178
179
180
181
            // reorder children alphabetically
            this._children.data.sort((a,b) => {
              var child1 = a.slug.toUpperCase();
              var child2 = b.slug.toUpperCase();

              return (child1 < child2) ? -1 : (child1 > child2) ? 1 : 0;
            });

FORESTIER Fabien's avatar
FORESTIER Fabien committed
182
            return dataset;
183
184
185
186
187
188
          }),
          catchError((err) => {
            throw this._errorService.handleError(err, { message: notificationMessages.geosource.getDatasetChildren });
          }),
        );
      }),
FORESTIER Fabien's avatar
FORESTIER Fabien committed
189
190
191
      finalize(() => {
        this.asyncRequestDone();
      }),
192
    );
193

194
  }
FORESTIER Fabien's avatar
FORESTIER Fabien committed
195

196
  // Get the next page of data, add it to the current array and return the array with the updated data
197
  retrieveMoreDatasetData(): Observable<Data[]> {
FORESTIER Fabien's avatar
FORESTIER Fabien committed
198
    this.newAsyncRequest();
199
    return this._elasticsearchService.getDatasetData(this._dataset.uuid, this._elasticSearchOptions).pipe(
200
201
202
203
204
205
      tap((res) => {
        this.setDatasetData(res);
      }),
      map((res) => {
        return this._dataset.data;
      }),
206
      catchError((err) => {
207
        throw this._errorService.handleError(err, { message: notificationMessages.geosource.getDatasetData });
208
      }),
FORESTIER Fabien's avatar
FORESTIER Fabien committed
209
210
211
      finalize(() => {
        this.asyncRequestDone();
      }),
212
213
214
215
    );
  }

  // Initialize the dataset data array with an empty array and push the data retreived
216
  retrieveDatasetData(): Observable<Data[]> {
FORESTIER Fabien's avatar
FORESTIER Fabien committed
217
    this.newAsyncRequest();
218
    return this._elasticsearchService.getDatasetData(this._dataset.uuid, this._elasticSearchOptions).pipe(
219
      tap((res) => {
220
        this._dataset.data = [];
FORESTIER Fabien's avatar
FORESTIER Fabien committed
221
        this._researchMaxResult = res.hits.total;
222
223
224
225
226
        this.setDatasetData(res);
      }),
      map((res) => {
        return this._dataset.data;
      }),
227
      catchError((err) => {
228
        throw this._errorService.handleError(err, { message: notificationMessages.geosource.getDatasetData });
229
      }),
FORESTIER Fabien's avatar
FORESTIER Fabien committed
230
231
232
      finalize(() => {
        this.asyncRequestDone();
      }),
233
    );
234
235
  }

FORESTIER Fabien's avatar
FORESTIER Fabien committed
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
  retrieveRelatedPosts(slug: string) {
    this.newAsyncRequest();

    const options = {
      sortDate: true,
      type: 'post',
      relatedDatasetSlug: slug,
    };

    return this._elasticsearchService.getPosts(options).pipe(
      map((posts) => {
        return posts.hits.hits.map(result => new CMSContent(result._source));
      }),
      catchError((err) => {
        throw this._errorService.handleError(err, { message: notificationMessages.edito.getDatasetRelatedPosts });
      }),
      finalize(() => {
        this.asyncRequestDone();
      }),
    );
  }

258
  setDatasetData(res: IElasticsearchResponse): void {
259
    res.hits.hits.forEach((e) => {
260
261
262
263
      if (e._source['data-fr'] !== undefined) {
        this._dataset.data.push(
          new Data(e._source['data-fr']),
        );
264
265
        // Set the properties order based on the natural order from the data provider (set in 'dataset.fields.list')
        this._dataset.data.forEach((data) => {
266
          this.orderProperties(data, this._dataset.fields.list);
267
        });
268
      }
269
    });
270
271
272
    if (this._dataset.editorialMetadata.isSample) {
      this._sampleDataLoaded.next(this._dataset.data);
    }
273
274
  }

275
276
277
278
  getKeyType(key: string) {
    return  key+'_'+ eval(`this._dataset.fields.types.${key}`);
  }

279
280
281
  orderProperties(data: any, orderedProperties: string[]) {
    const newDataPropertiesOrder = {};
    orderedProperties.forEach((field) => {
282
      // Adds type to field. First time, the field with type is used, and attributed to field without type
283
      // force _json detection since the fieldtype is not listed. (used for complex data)
284
285
      var fieldtype = field + '_' + eval(`this._dataset.fields.types.${field}`);
      newDataPropertiesOrder[field] = data.properties[fieldtype] ? data.properties[fieldtype] :
286
          data.properties[field] ? data.properties[field] : data.properties[`${field}_json`] ?
287
288
          data.properties[`${field}_json`] : data.properties[`${field}_str`] ?
          data.properties[`${field}_str`] : '';
289
    });
290

291

292
293
294
    data.properties = newDataPropertiesOrder;
  }

295
  get datasetData(): Data[] {
FORESTIER Fabien's avatar
zut    
FORESTIER Fabien committed
296
297
298
    return this._dataset.data;
  }

299
300
  get datasetMetadata(): Metadata {
    return this._dataset.metadata;
301
302
  }

303
304
305
306
  get dataset(): Dataset {
    return this._dataset;
  }

307
308
309
310
311
312
313
314
  get lastDatasetIdForMap(): string {
    return this._lastDatasetIdForMap;
  }

  set lastDatasetIdForMap(datasetId: string) {
    this._lastDatasetIdForMap = datasetId;
  }

315
316
317
318
  get datasetEditorialMetadata(): EditorialMetadata {
    return this._dataset.editorialMetadata;
  }

319
320
321
322
  get dataset$(): Observable<void> {
    return this._datasetSubject.asObservable();
  }

FORESTIER Fabien's avatar
FORESTIER Fabien committed
323
324
325
326
  get datasetDataSearchChanged$(): Observable<string> {
    return this._datasetDataSearchChanged.asObservable();
  }

327
328
329
330
  get sampleDataLoaded$(): Observable<Data[]> {
    return this._sampleDataLoaded.asObservable();
  }

331
  get datasetChildren() {
332
333
334
    return this._children;
  }

335
  get datasetDataNumber(): number {
FORESTIER Fabien's avatar
zut    
FORESTIER Fabien committed
336
337
338
    return this._dataset.totalData;
  }

339
340
341
342
  get datasetNumberOfViews(): number {
    return this._dataset.nbViews;
  }

343
  get scrollOptionsSum(): number {
344
    return this._elasticSearchOptions.pageSize + this._elasticSearchOptions.from;
345
346
  }

347
  get sortValue(): string {
348
    return this._elasticSearchOptions.sortOptions.value;
349
350
351
  }

  get sortOrder(): string {
352
    return this._elasticSearchOptions.sortOptions.order;
353
354
  }

FORESTIER Fabien's avatar
FORESTIER Fabien committed
355
  get searchString() {
356
    return this._elasticSearchOptions.searchString;
FORESTIER Fabien's avatar
FORESTIER Fabien committed
357
358
359
360
361
362
  }

  get researchMaxResult() {
    return this._researchMaxResult;
  }

363
  get datasetIndex() {
364
    return this._dataset.uuid;
365
  }
CARRON Guilhem's avatar
CARRON Guilhem committed
366
367
368
  get datasetReuses(): Reuse[] {
    return this._dataset.reuses;
  }
369

370
371
  sortBy(val: string): void {
    // Reset the from of the query to start again from the beggining
372
    this._elasticSearchOptions.from = this._initialScrollOptions.from;
373
    // If same value jsut reverse the order
374
    if (this._elasticSearchOptions.sortOptions.value === val) {
375
376
      this.reverseSortOrder();
    } else {
377
      // Set the new value and take initial order
378
379
      this._elasticSearchOptions.sortOptions.value = val;
      this._elasticSearchOptions.sortOptions.order = this._initialScrollOptions.sortOptions.order;
380
381
382
    }
  }

FORESTIER Fabien's avatar
FORESTIER Fabien committed
383
384
  searchChanged(value: string) {
    // Reset the from of the query to start again from the beggining
385
386
    this._elasticSearchOptions.from = this._initialScrollOptions.from;
    this._elasticSearchOptions.searchString = value;
FORESTIER Fabien's avatar
FORESTIER Fabien committed
387
    this._datasetDataSearchChanged.next(value);
388
389
  }

390
  reverseSortOrder(): void {
391
392
    if (this._elasticSearchOptions.sortOptions.order === 'asc') {
      this._elasticSearchOptions.sortOptions.order = 'desc';
393
    } else {
394
      this._elasticSearchOptions.sortOptions.order = 'asc';
395
396
397
    }
  }

398
399
400
401
402
403
404
  putDataAtBeginningOfDatasetDataArray(data: Data) {
    if (data) {
      this._dataset.data = this._dataset.data.filter(item => item.id !== data.id);
      this._dataset.data.unshift(data);
    }
  }

405
  datasetChanged(): void {
406
407
408
    this._datasetSubject.next();
  }

409
410
411
  nextPage(): void {
    // Increase the from value by 'size' (think of a pagination)
    // The next request that will use this value will get the 'size'next elements
412
    this._elasticSearchOptions.from += this._elasticSearchOptions.pageSize;
413
414
  }

FORESTIER Fabien's avatar
FORESTIER Fabien committed
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
  // Helpers for knowing if there are pending requests or not
  // Increments the number of curretly running async request by one
  newAsyncRequest() {
    this._pendingRequests += 1;
  }

  // Decrement the number of async requests by one
  asyncRequestDone() {
    this._pendingRequests -= 1;
  }

  // Return true if http queries are being made
  get isLoading() {
    return this._pendingRequests > 0 ? true : false;
  }

431
}