<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var CellParser = {
  __proto__: grammar,

  START: alt(sym('number'), sym('formula'), sym('string')),

  formula: seq1(1, '=', sym('expr')),

  expr: alt(
    sym('number'),
    sym('cell'),
    sym('add'),
    sym('sub'),
    sym('mul'),
    sym('div'),
    sym('mod'),
    sym('sum'),
    sym('prod')
  ),

  add:  seq(literal_ic('add('),  sym('expr'), ',', sym('expr'), ')'),
  sub:  seq(literal_ic('sub('),  sym('expr'), ',', sym('expr'), ')'),
  mul:  seq(literal_ic('mul('),  sym('expr'), ',', sym('expr'), ')'),
  div:  seq(literal_ic('div('),  sym('expr'), ',', sym('expr'), ')'),
  mod:  seq(literal_ic('mod('),  sym('expr'), ',', sym('expr'), ')'),
  sum:  seq(literal_ic('sum('),  sym('vargs'), ')'),
  prod: seq(literal_ic('prod('), sym('vargs'), ')'),

  vargs: repeat(alt(sym('range'), sym('expr')), ','),

  range: seq(sym('col'), sym('row'), ':', sym('col'), sym('row')),

  number: str(seq(
    optional('-'),
    str(alt(
      seq(str(repeat(sym('digit'))), '.', str(plus(sym('digit')))),
      plus(sym('digit')))))),

  cell: seq(sym('col'), sym('row')),

  col: alt(sym('az'), sym('AZ')),

  digit: range('0', '9'),

  az: range('a', 'z'),

  AZ: range('A', 'Z'),

  row: str(repeat(sym('digit'), null, 1, 2)),

  string: str(repeat(anyChar))
}.addActions({
  add: function(a) { return function(cs) { return a[1](cs) + a[3](cs); }; },
  sub: function(a) { return function(cs) { return a[1](cs) - a[3](cs); }; },
  mul: function(a) { return function(cs) { return a[1](cs) * a[3](cs); }; },
  div: function(a) { return function(cs) { return a[1](cs) / a[3](cs); }; },
  mod: function(a) { return function(cs) { return a[1](cs) % a[3](cs); }; },
  sum: function(a) { return function(cs) {
    var arr = a[1](cs), sum = 0;
    for ( var i = 0 ; i < arr.length ; i++ ) sum += arr[i];
    return sum;
  }; },
  prod: function(a) { return function(cs) {
    var arr = a[1](cs), prod = 1;
    for ( var i = 0 ; i < arr.length ; i++ ) prod *= arr[i];
    return prod;
  }; },
  az:  function(c) { return c.toUpperCase(); },
  //AZ:  function(c) { return c.charCodeAt(0) - 'A'.charCodeAt(0); },
  row: function(c) { return parseInt(c); },
  number: function(s) { var f = parseFloat(s); return function() { return f; }; },
  cell: function(a) { return function(cs) { return cs.cell(a[0] + a[1]).numValue; }; },
  vargs: function(a) {
    return function(cs) {
      var ret = [];
      for ( var i = 0 ; i < a.length ; i++ ) {
        var r = a[i](cs);
        if ( Array.isArray(r) )
          ret.pushAll(r);
        else
          ret.push(r);
      }
      return ret;
    }
  },
  range: function(a) {
    var c1 = a[0], r1 = a[1], c2 = a[3], r2 = a[4];
    return function(cs) {
      var ret = [];
      for ( var c = c1 ; c <= c2; c++ )
        for ( var r = r1 ; r <= r2 ; r++ )
          ret.push(cs.cell(c + r).numValue);
      return ret;
    }
  },
  string: function(s) { return function() { return s; }; }
});

