All files / data_handler exporter.ts

76.92% Statements 70/91
74.24% Branches 49/66
87.5% Functions 7/8
79.26% Lines 65/82

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 1464x 4x 4x 4x 4x       4x             11x 11x 11x 11x 11x 11x           11x             11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 40x       11x 11x 21x   21x               11x 33x 23x 23x         11x 22x 20x 20x 20x 20x     11x 11x 11x 11x 1x 2x 2x 2x 2x       10x       14x 11x 11x 44x 44x         36x 36x 36x 36x       11x 11x 11x   11x                                 11x                         11x 11x 11x    
import * as params from './export_params'
import {group_checks, export_checks} from './checks'
import {passes_filter, passes_feature_filter} from './filter_check'
import {patterns} from './patterns'
import * as row_writers from './row_writers'
import type {Query, Entity, Features, Entities, RawQuery} from '../types'
import DataHandler from './index'
 
export async function exporter(
  this: DataHandler,
  query_string?: RawQuery,
  entities?: Entities,
  in_browser?: boolean,
  all_data?: boolean
) {
  if (!in_browser) await this.data_ready
  const query: Query = this.parse_query(query_string)
  entities = entities || this.entities
  Iif (-1 === params.options.file_format.indexOf(query.file_format)) query.file_format = params.defaults.file_format
  Iif (!(query.table_format in row_writers)) query.table_format = params.defaults.table_format
  const res = {
      statusCode: 400,
      headers: {'Content-Type': 'text/plain; charset=utf-8', 'Content-Disposition': 'attachment; filename='},
      body: 'Invalid Request',
    },
    inc: string[] =
      query.include && query.include.length
        ? 'string' === typeof query.include
          ? query.include in this.variables
            ? [query.include]
            : query.include.split(',')
          : query.include
        : Object.keys(this.variables),
    exc = query.exclude || [],
    vars: string[] = [],
    feats: Features = query.features || JSON.parse(JSON.stringify(params.defaults.features)),
    rows: string[] = [],
    range = [Infinity, -Infinity],
    sep = 'csv' === query.file_format ? ',' : '\t',
    rw: Function = row_writers[query.table_format].bind(this),
    no_filter = !query.variables.filter_by.length,
    no_feature_filter = !query.feature_conditions.length,
    in_group: Function = !('dataset' in query)
      ? () => true
      : group_checks[query.dataset.operator].bind(
          'number' === typeof query.dataset.value ? query.dataset.value + '' : query.dataset.value
        )
  let malformed = ''
  inc.forEach(ii => {
    Iif (ii in this.features && !(ii in feats)) {
      feats[ii] = this.format_label(ii)
    } else Iif (!(ii in this.variables)) {
      malformed += ii + ','
      Iif (malformed in this.variables) {
        inc.push(malformed)
        malformed = ''
      }
    }
  })
  for (const k in export_checks)
    if (k in query) {
      const r: string = export_checks[k]('include' === k ? inc : query[k as keyof Query], this.variables)
      Iif (r) {
        res.body = 'Failed check for ' + k + ': ' + r
        return res
      }
    }
  Object.keys(this.variable_codes).forEach(k => {
    if (-1 !== inc.indexOf(this.variable_codes[k].name) && -1 === exc.indexOf(this.variable_codes[k].name)) {
      vars.push(this.variable_codes[k].name)
      const tr: [number, number] = this.meta.ranges[this.variable_codes[k].name]
      if (tr[0] < range[0]) range[0] = tr[0]
      if (tr[1] > range[1]) range[1] = tr[1]
    }
  })
  Iif (query.time_range[0] < range[0]) query.time_range[0] = range[0]
  Iif (query.time_range[1] > range[1]) query.time_range[1] = range[1]
  rows.push(Object.keys(feats).join(sep))
  if ('wide' === query.table_format) {
    vars.forEach(vi => {
      const tr: [number, number] = this.meta.ranges[vi],
        yn = Math.min(query.time_range[1], tr[1]) + 1
      for (let y = Math.max(query.time_range[0], tr[0]); y < yn; y++) {
        rows[0] += sep + '"' + vi + '_' + this.meta.overall.value[y] + '"'
      }
    })
  } else
    rows[0] +=
      sep +
      'time' +
      sep +
      ('mixed' === query.table_format ? vars.map(n => '"' + n + '"') : ['variable', 'value']).join(sep)
  let first_entity = ''
  Object.keys(entities).forEach(k => {
    const e: Entity = entities[k]
    if (
      in_group(e.group) &&
      (no_feature_filter || passes_feature_filter(entities, k, query.feature_conditions)) &&
      (no_filter || passes_filter(e, query.time_range, query.variables, this.variables))
    ) {
      const r: string = rw(e, query.time_range, feats, vars, sep)
      if (r) {
        if (!first_entity) first_entity = k
        rows.push(r)
      }
    }
  })
  res.headers['Content-Type'] = 'text/' + (',' === sep ? 'csv' : 'plain') + '; charset=utf-8'
  res.body = rows.join('\n')
  const times: number[] = this.meta.overall.value,
    filename =
      'export_' +
      (in_browser && !all_data && first_entity
        ? entities[first_entity].group + '_'
        : query.dataset
        ? query.dataset.value + '_'
        : '') +
      (query.time_range[0] === query.time_range[1]
        ? times[query.time_range[0]]
        : times[query.time_range[0]] + '-' + times[query.time_range[1]]) +
      '_' +
      (1 === vars.length
        ? this.variables[vars[0]].meta.short_name.toLowerCase().replace(patterns.non_letter_num, '-')
        : vars.length + '-variables') +
      '_' +
      (rows.join('\n').split('\n').length - 1) +
      'x' +
      rows[0].split(sep).length
  Iif (in_browser) {
    const e = document.createElement('a')
    document.body.appendChild(e)
    e.rel = 'noreferrer'
    e.target = '_blank'
    e.download = filename + '.' + query.file_format
    e.href = URL.createObjectURL(new Blob([res.body], {type: res.headers['Content-Type']}))
    setTimeout(function () {
      e.dispatchEvent(new MouseEvent('click'))
      URL.revokeObjectURL.bind(null, e.href)
      document.body.removeChild(e)
    }, 0)
  }
  res.statusCode = 200
  res.headers['Content-Disposition'] += filename + '.' + query.file_format
  return res
}