// https://www.artima.com/pins1ed/the-scells-spreadsheet.html
foam.CLASS({
  package: 'com.google.foam.demos.sevenguis',
  name: 'Cells',
  extends: 'foam.u2.Element',

  requires: [ 'foam.u2.tag.Input', 'foam.u2.ElementParser', 'foam.u2.PropertyView' ],
  imports:  [ 'dynamicFn' ],
  exports:  [ 'as cells' ],

  models: [
    {
      name: 'Cell',
      extends: 'foam.u2.ReadWriteView',
      requires: [ 'foam.u2.tag.Input' ],
      imports: [ 'cells' ],
      documentation: function() {/*
        Doesn't build inner views until value is set or user clicks on view.
        This complicates the design but saves memory and startup time.
      */},
      properties: [
        {
          name: 'formula',
          displayWidth: 10
        },
        {
          name: 'value',
          adapt: function(_, v) {
            var ret = parseFloat(v);
            return ret && ! Number.isInteger(ret) ? ret.toFixed(2) : v;
          },
          displayWidth: 12
        },
        {
          name: 'numValue',
          getter: function() { return parseFloat(this.value); }
        }
      ],
      methods: [
        function isLoaded() { return this.value; },
        function listenForLoad() { this.value$.addListener(this.onDataLoad); },
        function toReadE() { return this.E('span').add(this.value$); },
        function toWriteE() {
          this.formula$.addListener(this.onDataLoad);
          var e = this.E('input');
          e.data$ = this.formula$;
          return e;
        }
      ],

      templates: [
        function CSS() {/*
          ^ > span {
            display: block;
            height: 15px;
            padding: 2px;
            width: 100%;
          }
          ^ > input {
            border: none;
            outline: 1px solid blue;
            outline-offset: 0;
            padding-left: 2px;
            width: 100%;
          }
        */},
        function initE(){/*#U2*/}
      ]
    }
  ],

  properties: [
    [ 'rows',    99 ],
    [ 'columns', 26 ],
    {
      name: 'cells',
      factory: function() { return {}; }
    },
    {
      name: 'parser',
      factory: function() { return CellParser; }
    }
  ],

  methods: [
    function init() {
      this.SUPER();

      this.ElementParser.getPrototype();

      // Two sample spreadsheets
      // Spreadsheet taken from Visicalc
// this.loadCells({"A0":"Item","B0":"No.","C0":"Unit","D0":"Cost","A1":"Muck Rake","B1":"43","C1":"12.95","D1":"=mul(B1,C1)","A2":"Buzz Cut","B2":"15","C2":"6.76","D2":"=mul(B2,C2)","A3":"Toe Toner","B3":"250","C3":"49.95","D3":"=mul(B3,C3)","A4":"Eye Snuff","B4":"2","C4":"4.95","D4":"=mul(B4,C4)","C5":"Subtotal","D5":"=sum(D1:D4)","B6":"9.75","C6":"Tax","D6":"=div(mul(B6,D5),100)","C7":"Total","D7":"=add(D5,D6)"});

      // Spreadsheet to test all functions
//      this.loadCells({"A0":"Formulas","B0":"Values","A1":" 1","B1":"1","A2":" 10","B2":"10","A3":" 10.12","B3":"10.12","A4":" -10.1","B4":"-10.1","A5":" foobar","B5":"foobar","A6":" =add(1,2)","B6":"=add(1,2)","A7":" =sub(2,1)","B7":"=sub(2,1)","A8":" =mul(2,3)","B8":"=mul(2,3)","A9":" =div(9,3)","B9":"=div(9,3)","A10":" =mod(8,3)","B10":"=mod(8,3)","A11":" =add(mul(2,3),div(3,2))","B11":"=add(mul(2,3),div(3,2))","A12":" =A1","B12":"=A1","A13":" =add(A1,B1)","B13":"=add(A1,B1)","A14":" =sum(1,2,3,4,5)","B14":"=sum(1,2,3,4,5)","A15":" =sum(B6:B10)","B15":"=sum(B6:B10)","A16":" =prod(B6:B10)","B16":"=prod(B6:B10)"});

this.loadCells({"A0":"
Benchmark
","B0":"IndexedDB","C0":"DAO","A1":"Create Albums","B1":"190","C1":"366","A2":"Create Photos","B2":"2772","C2":"2492","A3":"Select All Albums","B3":"168","C3":"1.93","A4":"Select All Photos","B4":"1361","C4":"3.86","B5":"1.43","C5":"0.06","B6":"1.56","C6":"0.63","B7":"10.28","C7":"1.12","D0":"Speedup","D1":"=div(B1,C1)","D2":"=div(B2,C2)","D3":"=div(B3,C3)","D4":"=div(B4,C4)","A5":"Single Key Query","D5":"=div(B5,C5)","A6":"Multi-Key Query","D6":"=div(B6,C6)","A7":"Multi-Key Query","D7":"=div(B7,C7)","A8":"Multi-Key Query","B8":"102","C8":"12.24","D8":"=div(B8,C8)","A9":"Multi-Key Query","B9":"561","C9":"15.24","D9":"=div(B9,C9)","A10":"Indexed Field Query","B10":"4.63","C10":"0.46","D10":"=div(B10,C10)","A11":"Ad-Hoc Query","B11":"658","C11":"9.91","D11":"=div(B11,C11)","A12":"Simple Inner-Join","B12":"721","C12":"9.55","D12":"=div(B12,C12)","A13":"Inner-Join Aggregation","B13":"647","C13":"38.56","D13":"=div(B13,C13)","A14":"Order-By","B14":"59","C14":"0.55","D14":"=div(B14,C14)","A15":"Order and Group By","B15":"1232","C15":"3.63","D15":"=div(B15,C15)","A16":"Average:","B16":"=SUM(B1:B15)","C16":"=SUM(C1:C15)","D16":"=div(B14,C14)"});
    },
    function loadCells(map) {
      for ( var key in map ) this.cell(key).formula = String(map[key]);
    },
    function save() {
      var map = {};
      for ( var key in this.cells ) {
        var cell = this.cells[key];
        if ( cell.formula !== '' ) map[key] = cell.formula;
      }
      return map;
    },
    function cellName(c, r) { return String.fromCharCode(65 + c) + r; },
    function cell(name) {
      var self = this;
      var cell = this.cells[name];
      var cancel = null;
      if ( ! cell ) {
        cell = this.cells[name] = this.Cell.create();
        cell.formula$.addListener(function(_, __, ___, formula) {
          var f = self.parser.parseString(formula);
          cancel && cancel.destroy();
          cancel = self.dynamicFn(f.bind(null, self), function(v) {
            cell.value = v;
          });
        });
      }
      return cell;
    }
  ],
  templates: [
    function CSS() {/*
      ^ tr, ^ td, ^ th, ^ input {
        color: #333;
        font: 13px roboto, arial, sans-serif;
      }
      ^ tr { height: 26px; }
      ^cell { min-width: 102px; }
      ^, ^ th, ^ td { border: 1px solid #ccc; }
      ^ td { height: 100%; }
      ^ th, ^ td {
        border-right: none;
        border-bottom: none;
      }
      ^ th {
        background: #eee;
        color: #333;
        padding: 2px 18px;
      }
      ^ {
        border-left: none;
        border-top: none;
        overflow: auto;
      }
    */},
    function initE() {/*#U2

          {{String.fromCharCode(65 + j)}}

          {{i}}
          {{this.cell(this.cellName(j, i))}}

    */}
  ]
});
<commit_msg>Porting Cells to FOAM2.<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var CellParser = {
  __proto__: grammar,

  START: alt(sym('number'), sym('formula'), sym('string')),

  formula: seq1(1, '=', sym('expr')),

  expr: alt(
    sym('number'),
    sym('cell'),
    sym('add'),
    sym('sub'),
    sym('mul'),
    sym('div'),
    sym('mod'),
    sym('sum'),
    sym('prod')
  ),

  add:  seq(literal_ic('add('),  sym('expr'), ',', sym('expr'), ')'),
  sub:  seq(literal_ic('sub('),  sym('expr'), ',', sym('expr'), ')'),
  mul:  seq(literal_ic('mul('),  sym('expr'), ',', sym('expr'), ')'),
  div:  seq(literal_ic('div('),  sym('expr'), ',', sym('expr'), ')'),
  mod:  seq(literal_ic('mod('),  sym('expr'), ',', sym('expr'), ')'),
  sum:  seq(literal_ic('sum('),  sym('vargs'), ')'),
  prod: seq(literal_ic('prod('), sym('vargs'), ')'),

  vargs: repeat(alt(sym('range'), sym('expr')), ','),

  range: seq(sym('col'), sym('row'), ':', sym('col'), sym('row')),

  number: str(seq(
    optional('-'),
    str(alt(
      seq(str(repeat(sym('digit'))), '.', str(plus(sym('digit')))),
      plus(sym('digit')))))),

  cell: seq(sym('col'), sym('row')),

  col: alt(sym('az'), sym('AZ')),

  digit: range('0', '9'),

  az: range('a', 'z'),

  AZ: range('A', 'Z'),

  row: str(repeat(sym('digit'), null, 1, 2)),

  string: str(repeat(anyChar))
}.addActions({
  add: function(a) { return function(cs) { return a[1](cs) + a[3](cs); }; },
  sub: function(a) { return function(cs) { return a[1](cs) - a[3](cs); }; },
  mul: function(a) { return function(cs) { return a[1](cs) * a[3](cs); }; },
  div: function(a) { return function(cs) { return a[1](cs) / a[3](cs); }; },
  mod: function(a) { return function(cs) { return a[1](cs) % a[3](cs); }; },
  sum: function(a) { return function(cs) {
    var arr = a[1](cs), sum = 0;
    for ( var i = 0 ; i < arr.length ; i++ ) sum += arr[i];
    return sum;
  }; },
  prod: function(a) { return function(cs) {
    var arr = a[1](cs), prod = 1;
    for ( var i = 0 ; i < arr.length ; i++ ) prod *= arr[i];
    return prod;
  }; },
  az:  function(c) { return c.toUpperCase(); },
  //AZ:  function(c) { return c.charCodeAt(0) - 'A'.charCodeAt(0); },
  row: function(c) { return parseInt(c); },
  number: function(s) { var f = parseFloat(s); return function() { return f; }; },
  cell: function(a) { return function(cs) { return cs.cell(a[0] + a[1]).numValue; }; },
  vargs: function(a) {
    return function(cs) {
      var ret = [];
      for ( var i = 0 ; i < a.length ; i++ ) {
        var r = a[i](cs);
        if ( Array.isArray(r) )
          ret.pushAll(r);
        else
          ret.push(r);
      }
      return ret;
    }
  },
  range: function(a) {
    var c1 = a[0], r1 = a[1], c2 = a[3], r2 = a[4];
    return function(cs) {
      var ret = [];
      for ( var c = c1 ; c <= c2; c++ )
        for ( var r = r1 ; r <= r2 ; r++ )
          ret.push(cs.cell(c + r).numValue);
      return ret;
    }
  },
  string: function(s) { return function() { return s; }; }
});

// https://www.artima.com/pins1ed/the-scells-spreadsheet.html
foam.CLASS({
  package: 'com.google.foam.demos.sevenguis',
  name: 'Cells',
  extends: 'foam.u2.Element',

  requires: [
    'foam.u2.tag.Input',
    'foam.u2.ElementParser',
    'foam.u2.PropertyView'
  ],

  exports:  [ 'as cells' ],

  classes: [
    {
      name: 'Cell',
      extends: 'foam.u2.ReadWriteView',
      requires: [ 'foam.u2.tag.Input' ],
      imports: [ 'cells' ],
      documentation: function() {/*
        Doesn't build inner views until value is set or user clicks on view.
        This complicates the design but saves memory and startup time.
      */},
      axioms: [
        foam.u2.CSS.create({
          code: function() {/*
            ^ > span {
              display: block;
              height: 15px;
              padding: 2px;
              width: 100%;
            }
            ^ > input {
              border: none;
              outline: 1px solid blue;
              outline-offset: 0;
              padding-left: 2px;
              width: 100%;
            }
          */}
        })
      ],
      properties: [
        [ 'nodeName', 'span' ],
        {
          name: 'formula',
          displayWidth: 10
        },
        {
          name: 'value',
          adapt: function(_, v) {
            var ret = parseFloat(v);
            return ret && ! Number.isInteger(ret) ? ret.toFixed(2) : v;
          },
          displayWidth: 12
        },
        {
          name: 'numValue',
          getter: function() { return parseFloat(this.value); }
        }
      ],
      methods: [
        function initE() { this.cssClass(this.myCls()); },
        function isLoaded() { return this.value; },
        function listenForLoad() { this.value$.addListener(this.onDataLoad); },
        function toReadE() { return this.E('span').add(this.value$); },
        function toWriteE() {
          this.formula$.addListener(this.onDataLoad);
          var e = this.E('input');
          e.data$ = this.formula$;
          return e;
        }
      ]
    }
  ],

  axioms: [
    foam.u2.CSS.create({
      code: function() {/*
      ^ tr, ^ td, ^ th, ^ input {
        color: #333;
        font: 13px roboto, arial, sans-serif;
      }
      ^ tr { height: 26px; }
      ^cell { min-width: 102px; }
      ^, ^ th, ^ td { border: 1px solid #ccc; }
      ^ td { height: 100%; }
      ^ th, ^ td {
        border-right: none;
        border-bottom: none;
      }
      ^ th {
        background: #eee;
        color: #333;
        padding: 2px 18px;
      }
      ^ {
        border-left: none;
        border-top: none;
        overflow: auto;
      }
      */}
    })
  ],

  properties: [
    [ 'rows',    99 ],
    [ 'columns', 26 ],
    {
      name: 'cells',
      factory: function() { return {}; }
    },
    {
      name: 'parser',
      factory: function() { return CellParser; }
    }
  ],

  methods: [
    function init() {
      this.SUPER();

      this.ElementParser.getPrototype();

      // Two sample spreadsheets
      // Spreadsheet taken from Visicalc
// this.loadCells({"A0":"Item","B0":"No.","C0":"Unit","D0":"Cost","A1":"Muck Rake","B1":"43","C1":"12.95","D1":"=mul(B1,C1)","A2":"Buzz Cut","B2":"15","C2":"6.76","D2":"=mul(B2,C2)","A3":"Toe Toner","B3":"250","C3":"49.95","D3":"=mul(B3,C3)","A4":"Eye Snuff","B4":"2","C4":"4.95","D4":"=mul(B4,C4)","C5":"Subtotal","D5":"=sum(D1:D4)","B6":"9.75","C6":"Tax","D6":"=div(mul(B6,D5),100)","C7":"Total","D7":"=add(D5,D6)"});

      // Spreadsheet to test all functions
//      this.loadCells({"A0":"Formulas","B0":"Values","A1":" 1","B1":"1","A2":" 10","B2":"10","A3":" 10.12","B3":"10.12","A4":" -10.1","B4":"-10.1","A5":" foobar","B5":"foobar","A6":" =add(1,2)","B6":"=add(1,2)","A7":" =sub(2,1)","B7":"=sub(2,1)","A8":" =mul(2,3)","B8":"=mul(2,3)","A9":" =div(9,3)","B9":"=div(9,3)","A10":" =mod(8,3)","B10":"=mod(8,3)","A11":" =add(mul(2,3),div(3,2))","B11":"=add(mul(2,3),div(3,2))","A12":" =A1","B12":"=A1","A13":" =add(A1,B1)","B13":"=add(A1,B1)","A14":" =sum(1,2,3,4,5)","B14":"=sum(1,2,3,4,5)","A15":" =sum(B6:B10)","B15":"=sum(B6:B10)","A16":" =prod(B6:B10)","B16":"=prod(B6:B10)"});

this.loadCells({"A0":"
Benchmark
","B0":"IndexedDB","C0":"DAO","A1":"Create Albums","B1":"190","C1":"366","A2":"Create Photos","B2":"2772","C2":"2492","A3":"Select All Albums","B3":"168","C3":"1.93","A4":"Select All Photos","B4":"1361","C4":"3.86","B5":"1.43","C5":"0.06","B6":"1.56","C6":"0.63","B7":"10.28","C7":"1.12","D0":"Speedup","D1":"=div(B1,C1)","D2":"=div(B2,C2)","D3":"=div(B3,C3)","D4":"=div(B4,C4)","A5":"Single Key Query","D5":"=div(B5,C5)","A6":"Multi-Key Query","D6":"=div(B6,C6)","A7":"Multi-Key Query","D7":"=div(B7,C7)","A8":"Multi-Key Query","B8":"102","C8":"12.24","D8":"=div(B8,C8)","A9":"Multi-Key Query","B9":"561","C9":"15.24","D9":"=div(B9,C9)","A10":"Indexed Field Query","B10":"4.63","C10":"0.46","D10":"=div(B10,C10)","A11":"Ad-Hoc Query","B11":"658","C11":"9.91","D11":"=div(B11,C11)","A12":"Simple Inner-Join","B12":"721","C12":"9.55","D12":"=div(B12,C12)","A13":"Inner-Join Aggregation","B13":"647","C13":"38.56","D13":"=div(B13,C13)","A14":"Order-By","B14":"59","C14":"0.55","D14":"=div(B14,C14)","A15":"Order and Group By","B15":"1232","C15":"3.63","D15":"=div(B15,C15)","A16":"Average:","B16":"=SUM(B1:B15)","C16":"=SUM(C1:C15)","D16":"=div(B14,C14)"});
    },

    function initE() {
      this.setNodeName('table').cssClass(this.myCls()).attrs({cellspacing: 0}).
        start('tr').
          tag('th').
          call(function () {
            for ( var i = 0 ; i < this.columns ; i++ ) {
              this.start('th').add(String.fromCharCode(65 + i)).end();
            }
          }).
        end().
        call(function() {
          for ( var i = 0 ; i < this.rows ; i++ ) {
            this.start('tr').
              start('th').add(i).end().
              start('td').
                call(function() {
                  for ( var j = 0 ; j < this.columns ; j++ ) {
                    this.add(this.cell(this.cellName(j, i)));
                  }
                }).
              end().
            end();
          }
        });
    },

              /*
    function initE() {

          {{String.fromCharCode(65 + j)}}

          {{i}}
          {{this.cell(this.cellName(j, i))}}

    },
                */

    function loadCells(map) {
      for ( var key in map ) this.cell(key).formula = String(map[key]);
    },

    function save() {
      var map = {};
      for ( var key in this.cells ) {
        var cell = this.cells[key];
        if ( cell.formula !== '' ) map[key] = cell.formula;
      }
      return map;
    },

    function cellName(c, r) { return String.fromCharCode(65 + c) + r; },

    function cell(name) {
      var self = this;
      var cell = this.cells[name];
      var cancel = null;
      if ( ! cell ) {
        cell = this.cells[name] = this.Cell.create();
        cell.formula$.addListener(function(_, __, ___, formula) {
          var f = self.parser.parseString(formula);
          cancel && cancel.destroy();
          cancel = self.dynamicFn(f.bind(null, self), function(v) {
            cell.value = v;
          });
        });
      }
      return cell;
    }
  ],
  templates: [
  ]
});
<|endoftext|>


<commit_before>import { UIDatabase } from '..';
import { createRecord } from './createRecord';

/**
 * Returns a filter method for finding a subset of an array of IndicatorAttribute objects by period.
 * @param {Array.<IndicatorAttribute>} filterPeriod
 * @return {Func}
 */
const filterByPeriod = filterPeriod => ({ period }) => period.id === filterPeriod.id;

/**
 * Returns a filter method for getting the intersection of two arrays of IndicatorValue objects.
 * @param {Array.<IndicatorValue>} values
 * @return {Func}
 */
const filterByValues = values => value => includesValue(values, value);

/**
 * Returns a find method for querying an array of indicatorValue for the index of a given object.
 * @param {IndicatorValue} findValue
 * @return {Func}
 */
const findByValue = findValue => value => findValue.id === value.id;

/**
 * Query if an IndicatorValue array contains a given object.
 * @param {Array.<IndicatorValue>} values
 * @param {IndicatorValue} value
 */
const includesValue = (values, value) => values.some(findByValue(value));

/**
 * Create a new indicator value.
 * @param {IndicatorAttribute} row
 * @param {IndicatorAttribute} column
 * @param {Period} period
 * @return {IndicatorValue}
 */
const createIndicatorValue = (row, column, period) => {
  let record;
  UIDatabase.write(() => {
    record = createRecord(UIDatabase, 'IndicatorValue', row, column, period);
  });
  return record;
};

/**
 * Update indicator value object with new value.
 * @param {IndicatorValue} indicatorValue
 * @param {string} value
 */
const updateIndicatorValue = (indicatorValue, value) => {
  UIDatabase.write(() => {
    UIDatabase.update('IndicatorValue', { ...indicatorValue, value });
  });
};

/**
 * Get all indicator values for a given period.
 * @param {*} period
 */
const getPeriodIndicatorValues = period =>
  UIDatabase.objects('IndicatorValue').filtered('period.id == $0', period.id);

/**
 * Get value for indicator row, column attribute pair.
 *
 * @param {IndicatorAttribute} row
 * @param {IndicatorAttribute} column
 * @param {Period} period
 * @return {IndicatorValue}
 */
const getRowColumnIndicatorValue = (row, column, period) => {
  const rowValues = row.values.filter(filterByPeriod(period));
  console.log(row.values.map(({ id }) => id));
  console.log(period.id);
  console.log(row.id);
  console.log(column.id);
  console.log(rowValues.map(({ value }) => value));
  const columnValues = column.values.filter(filterByPeriod(period));
  const rowColumnValues = rowValues?.filter(filterByValues(columnValues));
  const [rowColumnValue] = rowColumnValues;
  return rowColumnValue; // createIndicatorValue(row, column, period);
};

/**
 * Find indicator row by id.
 * @param {Array.<IndicatorAttribute} indicatorRows
 * @param {string} rowId
 * @return {IndicatorAttribute}
 */
const getIndicatorRow = (indicatorRows, rowId) => indicatorRows.find(({ id }) => id === rowId);

/**
 * Get indicator rows by id.
 * @param {ProgramIndicator} indicator
 * @param {string} id
 * @return {Array.<IndicatorAttribute>}
 */
const getIndicatorRows = (indicator, id) =>
  indicator?.rows?.filter(({ id: rowId }) => rowId === id);

/**
 * Find indicator column by code.
 * @param {Array.<IndicatorAttribute>} indicatorColumns
 * @param {string} columnCode
 * @return {IndicatorAttribute}
 */
const getIndicatorColumn = (indicatorColumns, columnCode) =>
  indicatorColumns.find(({ code }) => code === columnCode);

/**
 * Get indicator columns by code.
 * @param {ProgramIndicator} indicator
 * @param {string} code
 * @return {Array.<IndicatorAttribute>}
 */
const getIndicatorColumns = (indicator, code) =>
  indicator?.columns?.filter(({ code: columnCode }) => columnCode === code);

const getIndicatorData = indicator => ({
  rows: indicator.rows,
  columns: indicator.columns,
});

export {
  createIndicatorValue,
  updateIndicatorValue,
  getRowColumnIndicatorValue,
  getPeriodIndicatorValues,
  getIndicatorRow,
  getIndicatorRows,
  getIndicatorColumn,
  getIndicatorColumns,
  getIndicatorData,
};

export default getIndicatorData;
<commit_msg>Fix up getRowColumnIndicatorValue helper<commit_after>import { UIDatabase } from '..';
import { createRecord } from './createRecord';

/**
 * Returns a filter method for finding a subset of an array of IndicatorAttribute objects by period.
 * @param {Array.<IndicatorAttribute>} filterPeriod
 * @return {Func}
 */
const filterByPeriod = filterPeriod => ({ period }) => period.id === filterPeriod.id;

/**
 * Returns a filter method for getting the intersection of two arrays of IndicatorValue objects.
 * @param {Array.<IndicatorValue>} values
 * @return {Func}
 */
const filterByValues = values => value => includesValue(values, value);

/**
 * Returns a find method for querying an array of indicatorValue for the index of a given object.
 * @param {IndicatorValue} findValue
 * @return {Func}
 */
const findByValue = findValue => value => findValue.id === value.id;

/**
 * Query if an IndicatorValue array contains a given object.
 * @param {Array.<IndicatorValue>} values
 * @param {IndicatorValue} value
 */
const includesValue = (values, value) => values.some(findByValue(value));

/**
 * Create a new indicator value.
 * @param {IndicatorAttribute} row
 * @param {IndicatorAttribute} column
 * @param {Period} period
 * @return {IndicatorValue}
 */
const createIndicatorValue = (row, column, period) => {
  let record;
  UIDatabase.write(() => {
    record = createRecord(UIDatabase, 'IndicatorValue', row, column, period);
  });
  return record;
};

/**
 * Update indicator value object with new value.
 * @param {IndicatorValue} indicatorValue
 * @param {string} value
 */
const updateIndicatorValue = (indicatorValue, value) => {
  UIDatabase.write(() => {
    UIDatabase.update('IndicatorValue', { ...indicatorValue, value });
  });
};

/**
 * Get all indicator values for a given period.
 * @param {*} period
 */
const getPeriodIndicatorValues = period =>
  UIDatabase.objects('IndicatorValue').filtered('period.id == $0', period.id);

/**
 * Get value for indicator row, column attribute pair.
 *
 * @param {IndicatorAttribute} row
 * @param {IndicatorAttribute} column
 * @param {Period} period
 * @return {IndicatorValue}
 */
const getRowColumnIndicatorValue = (row, column, period) => {
  const rowColumnValues = UIDatabase.objects('IndicatorValue').filtered('row.id = $0 AND column.id = $1 AND period.id = $2', row.id, column.id, period.id).slice();
  const [rowColumnValue] = rowColumnValues;
  return rowColumnValue ?? createIndicatorValue(row, column, period);
};

/**
 * Find indicator row by id.
 * @param {Array.<IndicatorAttribute} indicatorRows
 * @param {string} rowId
 * @return {IndicatorAttribute}
 */
const getIndicatorRow = (indicatorRows, rowId) => indicatorRows.find(({ id }) => id === rowId);

/**
 * Get indicator rows by id.
 * @param {ProgramIndicator} indicator
 * @param {string} id
 * @return {Array.<IndicatorAttribute>}
 */
const getIndicatorRows = (indicator, id) =>
  indicator?.rows?.filter(({ id: rowId }) => rowId === id);

/**
 * Find indicator column by code.
 * @param {Array.<IndicatorAttribute>} indicatorColumns
 * @param {string} columnCode
 * @return {IndicatorAttribute}
 */
const getIndicatorColumn = (indicatorColumns, columnCode) =>
  indicatorColumns.find(({ code }) => code === columnCode);

/**
 * Get indicator columns by code.
 * @param {ProgramIndicator} indicator
 * @param {string} code
 * @return {Array.<IndicatorAttribute>}
 */
const getIndicatorColumns = (indicator, code) =>
  indicator?.columns?.filter(({ code: columnCode }) => columnCode === code);

const getIndicatorData = indicator => ({
  rows: indicator.rows,
  columns: indicator.columns,
});

export {
  createIndicatorValue,
  updateIndicatorValue,
  getRowColumnIndicatorValue,
  getPeriodIndicatorValues,
  getIndicatorRow,
  getIndicatorRows,
  getIndicatorColumn,
  getIndicatorColumns,
  getIndicatorData,
};

export default getIndicatorData;
<|endoftext|>


<commit_before>// FIRST TIME ONLY- run:
//   ./node_modules/.bin/webdriver-manager update
//
//   Try: `npm run webdriver:update`
//
// AND THEN EVERYTIME ...
//   1. Compile with `tsc`
//   2. Make sure the test server (e.g., lite-server: localhost:8080) is running.
//   3. ./node_modules/.bin/protractor protractor.config.js
//
//   To do all steps, try:  `npm run e2e`

"use strict";
var protractor_1 = require('protractor');
var fs = require('fs');
var path = require('canonical-path');
var _ = require('lodash');

exports.config = {
  directConnect: false,
  seleniumAddress: 'http://localhost:4444/wd/hub',

  // Capabilities to be passed to the webdriver instance.
  capabilities: {
    'browserName': 'chrome'
  },
  framework: 'custom',
  // serenity-js generates nice reports, not using the ScreenPlay pattern for implementation
  frameworkPath: require.resolve('serenity-js'),
  specs: [
    'e2e/features/*.feature'
  ],

  // For angular tests
  useAllAngular2AppRoots: true,
  // Base URL for application server
  baseUrl: 'http://localhost:8080',

  cucumberOpts: {
    require: ['e2e/steps/*.js', 'e2e/support/*.js'],
    format: 'json:results.json',

  },
};

// Custom reporter
function Reporter(options) {
  var _defaultOutputFile = path.resolve(process.cwd(), './_test-output', 'protractor-results.txt');
  options.outputFile = options.outputFile || _defaultOutputFile;

  initOutputFile(options.outputFile);
  options.appDir = options.appDir || './';
  var _root = { appDir: options.appDir, suites: [] };
  log('AppDir: ' + options.appDir, +1);
  var _currentSuite;

  this.suiteStarted = function (suite) {
    _currentSuite = { description: suite.description, status: null, specs: [] };
    _root.suites.push(_currentSuite);
    log('Suite: ' + suite.description, +1);
  };

  this.suiteDone = function (suite) {
    var statuses = _currentSuite.specs.map(function (spec) {
      return spec.status;
    });
    statuses = _.uniq(statuses);
    var status = statuses.indexOf('failed') >= 0 ? 'failed' : statuses.join(', ');
    _currentSuite.status = status;
    log('Suite ' + _currentSuite.status + ': ' + suite.description, -1);
  };

  this.specStarted = function (spec) {

  };

  this.specDone = function (spec) {
    var currentSpec = {
      description: spec.description,
      status: spec.status
    };
    if (spec.failedExpectations.length > 0) {
      currentSpec.failedExpectations = spec.failedExpectations;
    }

    _currentSuite.specs.push(currentSpec);
    log(spec.status + ' - ' + spec.description);
  };

  this.jasmineDone = function () {
    outputFile = options.outputFile;
    //// Alternate approach - just stringify the _root - not as pretty
    //// but might be more useful for automation.
    // var output = JSON.stringify(_root, null, 2);
    var output = formatOutput(_root);
    fs.appendFileSync(outputFile, output);
  };

  function ensureDirectoryExistence(filePath) {
    var dirname = path.dirname(filePath);
    if (directoryExists(dirname)) {
      return true;
    }
    ensureDirectoryExistence(dirname);
    fs.mkdirSync(dirname);
  }

  function directoryExists(path) {
    try {
      return fs.statSync(path).isDirectory();
    }
    catch (err) {
      return false;
    }
  }

  function initOutputFile(outputFile) {
    ensureDirectoryExistence(outputFile);
    var header = "Protractor results for: " + (new Date()).toLocaleString() + "\n\n";
    fs.writeFileSync(outputFile, header);
  }

  // for output file output
  function formatOutput(output) {
    var indent = '  ';
    var pad = '  ';
    var results = [];
    results.push('AppDir:' + output.appDir);
    output.suites.forEach(function (suite) {
      results.push(pad + 'Suite: ' + suite.description + ' -- ' + suite.status);
      pad += indent;
      suite.specs.forEach(function (spec) {
        results.push(pad + spec.status + ' - ' + spec.description);
        if (spec.failedExpectations) {
          pad += indent;
          spec.failedExpectations.forEach(function (fe) {
            results.push(pad + 'message: ' + fe.message);
          });
          pad = pad.substr(2);
        }
      });
      pad = pad.substr(2);
      results.push('');
    });
    results.push('');
    return results.join('\n');
  }

  // for console output
  var _pad;
  function log(str, indent) {
    _pad = _pad || '';
    if (indent == -1) {
      _pad = _pad.substr(2);
    }
    console.log(_pad + str);
    if (indent == 1) {
      _pad = _pad + '  ';
    }
  }

}
<commit_msg>Use directConnect<commit_after>// FIRST TIME ONLY- run:
//   ./node_modules/.bin/webdriver-manager update
//
//   Try: `npm run webdriver:update`
//
// AND THEN EVERYTIME ...
//   1. Compile with `tsc`
//   2. Make sure the test server (e.g., lite-server: localhost:8080) is running.
//   3. ./node_modules/.bin/protractor protractor.config.js
//
//   To do all steps, try:  `npm run e2e`

"use strict";
var protractor_1 = require('protractor');
var fs = require('fs');
var path = require('canonical-path');
var _ = require('lodash');

exports.config = {
  directConnect: true,

  // Capabilities to be passed to the webdriver instance.
  capabilities: {
    'browserName': 'chrome'
  },
  framework: 'custom',
  // serenity-js generates nice reports, not using the ScreenPlay pattern for implementation
  frameworkPath: require.resolve('serenity-js'),
  specs: [
    'e2e/features/*.feature'
  ],

  // For angular tests
  useAllAngular2AppRoots: true,
  // Base URL for application server
  baseUrl: 'http://localhost:8080',

  cucumberOpts: {
    require: ['e2e/steps/*.js', 'e2e/support/*.js'],
    format: 'json:results.json',

  },
};

// Custom reporter
function Reporter(options) {
  var _defaultOutputFile = path.resolve(process.cwd(), './_test-output', 'protractor-results.txt');
  options.outputFile = options.outputFile || _defaultOutputFile;

  initOutputFile(options.outputFile);
  options.appDir = options.appDir || './';
  var _root = { appDir: options.appDir, suites: [] };
  log('AppDir: ' + options.appDir, +1);
  var _currentSuite;

  this.suiteStarted = function (suite) {
    _currentSuite = { description: suite.description, status: null, specs: [] };
    _root.suites.push(_currentSuite);
    log('Suite: ' + suite.description, +1);
  };

  this.suiteDone = function (suite) {
    var statuses = _currentSuite.specs.map(function (spec) {
      return spec.status;
    });
    statuses = _.uniq(statuses);
    var status = statuses.indexOf('failed') >= 0 ? 'failed' : statuses.join(', ');
    _currentSuite.status = status;
    log('Suite ' + _currentSuite.status + ': ' + suite.description, -1);
  };

  this.specStarted = function (spec) {

  };

  this.specDone = function (spec) {
    var currentSpec = {
      description: spec.description,
      status: spec.status
    };
    if (spec.failedExpectations.length > 0) {
      currentSpec.failedExpectations = spec.failedExpectations;
    }

    _currentSuite.specs.push(currentSpec);
    log(spec.status + ' - ' + spec.description);
  };

  this.jasmineDone = function () {
    outputFile = options.outputFile;
    //// Alternate approach - just stringify the _root - not as pretty
    //// but might be more useful for automation.
    // var output = JSON.stringify(_root, null, 2);
    var output = formatOutput(_root);
    fs.appendFileSync(outputFile, output);
  };

  function ensureDirectoryExistence(filePath) {
    var dirname = path.dirname(filePath);
    if (directoryExists(dirname)) {
      return true;
    }
    ensureDirectoryExistence(dirname);
    fs.mkdirSync(dirname);
  }

  function directoryExists(path) {
    try {
      return fs.statSync(path).isDirectory();
    }
    catch (err) {
      return false;
    }
  }

  function initOutputFile(outputFile) {
    ensureDirectoryExistence(outputFile);
    var header = "Protractor results for: " + (new Date()).toLocaleString() + "\n\n";
    fs.writeFileSync(outputFile, header);
  }

  // for output file output
  function formatOutput(output) {
    var indent = '  ';
    var pad = '  ';
    var results = [];
    results.push('AppDir:' + output.appDir);
    output.suites.forEach(function (suite) {
      results.push(pad + 'Suite: ' + suite.description + ' -- ' + suite.status);
      pad += indent;
      suite.specs.forEach(function (spec) {
        results.push(pad + spec.status + ' - ' + spec.description);
        if (spec.failedExpectations) {
          pad += indent;
          spec.failedExpectations.forEach(function (fe) {
            results.push(pad + 'message: ' + fe.message);
          });
          pad = pad.substr(2);
        }
      });
      pad = pad.substr(2);
      results.push('');
    });
    results.push('');
    return results.join('\n');
  }

  // for console output
  var _pad;
  function log(str, indent) {
    _pad = _pad || '';
    if (indent == -1) {
      _pad = _pad.substr(2);
    }
    console.log(_pad + str);
    if (indent == 1) {
      _pad = _pad + '  ';
    }
  }

}
<|endoftext|>


<commit_before>/**
 * http://www.privacyidea.org
 * (c) cornelius kölbel, cornelius@privacyidea.org
 *
 * 2015-01-11 Cornelius Kölbel, <cornelius@privacyidea.org>
 *
 * This code is free software; you can redistribute it and/or
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or any later version.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
myApp.controller("tokenController", function (TokenFactory, ConfigFactory,
                                              $scope, $location, AuthFactory,
                                              $rootScope, gettext, hotkeys) {
    $scope.params = {page: 1, sortdir: "asc"};
    $scope.reverse = false;
    $scope.loggedInUser = AuthFactory.getUser();
    $scope.selectedToken = {serial: null};
    // go to the list view by default
    if ($location.path() == "/token") {
        $location.path("/token/list");
    }

    // Change the pagination
    $scope.pageChanged = function () {
        console.log('Page changed to: ' + $scope.params.page);
        $scope.getTokens();
    };

    // This function fills $scope.tokendata
    $scope.getTokens = function () {
        $scope.params.serial = "*" + ($scope.serialFilter || "") + "*";
        $scope.params.type = "*" + ($scope.typeFilter || "") + "*";
        $scope.params.description = "*" + ($scope.descriptionFilter || "") + "*";
        $scope.params.userid = "*" + ($scope.userIdFilter || "") + "*";
        $scope.params.resolver = "*" + ($scope.resolverFilter || "") + "*";
        $scope.params.pagesize = $scope.token_page_size;
        if ($scope.reverse) {
            $scope.params.sortdir = "desc";
        } else {
            $scope.params.sortdir = "asc";
        }
        TokenFactory.getTokens(function (data) {
            $scope.tokendata = data.result.value;
            console.log($scope.tokendata);
        }, $scope.params);
    };

    $scope.getTokens();

    /*
     * Functions to check and to create a default realm. At the moment this is
     * in the tokenview, as the token view is the first view. This could be
     * changed to be located anywhere else.
     */
    if ($scope.loggedInUser.role == "admin") {
        ConfigFactory.getRealms(function (data) {
            // Check if there is a realm defined, or if we should display the
            // Auto Create Dialog
            number_of_realms = Object.keys(data.result.value).length;
            if (number_of_realms == 0) {
                $('#dialogAutoCreateRealm').modal();
            }
        });
    }

});

myApp.controller("tokenAssignController", function ($scope, TokenFactory,
                                                    $stateParams, AuthFactory,
                                                    UserFactory, $state) {
    $scope.assignToken = function () {
        TokenFactory.assign({
            serial: fixSerial($scope.newToken.serial),
            pin: $scope.newToken.pin
        }, function () {
            $state.go('token.list');
        });
    };
});

myApp.controller("tokenEnrollController", function ($scope, TokenFactory,
                                                    $stateParams, AuthFactory,
                                                    UserFactory, $state,
                                                    ConfigFactory, instanceUrl,
                                                    $http, hotkeys, gettext,
                                                    inform, U2fFactory) {

    hotkeys.bindTo($scope).add({
        combo: 'alt+e',
        description: gettext('Enroll a new token'),
        callback: function (event, hotkey) {
            event.preventDefault();
            $state.go('token.enroll');
            $scope.enrolledToken = null;
        }
    });
    hotkeys.bindTo($scope).add({
        combo: 'alt+r',
        description: gettext('Roll the token'),
        callback: function () {
            $scope.enrollToken();
        }
    });

    $scope.loggedInUser = AuthFactory.getUser();
    $scope.newUser = {};
    $scope.tempData = {};
    $scope.instanceUrl = instanceUrl;
    $scope.click_wait = true;
    $scope.U2FToken = {};
    // System default values for enrollment
    $scope.systemDefault = {};
    // These are values that are also sent to the backend!
    $scope.form = {
        timeStep: 30,
        otplen: 6,
        genkey: true,
        type: $scope.default_tokentype,
        hashlib: "sha1",
        'radius.system_settings': true
    };

    $scope.formInit = {
        tokenTypes: {"hotp": gettext("HOTP: event based One Time Passwords"),
            "totp": gettext("TOTP: time based One Time Passwords"),
            "spass": gettext("SPass: Simple Pass token. Static passwords"),
            "motp": gettext("mOTP: classical mobile One Time Passwords"),
            "sshkey": gettext("SSH Public Key: The public SSH key"),
            "yubikey": gettext("Yubikey AES mode: One Time Passwords with" +
                " Yubikey"),
            "remote": gettext("Remote Token: Forward authentication request" +
                " to another server"),
            "yubico": gettext("Yubikey Cloud mode: Forward authentication" +
                " request to YubiCloud"),
            "radius": gettext("RADIUS: Forward authentication request to a" +
                " RADIUS server"),
            "email": gettext("EMail: Send a One Time Password to the users email" +
                " address."),
            "sms": gettext("SMS: Send a One Time Password to the users" +
                " mobile phone."),
            "certificate": gettext("Certificate: Enroll an x509 Certificate" +
                " Token."),
            "4eyes": gettext("Four Eyes: Two or more users are required to" +
                " log in."),
            "tiqr": gettext("TiQR: Authenticate with Smartphone by scanning" +
                " a QR code."),
            "u2f": gettext("U2F: Universal 2nd Factor hardware token.")},
        timesteps: [30, 60],
        otplens: [6, 8],
        hashlibs: ["sha1", "sha256", "sha512"]
    };

    // These token need to PIN
    // TODO: THis is also contained in the tokentype class!
    $scope.changeTokenType = function() {
        console.log("Token Type Changed.");
        if (["sshkey", "certificate"].indexOf($scope.form.type) >= 0) {
            $scope.hidePin = true;
        } else {
            $scope.hidePin = false;
        }
    };

    // A watch function to change the form data in case another user is selected
    $scope.$watch(function(scope) {return scope.newUser.email;},
        function(newValue, oldValue){
            $scope.form.email = newValue;
        });
    $scope.$watch(function(scope) {return scope.newUser.mobile;},
        function(newValue, oldValue){
            $scope.form.phone = newValue;
        });

    // Get the realms and fill the realm dropdown box
    if (AuthFactory.getRole() == 'admin') {
        ConfigFactory.getRealms(function (data) {
            $scope.realms = data.result.value;
            angular.forEach($scope.realms, function (realm, realmname) {
                if (realm.default && !$stateParams.realmname) {
                    // Set the default realm
                    $scope.newUser = {user: "", realm: realmname};
                    console.log("tokenEnrollController");
                    console.log($scope.newUser);
                }
            });
        });
        // init the user, if token.enroll was called from the user.details
        if ($stateParams.realmname) {
            $scope.newUser.realm = $stateParams.realmname;
        }
        if ($stateParams.username) {
            $scope.newUser.user = $stateParams.username;
            // preset the mobile and email for SMS or EMAIL token
            UserFactory.getUsers({realm: $scope.newUser.realm,
                username: $scope.newUser.user},
                function(data) {
                    userObject = data.result.value[0];
                    $scope.form.email = userObject.email;
                    $scope.form.phone = userObject.mobile;
            });
        }
    } else if (AuthFactory.getRole() == 'user') {
        // init the user, if token.enroll was called as a normal user
        $scope.newUser.user = AuthFactory.getUser().username;
        $scope.newUser.realm = AuthFactory.getUser().realm;
    }

    // Read the the tokentypes from the server
    TokenFactory.getEnrollTokens(function(data){
        console.log(data);
        $scope.formInit["tokenTypes"] = data.result.value;
        // set the default tokentype
        if (!$scope.formInit.tokenTypes.hasOwnProperty("hotp")) {
            // if HOTP does not exist, we set another default type
            for (var tkey in $scope.formInit.tokenTypes) {
                // set the first key to be the default tokentype
                $scope.form.type = tkey;
                break;
            }
        }
    });

    $scope.CAConnectors = [];
    $scope.radioCSR = 'csrgenerate';

    // default callback
    $scope.callback = function (data) {
        $scope.U2FToken = {};
        $scope.enrolledToken = data.detail;
        $scope.click_wait=false;
        if ($scope.enrolledToken.certificate) {
            var blob = new Blob([ $scope.enrolledToken.certificate ],
                { type : 'text/plain' });
            $scope.certificateBlob = (window.URL || window.webkitURL).createObjectURL( blob );
        }
        if ($scope.enrolledToken.u2fRegisterRequest) {
            // This is the first step of U2F registering
            // save serial
            $scope.serial = data.detail.serial;
            // We need to send the 2nd stage of the U2F enroll
            $scope.register_u2f($scope.enrolledToken.u2fRegisterRequest);
            $scope.click_wait=true;
        }
    };

    $scope.enrollToken = function () {
        console.log($scope.newUser.user);
        console.log($scope.newUser.realm);
        console.log($scope.newUser.pin);
        $scope.newUser.user = fixUser($scope.newUser.user);
        TokenFactory.enroll($scope.newUser,
            $scope.form, $scope.callback);
    };

    // Special Token functions
    $scope.sshkeyChanged = function () {
        var keyArr = $scope.form.sshkey.split(" ");
        $scope.form.description = keyArr.slice(2).join(" ");
    };

    $scope.yubikeyGetLen = function () {
        if ($scope.tempData.yubikeyTest.length >= 32) {
            $scope.form.otplen = $scope.tempData.yubikeyTest.length;
            if ($scope.tempData.yubikeyTest.length > 32) {
                $scope.tempData.yubikeyUid = true;
                $scope.tempData.yubikeyUidLen = $scope.tempData.yubikeyTest.length - 32;
            }
        }
    };

    // U2F
    $scope.register_u2f = function (registerRequest) {
        U2fFactory.register_request(registerRequest, function (params) {
            params.serial = $scope.serial;
            TokenFactory.enroll($scope.newUser,
                params, function (response) {
                    $scope.click_wait = false;
                    $scope.U2FToken.subject = response.detail.u2fRegisterResponse.subject;
                    $scope.U2FToken.vendor = $scope.U2FToken.subject.split(" ")[0];
                    console.log($scope.U2FToken);
                });
        });
    };

    // get the list of configured CA connectors
    $scope.getCAConnectors = function () {
        ConfigFactory.getCAConnectors(function (data){
            var CAConnectors = data.result.value;
            angular.forEach(CAConnectors, function(value, key){
                $scope.CAConnectors.push(value.connectorname);
                $scope.form.ca = value.connectorname;
            });
            console.log($scope.CAConnectors);
        });
    };
    $scope.getCAConnectors();

    // If the user is admin, he can read the config.
    if ($scope.loggedInUser.role == "admin") {
        ConfigFactory.loadSystemConfig(function (data) {
            /* Default config values like
                radius.server, radius.secret...
             are stored in systemDefault and $scope.form
             */
            var systemDefault = data.result.value;
            console.log("SystemDefault");
            console.log(systemDefault);
            // TODO: The entries should be handled automatically.
            var entries = ["radius.server", "radius.secret", "remote.server",
                "totp.hashlib", "hotp.hashlib", "email.mailserver",
                "email.mailfrom", "sms.provider", "yubico.id", "tiqr.regServer"];
            entries.forEach(function(entry) {
                if (!$scope.form[entry]) {
                    $scope.form[entry] = systemDefault[entry];
                }
            });
            console.log($scope.form);
        });
    }

    // open the window to generate the key pair
    $scope.openCertificateWindow = function () {
        var params = {authtoken: AuthFactory.getAuthToken(),
                      ca: $scope.form.ca};
        var tabWindowId = window.open('about:blank', '_blank');
        $http.post(instanceUrl + '/certificate', params).then(
            function (response) {
                console.log(response);
                tabWindowId.document.write(response.data);
                //tabWindowId.location.href = response.headers('Location');
        });
    };

    // ===========================================================
    // ===============  Date stuff ===============================
    // ===========================================================

    $scope.openDate = function($event) {
        $event.stopPropagation();
        return true;
    };

    $scope.today = new Date();
    $scope.dateOptions = {
        formatYear: 'yy',
        startingDay: 1
    };

});

myApp.controller("tokenImportController", function ($scope, $upload,
                                                    AuthFactory,
                                                    ConfigFactory, inform) {
    $scope.formInit = {
        fileTypes: ["OATH CSV", "Yubikey CSV", "pskc"]
    };
    // These are values that are also sent to the backend!
    $scope.form = {
        type: "OATH CSV",
        realm: ""
    };

    // get Realms
    ConfigFactory.getRealms(function (data) {
        $scope.realms = data.result.value;
        // Preset the default realm
        angular.forEach($scope.realms, function (realm, realmname) {
                if (realm.default) {
                    $scope.form.realm = realmname;
                }
            });
    });

    $scope.upload = function (files) {
        if (files && files.length) {
            for (var i = 0; i < files.length; i++) {
                var file = files[i];
                $upload.upload({
                    url: 'token/load/filename',
                    headers: {'PI-Authorization': AuthFactory.getAuthToken()},
                    fields: {type: $scope.form.type,
                            tokenrealms: $scope.form.realm},
                    file: file
                }).progress(function (evt) {
                    $scope.progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
                }).success(function (data, status, headers, config) {
                    $scope.uploadedFile = config.file.name;
                    $scope.uploadedTokens = data.result.value;
                }).error(function (error) {
                    if (error.result.error.code === -401) {
                        $state.go('login');
                    } else {
                        inform.add(error.result.error.message,
                                {type: "danger", ttl: 10000});
                    }
                });
            }
        }
    };
});
<commit_msg>Fix the import of token files...<commit_after>/**
 * http://www.privacyidea.org
 * (c) cornelius kölbel, cornelius@privacyidea.org
 *
 * 2015-01-11 Cornelius Kölbel, <cornelius@privacyidea.org>
 *
 * This code is free software; you can redistribute it and/or
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or any later version.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
myApp.controller("tokenController", function (TokenFactory, ConfigFactory,
                                              $scope, $location, AuthFactory,
                                              $rootScope, gettext, hotkeys) {
    $scope.params = {page: 1, sortdir: "asc"};
    $scope.reverse = false;
    $scope.loggedInUser = AuthFactory.getUser();
    $scope.selectedToken = {serial: null};
    // go to the list view by default
    if ($location.path() == "/token") {
        $location.path("/token/list");
    }

    // Change the pagination
    $scope.pageChanged = function () {
        console.log('Page changed to: ' + $scope.params.page);
        $scope.getTokens();
    };

    // This function fills $scope.tokendata
    $scope.getTokens = function () {
        $scope.params.serial = "*" + ($scope.serialFilter || "") + "*";
        $scope.params.type = "*" + ($scope.typeFilter || "") + "*";
        $scope.params.description = "*" + ($scope.descriptionFilter || "") + "*";
        $scope.params.userid = "*" + ($scope.userIdFilter || "") + "*";
        $scope.params.resolver = "*" + ($scope.resolverFilter || "") + "*";
        $scope.params.pagesize = $scope.token_page_size;
        if ($scope.reverse) {
            $scope.params.sortdir = "desc";
        } else {
            $scope.params.sortdir = "asc";
        }
        TokenFactory.getTokens(function (data) {
            $scope.tokendata = data.result.value;
            console.log($scope.tokendata);
        }, $scope.params);
    };

    $scope.getTokens();

    /*
     * Functions to check and to create a default realm. At the moment this is
     * in the tokenview, as the token view is the first view. This could be
     * changed to be located anywhere else.
     */
    if ($scope.loggedInUser.role == "admin") {
        ConfigFactory.getRealms(function (data) {
            // Check if there is a realm defined, or if we should display the
            // Auto Create Dialog
            number_of_realms = Object.keys(data.result.value).length;
            if (number_of_realms == 0) {
                $('#dialogAutoCreateRealm').modal();
            }
        });
    }

});

myApp.controller("tokenAssignController", function ($scope, TokenFactory,
                                                    $stateParams, AuthFactory,
                                                    UserFactory, $state) {
    $scope.assignToken = function () {
        TokenFactory.assign({
            serial: fixSerial($scope.newToken.serial),
            pin: $scope.newToken.pin
        }, function () {
            $state.go('token.list');
        });
    };
});

myApp.controller("tokenEnrollController", function ($scope, TokenFactory,
                                                    $stateParams, AuthFactory,
                                                    UserFactory, $state,
                                                    ConfigFactory, instanceUrl,
                                                    $http, hotkeys, gettext,
                                                    inform, U2fFactory) {

    hotkeys.bindTo($scope).add({
        combo: 'alt+e',
        description: gettext('Enroll a new token'),
        callback: function (event, hotkey) {
            event.preventDefault();
            $state.go('token.enroll');
            $scope.enrolledToken = null;
        }
    });
    hotkeys.bindTo($scope).add({
        combo: 'alt+r',
        description: gettext('Roll the token'),
        callback: function () {
            $scope.enrollToken();
        }
    });

    $scope.loggedInUser = AuthFactory.getUser();
    $scope.newUser = {};
    $scope.tempData = {};
    $scope.instanceUrl = instanceUrl;
    $scope.click_wait = true;
    $scope.U2FToken = {};
    // System default values for enrollment
    $scope.systemDefault = {};
    // These are values that are also sent to the backend!
    $scope.form = {
        timeStep: 30,
        otplen: 6,
        genkey: true,
        type: $scope.default_tokentype,
        hashlib: "sha1",
        'radius.system_settings': true
    };

    $scope.formInit = {
        tokenTypes: {"hotp": gettext("HOTP: event based One Time Passwords"),
            "totp": gettext("TOTP: time based One Time Passwords"),
            "spass": gettext("SPass: Simple Pass token. Static passwords"),
            "motp": gettext("mOTP: classical mobile One Time Passwords"),
            "sshkey": gettext("SSH Public Key: The public SSH key"),
            "yubikey": gettext("Yubikey AES mode: One Time Passwords with" +
                " Yubikey"),
            "remote": gettext("Remote Token: Forward authentication request" +
                " to another server"),
            "yubico": gettext("Yubikey Cloud mode: Forward authentication" +
                " request to YubiCloud"),
            "radius": gettext("RADIUS: Forward authentication request to a" +
                " RADIUS server"),
            "email": gettext("EMail: Send a One Time Password to the users email" +
                " address."),
            "sms": gettext("SMS: Send a One Time Password to the users" +
                " mobile phone."),
            "certificate": gettext("Certificate: Enroll an x509 Certificate" +
                " Token."),
            "4eyes": gettext("Four Eyes: Two or more users are required to" +
                " log in."),
            "tiqr": gettext("TiQR: Authenticate with Smartphone by scanning" +
                " a QR code."),
            "u2f": gettext("U2F: Universal 2nd Factor hardware token.")},
        timesteps: [30, 60],
        otplens: [6, 8],
        hashlibs: ["sha1", "sha256", "sha512"]
    };

    // These token need to PIN
    // TODO: THis is also contained in the tokentype class!
    $scope.changeTokenType = function() {
        console.log("Token Type Changed.");
        if (["sshkey", "certificate"].indexOf($scope.form.type) >= 0) {
            $scope.hidePin = true;
        } else {
            $scope.hidePin = false;
        }
    };

    // A watch function to change the form data in case another user is selected
    $scope.$watch(function(scope) {return scope.newUser.email;},
        function(newValue, oldValue){
            $scope.form.email = newValue;
        });
    $scope.$watch(function(scope) {return scope.newUser.mobile;},
        function(newValue, oldValue){
            $scope.form.phone = newValue;
        });

    // Get the realms and fill the realm dropdown box
    if (AuthFactory.getRole() == 'admin') {
        ConfigFactory.getRealms(function (data) {
            $scope.realms = data.result.value;
            angular.forEach($scope.realms, function (realm, realmname) {
                if (realm.default && !$stateParams.realmname) {
                    // Set the default realm
                    $scope.newUser = {user: "", realm: realmname};
                    console.log("tokenEnrollController");
                    console.log($scope.newUser);
                }
            });
        });
        // init the user, if token.enroll was called from the user.details
        if ($stateParams.realmname) {
            $scope.newUser.realm = $stateParams.realmname;
        }
        if ($stateParams.username) {
            $scope.newUser.user = $stateParams.username;
            // preset the mobile and email for SMS or EMAIL token
            UserFactory.getUsers({realm: $scope.newUser.realm,
                username: $scope.newUser.user},
                function(data) {
                    userObject = data.result.value[0];
                    $scope.form.email = userObject.email;
                    $scope.form.phone = userObject.mobile;
            });
        }
    } else if (AuthFactory.getRole() == 'user') {
        // init the user, if token.enroll was called as a normal user
        $scope.newUser.user = AuthFactory.getUser().username;
        $scope.newUser.realm = AuthFactory.getUser().realm;
    }

    // Read the the tokentypes from the server
    TokenFactory.getEnrollTokens(function(data){
        console.log(data);
        $scope.formInit["tokenTypes"] = data.result.value;
        // set the default tokentype
        if (!$scope.formInit.tokenTypes.hasOwnProperty("hotp")) {
            // if HOTP does not exist, we set another default type
            for (var tkey in $scope.formInit.tokenTypes) {
                // set the first key to be the default tokentype
                $scope.form.type = tkey;
                break;
            }
        }
    });

    $scope.CAConnectors = [];
    $scope.radioCSR = 'csrgenerate';

    // default callback
    $scope.callback = function (data) {
        $scope.U2FToken = {};
        $scope.enrolledToken = data.detail;
        $scope.click_wait=false;
        if ($scope.enrolledToken.certificate) {
            var blob = new Blob([ $scope.enrolledToken.certificate ],
                { type : 'text/plain' });
            $scope.certificateBlob = (window.URL || window.webkitURL).createObjectURL( blob );
        }
        if ($scope.enrolledToken.u2fRegisterRequest) {
            // This is the first step of U2F registering
            // save serial
            $scope.serial = data.detail.serial;
            // We need to send the 2nd stage of the U2F enroll
            $scope.register_u2f($scope.enrolledToken.u2fRegisterRequest);
            $scope.click_wait=true;
        }
    };

    $scope.enrollToken = function () {
        console.log($scope.newUser.user);
        console.log($scope.newUser.realm);
        console.log($scope.newUser.pin);
        $scope.newUser.user = fixUser($scope.newUser.user);
        TokenFactory.enroll($scope.newUser,
            $scope.form, $scope.callback);
    };

    // Special Token functions
    $scope.sshkeyChanged = function () {
        var keyArr = $scope.form.sshkey.split(" ");
        $scope.form.description = keyArr.slice(2).join(" ");
    };

    $scope.yubikeyGetLen = function () {
        if ($scope.tempData.yubikeyTest.length >= 32) {
            $scope.form.otplen = $scope.tempData.yubikeyTest.length;
            if ($scope.tempData.yubikeyTest.length > 32) {
                $scope.tempData.yubikeyUid = true;
                $scope.tempData.yubikeyUidLen = $scope.tempData.yubikeyTest.length - 32;
            }
        }
    };

    // U2F
    $scope.register_u2f = function (registerRequest) {
        U2fFactory.register_request(registerRequest, function (params) {
            params.serial = $scope.serial;
            TokenFactory.enroll($scope.newUser,
                params, function (response) {
                    $scope.click_wait = false;
                    $scope.U2FToken.subject = response.detail.u2fRegisterResponse.subject;
                    $scope.U2FToken.vendor = $scope.U2FToken.subject.split(" ")[0];
                    console.log($scope.U2FToken);
                });
        });
    };

    // get the list of configured CA connectors
    $scope.getCAConnectors = function () {
        ConfigFactory.getCAConnectors(function (data){
            var CAConnectors = data.result.value;
            angular.forEach(CAConnectors, function(value, key){
                $scope.CAConnectors.push(value.connectorname);
                $scope.form.ca = value.connectorname;
            });
            console.log($scope.CAConnectors);
        });
    };
    $scope.getCAConnectors();

    // If the user is admin, he can read the config.
    if ($scope.loggedInUser.role == "admin") {
        ConfigFactory.loadSystemConfig(function (data) {
            /* Default config values like
                radius.server, radius.secret...
             are stored in systemDefault and $scope.form
             */
            var systemDefault = data.result.value;
            console.log("SystemDefault");
            console.log(systemDefault);
            // TODO: The entries should be handled automatically.
            var entries = ["radius.server", "radius.secret", "remote.server",
                "totp.hashlib", "hotp.hashlib", "email.mailserver",
                "email.mailfrom", "sms.provider", "yubico.id", "tiqr.regServer"];
            entries.forEach(function(entry) {
                if (!$scope.form[entry]) {
                    $scope.form[entry] = systemDefault[entry];
                }
            });
            console.log($scope.form);
        });
    }

    // open the window to generate the key pair
    $scope.openCertificateWindow = function () {
        var params = {authtoken: AuthFactory.getAuthToken(),
                      ca: $scope.form.ca};
        var tabWindowId = window.open('about:blank', '_blank');
        $http.post(instanceUrl + '/certificate', params).then(
            function (response) {
                console.log(response);
                tabWindowId.document.write(response.data);
                //tabWindowId.location.href = response.headers('Location');
        });
    };

    // ===========================================================
    // ===============  Date stuff ===============================
    // ===========================================================

    $scope.openDate = function($event) {
        $event.stopPropagation();
        return true;
    };

    $scope.today = new Date();
    $scope.dateOptions = {
        formatYear: 'yy',
        startingDay: 1
    };

});

myApp.controller("tokenImportController", function ($scope, $upload,
                                                    AuthFactory, tokenUrl,
                                                    ConfigFactory, inform) {
    $scope.formInit = {
        fileTypes: ["OATH CSV", "Yubikey CSV", "pskc"]
    };
    // These are values that are also sent to the backend!
    $scope.form = {
        type: "OATH CSV",
        realm: ""
    };

    // get Realms
    ConfigFactory.getRealms(function (data) {
        $scope.realms = data.result.value;
        // Preset the default realm
        angular.forEach($scope.realms, function (realm, realmname) {
                if (realm.default) {
                    $scope.form.realm = realmname;
                }
            });
    });

    $scope.upload = function (files) {
        if (files && files.length) {
            for (var i = 0; i < files.length; i++) {
                var file = files[i];
                $upload.upload({
                    url: tokenUrl + '/load/filename',
                    headers: {'PI-Authorization': AuthFactory.getAuthToken()},
                    fields: {type: $scope.form.type,
                            tokenrealms: $scope.form.realm},
                    file: file
                }).progress(function (evt) {
                    $scope.progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
                }).success(function (data, status, headers, config) {
                    $scope.uploadedFile = config.file.name;
                    $scope.uploadedTokens = data.result.value;
                }).error(function (error) {
                    if (error.result.error.code === -401) {
                        $state.go('login');
                    } else {
                        inform.add(error.result.error.message,
                                {type: "danger", ttl: 10000});
                    }
                });
            }
        }
    };
});
<|endoftext|>


<commit_before>/**
 * Utilities; usefull scripts
 *
 * @author	Tijs Verkoyen <tijs@netlash.com>
 */
var utils =
{
	// datamembers
	debug: false,
	eoo: true
}

/**
 * Functions related to arrays
 *
 * @author	Tijs Verkoyen <tijs@netlash.com>
 */
utils.array =
{
	/**
	 * Is the given value present in the array
	 *
	 * @return	bool
	 */
	inArray: function(needle, array)
	{
		// loop values
		for(var i in array)
		{
			if(array[i] == needle) return true;
		}

		// fallback
		return false;
	},

	// end
	eoo: true
},

/**
 * Function related to cookies
 *
 * @author	Tijs Verkoyen <tijs@netlash.com>
 */
utils.cookies =
{
	/**
	 * Are cookies enabled?
	 *
	 * @return	bool
	 */
	isEnabled: function()
	{
		// try to grab the property
		var cookiesEnabled = (navigator.cookieEnabled) ? true : false;

		// unknown property?
		if(typeof navigator.cookieEanbled == 'undefined' && !cookiesEnabled)
		{
			// try to set a cookie
			document.cookie = 'testcookie';
			cookiesEnabled = ($.inArray('testcookie', document.cookie) != -1);
		}

		// return
		return cookiesEnabled;
	},

	/**
	 * Read a cookie
	 *
	 * @return	mixed
	 */
	readCookie: function(name)
	{
		// get cookies
		var cookies = document.cookie.split(';');
		name = name + '=';

		for(var i = 0; i < cookies.length; i++)
		{
			var cookie = cookies[i];
			while(cookie.charAt(0) == ' ') cookie = cookie.substring(1, cookie.length);
			if(cookie.indexOf(name) == 0) return cookie.substring(name.lenght, cookie.length);
		}

		// fallback
		return null;
	},

	// end
	eoo: true
},

/**
 * Functions related to forms
 *
 * @author	Tijs Verkoyen <tijs@netlash.com>
 */
utils.form =
{
	/**
	 * Is a checkbox checked?
	 *
	 * @return	bool
	 * @param	object element
	 */
	isChecked: function(element)
	{
		return ($('input[name="' + element.attr('name') + '"]:checked').length >= 1);
	},

	/**
	 * Is the value inside the element a valid emailaddress
	 *
	 * @return	bool
	 * @param	object element
	 */
	isEmail: function(element)
	{
		var regexp = /^[a-z0-9!#\$%&'*+-\/=?^_`{|}\.~]+@([a-z0-9]+([\-]+[a-z0-9]+)*\.)+[a-z]{2,7}$/i;
		return regexp.test(element.val());
	},

	/**
	 * Is the element filled
	 *
	 * @return	bool
	 * @param	object element
	 */
	isFilled: function(element)
	{
		return (utils.string.trim(element.val()) != '');
	},

	/**
	 * Is the value inside the element a valid number
	 *
	 * @return	bool
	 * @param	object element
	 */
	isNumber: function(element)
	{
		return (!isNaN(element.val()) && element.val() != '');
	},

	/**
	 * Is the value inside the element a valid URL
	 *
	 * @return	bool
	 * @param	object element
	 */
	isURL: function(element)
	{
		var regexp = /^((http|ftp|https):\/{2})?(([0-9a-zA-Z_-]+\.)+[0-9a-zA-Z]+)((:[0-9]+)?)((\/([~0-9a-zA-Z\#%@\.\/_-]+)?(\?[0-9a-zA-Z%@\/&=_-]+)?)?)$/i;
		return regexp.test(element.val());
	},

	// end
	eoo: true
},

/**
 * Functions related to strings
 *
 * @author	Tijs Verkoyen <tijs@sumocoders.be>
 * @author	Dieter Vanden Eynde <dieter@netlash.com>
 * @author	Matthias Mullie <matthias@mullie.eu>
 */
utils.string =
{
	// data member
	div: false,

	/**
	 * Fix a HTML5-chunk, so IE can render it
	 *
	 * @return	string
	 * @param	string html
	 */
	html5: function(html)
	{
		var html5 = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' ');

		// create div if needed
		if(utils.string.div === false)
		{
			utils.string.div = document.createElement('div');

			utils.string.div.innerHTML = '';

			if(utils.string.div.childNodes.length !== 1)
			{
				var fragment = document.createDocumentFragment();
				var i = html5.length;
				while(i--) fragment.createElement(html5[i]);

				fragment.appendChild(utils.string.div);
			}
		}

		html = html.replace(/^\s\s*/, '').replace(/\s\s*$/, '')
					.replace(/


<commit_before>import { UIDatabase } from '..';
import { createRecord } from './createRecord';

/**
 * Create a new indicator value.
 * @param {IndicatorAttribute} row
 * @param {IndicatorAttribute} column
 * @param {Period} period
 * @return {IndicatorValue}
 */
const createIndicatorValue = (row, column, period) => {
  let record;
  UIDatabase.write(() => {
    record = createRecord(UIDatabase, 'IndicatorValue', row, column, period);
  });
  return record;
};

/**
 * Update indicator value object with new value.
 * @param {IndicatorValue} indicatorValue
 * @param {string} value
 */
const updateIndicatorValue = (indicatorValue, value) => {
  UIDatabase.write(() => {
    UIDatabase.update('IndicatorValue', { ...indicatorValue, value });
  });
};

/**
 * Get value for indicator row, column attribute pair.
 *
 * @param {IndicatorAttribute} row
 * @param {IndicatorAttribute} column
 * @param {Period} period
 * @return {IndicatorValue}
 */
const getIndicatorRowColumnValue = (row, column, period) => {
  const rowColumnValues = UIDatabase.objects('IndicatorValue')
    .filtered('row.id = $0 AND column.id = $1 AND period.id = $2', row.id, column.id, period.id)
    .slice();
  const [rowColumnValue] = rowColumnValues;
  return rowColumnValue ?? createIndicatorValue(row, column, period);
};

/**
 * Get all indicator values for a given period.
 * @param {Period} period
 */
const getIndicatorValuesByPeriod = period => {
  const indicatorValues = UIDatabase.objects('IndicatorValue').filtered(
    'period.id == $0',
    period.id
  );
  return indicatorValues;
};

/**
 * Find indicator row by id.
 * @param {Realm.Results.<IndicatorAttribute} indicatorRows
 * @param {string} rowId
 * @return {IndicatorAttribute}
 */
const getIndicatorRow = (indicatorRows, rowId) => {
  const [indicatorRow] = indicatorRows.filtered('id == $0', rowId).slice();
  return indicatorRow;
};

/**
 * Find indicator column by code.
 * @param {Realm.Results.<IndicatorAttribute>} indicatorColumns
 * @param {string} columnCode
 * @return {IndicatorAttribute}
 */
const getIndicatorColumn = (indicatorColumns, columnCode) => {
  const [indicatorColumn] = indicatorColumns.filtered('code == $0', columnCode).slice();
  return indicatorColumn;
};

/**
 * Map result set of indicator values to set of parent indicators.
 * @param {Realm.Results.<IndicatorValue>} indicatorValues
 */
const getIndicatorsByValues = indicatorValues => {
  const indicatorIds = indicatorValues.reduce(
    (acc, { indicator }) =>
      acc.some(indicatorId => indicatorId === indicator.id) ? acc : [...acc, indicator.id],
    []
  );
  const indicators = UIDatabase.objects('ProgramIndicator').filtered(
    indicatorIds.map(indicatorId => `id == "${indicatorId}"`).join(' OR ')
  );
  return indicators;
};

export {
  createIndicatorValue,
  updateIndicatorValue,
  getIndicatorRowColumnValue,
  getIndicatorValuesByPeriod,
  getIndicatorRow,
  getIndicatorColumn,
  getIndicatorsByValues,
};
<commit_msg>Update updateIndicatorValue utility<commit_after>import { UIDatabase } from '..';
import { createRecord } from './createRecord';

/**
 * Create a new indicator value.
 * @param {IndicatorAttribute} row
 * @param {IndicatorAttribute} column
 * @param {Period} period
 * @return {IndicatorValue}
 */
const createIndicatorValue = (row, column, period) => {
  let record;
  UIDatabase.write(() => {
    record = createRecord(UIDatabase, 'IndicatorValue', row, column, period);
  });
  return record;
};

/**
 * Update indicator value object with new value.
 * @param {IndicatorValue} indicatorValue
 * @param {string} value
 */
const updateIndicatorValue = (indicatorValue, value) => {
  UIDatabase.write(() => {
    indicatorValue.value = value;
  });
};

/**
 * Get value for indicator row, column attribute pair.
 *
 * @param {IndicatorAttribute} row
 * @param {IndicatorAttribute} column
 * @param {Period} period
 * @return {IndicatorValue}
 */
const getIndicatorRowColumnValue = (row, column, period) => {
  const rowColumnValues = UIDatabase.objects('IndicatorValue')
    .filtered('row.id = $0 AND column.id = $1 AND period.id = $2', row.id, column.id, period.id)
    .slice();
  const [rowColumnValue] = rowColumnValues;
  return rowColumnValue ?? createIndicatorValue(row, column, period);
};

/**
 * Get all indicator values for a given period.
 * @param {Period} period
 */
const getIndicatorValuesByPeriod = period => {
  const indicatorValues = UIDatabase.objects('IndicatorValue').filtered(
    'period.id == $0',
    period.id
  );
  return indicatorValues;
};

/**
 * Find indicator row by id.
 * @param {Realm.Results.<IndicatorAttribute} indicatorRows
 * @param {string} rowId
 * @return {IndicatorAttribute}
 */
const getIndicatorRow = (indicatorRows, rowId) => {
  const [indicatorRow] = indicatorRows.filtered('id == $0', rowId).slice();
  return indicatorRow;
};

/**
 * Find indicator column by code.
 * @param {Realm.Results.<IndicatorAttribute>} indicatorColumns
 * @param {string} columnCode
 * @return {IndicatorAttribute}
 */
const getIndicatorColumn = (indicatorColumns, columnCode) => {
  const [indicatorColumn] = indicatorColumns.filtered('code == $0', columnCode).slice();
  return indicatorColumn;
};

/**
 * Map result set of indicator values to set of parent indicators.
 * @param {Realm.Results.<IndicatorValue>} indicatorValues
 */
const getIndicatorsByValues = indicatorValues => {
  const indicatorIds = indicatorValues.reduce(
    (acc, { indicator }) =>
      acc.some(indicatorId => indicatorId === indicator.id) ? acc : [...acc, indicator.id],
    []
  );
  const indicators = UIDatabase.objects('ProgramIndicator').filtered(
    indicatorIds.map(indicatorId => `id == "${indicatorId}"`).join(' OR ')
  );
  return indicators;
};

export {
  createIndicatorValue,
  updateIndicatorValue,
  getIndicatorRowColumnValue,
  getIndicatorValuesByPeriod,
  getIndicatorRow,
  getIndicatorColumn,
  getIndicatorsByValues,
};
<|endoftext|>