<commit_before>var skill_list;
var skill_list_inverted;
var skill_list_special_group;
var ok_to_trigger_advanced_profession_removal = false;
var advanced_acknowledged = false;
var skill_cost_adjusted = {};
var unpack_has_been_called = false;

function persistence_set_skill_cost_adjustment(name, value) {
  skill_cost_adjusted[skill_list[name]] = value;
  pack_state();
}

function persistence_clear_skill_cost_adjustment(name, _bypass_pack) {
  var bypass_pack = _bypass_pack == undefined ? false : true;
  var exist = skill_cost_adjusted[skill_list[name]];
  delete skill_cost_adjusted[skill_list[name]];

  if (exist) {
    pack_state();
  }
}

function persistence_clear_all_skill_cost_adjustment() {
  skill_cost_adjusted = {};
}

function apply_skill_cost_adjusted() {
  $.each(skill_cost_adjusted, function(code, value) {
    var skill_name = skill_list_inverted[code];
    $('li[skill-name="' + skill_name + '"]').find('.badge').html(value + '+');
  })

  update_xp_count('#planned');
  update_xp_count('#acquired');
}

function set_advanced_acknowledgement(val) {
  advanced_acknowledged = val;
  if (val) {
    $('.advanced-recoverable').show();
    $('.advanced-to-hide').hide();
    $('#filter-group').multiselect('deselect', 'hide-advanced', true);
  } else {
    $('.advanced-recoverable').hide();
    $('.advanced-to-hide').show();
    $('#filter-group').multiselect('select', 'hide-advanced', true);
  }

  if (unpack_has_been_called) {
    pack_state();
  }
}

function clear_cookies() {
  Cookies.remove('drpedia');
}

function pack_state() {
  //console.log('called from ' + pack_state.caller.toString())
  calculate_xp_sum();

  var pack = $('#hp-addition').text() + '|'
           + $('#mp-addition').text() + '|'
           + (selected_strain || '') + '|'
           + (selected_professions || new Array()).join(',') + '|';
  var es = extract_skills();

  pack += es.acquired.join(',') + '|' + es.planned.join(',') + '|';
  pack += advanced_acknowledged ? 1 : 0;

  var sca = new Array();
  $.each(skill_cost_adjusted, function(code, value) {
    if (!isNaN(value)) {
      sca.push(code + value);
    }
  })

  pack += '|' + sca.join(',');

  Cookies.set(has_profile ? current_profile : 'dummy', pack, { expires: 365 });
  console.log('Packing to ' + current_profile + ': ' + pack);
}

function unpack_state() {
  unpack_has_been_called = true;
  if (!has_profile) {
    if (Cookies.get('dummy') == undefined) {
      return;
    }

    current_profile = 'dummy';
  }

  var decrypt_psionic_skills = function(a) {
    var prefix = '';
    $.each(skill_groups, function(group_name, skills) {
      if (skills[a] != undefined) {
        switch (group_name) {
          case 'Psionic Skill - Basic': prefix = 'Psi I - '; break;
          case 'Psionic Skill - Intermediate': prefix = 'Psi II - '; break;
          case 'Psionic Skill - Advanced': prefix = 'Psi III - '; break;
        }

        return false;
      }
    });

    return prefix + a;
  }

  var decrypt_skills = function(a) {
    var output = new Array();

    $.each(a, function(i, x) {
      var lookup = skill_list_inverted[x];
      if (lookup != undefined) {
        output.push(lookup);
      }
    })
    return output;
  }

  var relocate = function(target_list_id, skill_list, _hint) {
    $.each(skill_list, function(i, x) {

      var target_object = $('li[skill-name="' + x + '"]');
      if (target_object.length == 0) {
        target_object = $('li[skill-name="' + decrypt_psionic_skills(x) + '"]');
      }

      if (target_object) {
        append_lexicographically(target_list_id, target_object, _hint);
      }
    })
  }

  var reset_all_skills = function(list_id) {
    var entries = new Array();

    $('#' + list_id + ' .list-group-item').each(function() {
      var entry = $(this).attr('skill-name');
      entries.push(entry);
    })

    relocate('#graphical-list', entries, 'bypass_check_adjusted_cost');
  }

  //var unpack = current_profile == 'dummy' ? '0|0||||' : Cookies.get(current_profile);
  var unpack = Cookies.get(current_profile);
  console.log('Unpack from ' + current_profile + ': ' + unpack);

  if (unpack != undefined) {
    defer_update_beyond_basic(function() {

      var p0 = unpack.split('|');
      var hp = parseInt(p0[0]) || 0;
      var mp = parseInt(p0[1]) || 0;
      var strain = p0[2];

      $('#strain-selector').multiselect('select', 'Select Strain').multiselect('refresh');
      reset_all_skills('acquired-list');
      reset_all_skills('planned-list');
      set_stat_build($('#hp-addition'), 'hp-total', hp, false);
      set_stat_build($('#mp-addition'), 'mp-total', mp, false);
      initialize_stats_controller('#hp-sub');
      initialize_stats_controller('#mp-sub');
      var professions = p0[3].split(',');
      var acquired_skills = decrypt_skills(p0[4].split(','));
      var planned_skills = decrypt_skills(p0[5].split(','));

      if (p0[6] != undefined) {
        advanced_acknowledged = p0[6] == '1' ? true : false;
      } else {
        advanced_acknowledged = false;
      }

      skill_cost_adjusted = {};
      if (p0[7] != undefined) {
        $.each(p0[7].split(','), function(i, x) {
          var coded = x[0] + x[1];
          var value = parseInt(x[2] + (x[3] || ''));
          skill_cost_adjusted[coded] = value;
        })
      }

      selected_strain = strain || 'Select Strain';

      selected_professions = new Array();
      selected_advanced_profession = undefined;
      $.each(professions, function(i, x) {
        if (advanced_profession_struct[x] == undefined) {
          selected_professions.push(x);
        } else {
          selected_advanced_profession = x;
          if (!advanced_acknowledged) {
            $('#advanced-existance-warning').modal('show');
          }
        }
      })

      //selected_professions = professions;
      //$('#strain-selector').val(strain).multiselect('refresh');

      $('#strain-selector').multiselect('select', selected_strain);
      $('#profession-selector')
        .multiselect('deselectAll', false)
        .multiselect('updateButtonText');
      $.each(professions, function(i, x) {
        $('#profession-selector').multiselect('select', x, true);
      });

      //$('#profession-selector').val(professions).multiselect('refresh');

      apply_strain_restrictions();
      restrict_profession_selector();
      update_profession_cost();
      update_strain_specs();
      update_strain_stats();
      recalculate();
      replan();
      //update_availability();

      relocate('#planned-list', planned_skills);
      relocate('#acquired-list', acquired_skills);
      apply_skill_cost_adjusted();
      //pack_state();
      update_xp_count('#planned');
      update_xp_count('#acquired');
      generate_constraints();
      update_selected_skills(0);
    })

    check_advanced_profession_constraints();
    pack_state();
  } else {
    generate_constraints();
  }

  post_process_advanced_professions();
}

function extract_skills() {
  var psion_regex = /^Psi [I]+ - /;

  var extract_skills_from_region = function(id) {
    var s = new Array();
    $(id + ' .skill-draggable').each(function() {
      if ($(this).hasClass('faded')) { return true; }
      var skill_name = $(this).attr('skill-name');

      if (skill_name.match(psion_regex)) {
        skill_name = skill_name.replace(psion_regex, '');
      }
      s.push(skill_list[skill_name]);
    })

    return s;
  }

  var acqs = extract_skills_from_region('#acquired');
  var plans = extract_skills_from_region('#planned');

  return {
    planned: plans,
    acquired: acqs
  }
}

function generate_inverted_skills() {
  return new Promise(
    function(resolve, reject) {
      skill_list_inverted = new Object();
      skill_list_special_group = new Object();

      $.each(skill_list, function(key, val) {
        skill_list_inverted[val] = key;
      })

      $.each(skill_groups, function(group_name, d) {
        $.each(d, function(skill_name, _junk) {
          skill_list_special_group[skill_name] = group_name;
        });
      });

      resolve();
    }
  )
}

$(function() {
  $.when(get_json_strain(),
         get_json_profession(),
         get_json_skill_cat(),
         get_json_strain_stats(),
         get_json_strain_specs(),
         get_json_profession_advanced(),
         get_json_profession_concentration()).done(function() {
    $.when(get_json_advanced_cat()).done(function() {
      $.when(get_json_skill_group()).done(function() {
        generate_skill_cat();
        $('#init-skill').show();

        $.when(get_json_strain_restriction()).done(function() {
          $.when(get_json_skill_list()).done(function() {
            $('#init-postprocess').show();

            resize_graphical();
            generate_professions_select_box();
            generate_inverted_skills().then(function() {
              build_advanced_profession().then(function() {
                load_first_available_profile();
                update_all_alternators();
                calculate_xp_sum();
                $('#init-completed').show();
                init_completed = true;

                ok_to_trigger_advanced_profession_removal = true;
                //update_beyond_basic();
                $('#loading [data-dismissible]').hide(500, function() {
                  $('#loading').hide(1000);
                });
              });
            })
          });
        })
      })
    })

  })
  // $.when(get_json_strain(),
  //        get_json_profession(),
  //        get_json_skill_cat(),
  //        get_json_strain_restriction()).done(function() {
  //
  // })

});<commit_msg>Attach set_advanced_acknowledgedment() on profile load<commit_after>var skill_list;
var skill_list_inverted;
var skill_list_special_group;
var ok_to_trigger_advanced_profession_removal = false;
var advanced_acknowledged = false;
var skill_cost_adjusted = {};
var unpack_has_been_called = false;

function persistence_set_skill_cost_adjustment(name, value) {
  skill_cost_adjusted[skill_list[name]] = value;
  pack_state();
}

function persistence_clear_skill_cost_adjustment(name, _bypass_pack) {
  var bypass_pack = _bypass_pack == undefined ? false : true;
  var exist = skill_cost_adjusted[skill_list[name]];
  delete skill_cost_adjusted[skill_list[name]];

  if (exist) {
    pack_state();
  }
}

function persistence_clear_all_skill_cost_adjustment() {
  skill_cost_adjusted = {};
}

function apply_skill_cost_adjusted() {
  $.each(skill_cost_adjusted, function(code, value) {
    var skill_name = skill_list_inverted[code];
    $('li[skill-name="' + skill_name + '"]').find('.badge').html(value + '+');
  })

  update_xp_count('#planned');
  update_xp_count('#acquired');
}

function set_advanced_acknowledgement(val) {
  advanced_acknowledged = val;
  if (val) {
    $('.advanced-recoverable').show();
    $('.advanced-to-hide').hide();
    $('#filter-group').multiselect('deselect', 'hide-advanced', true);
  } else {
    $('.advanced-recoverable').hide();
    $('.advanced-to-hide').show();
    $('#filter-group').multiselect('select', 'hide-advanced', true);
  }

  if (unpack_has_been_called) {
    pack_state();
  }
}

function clear_cookies() {
  Cookies.remove('drpedia');
}

function pack_state() {
  //console.log('called from ' + pack_state.caller.toString())
  calculate_xp_sum();

  var pack = $('#hp-addition').text() + '|'
           + $('#mp-addition').text() + '|'
           + (selected_strain || '') + '|'
           + (selected_professions || new Array()).join(',') + '|';
  var es = extract_skills();

  pack += es.acquired.join(',') + '|' + es.planned.join(',') + '|';
  pack += advanced_acknowledged ? 1 : 0;

  var sca = new Array();
  $.each(skill_cost_adjusted, function(code, value) {
    if (!isNaN(value)) {
      sca.push(code + value);
    }
  })

  pack += '|' + sca.join(',');

  Cookies.set(has_profile ? current_profile : 'dummy', pack, { expires: 365 });
  console.log('Packing to ' + current_profile + ': ' + pack);
}

function unpack_state() {
  unpack_has_been_called = true;
  if (!has_profile) {
    if (Cookies.get('dummy') == undefined) {
      return;
    }

    current_profile = 'dummy';
  }

  var decrypt_psionic_skills = function(a) {
    var prefix = '';
    $.each(skill_groups, function(group_name, skills) {
      if (skills[a] != undefined) {
        switch (group_name) {
          case 'Psionic Skill - Basic': prefix = 'Psi I - '; break;
          case 'Psionic Skill - Intermediate': prefix = 'Psi II - '; break;
          case 'Psionic Skill - Advanced': prefix = 'Psi III - '; break;
        }

        return false;
      }
    });

    return prefix + a;
  }

  var decrypt_skills = function(a) {
    var output = new Array();

    $.each(a, function(i, x) {
      var lookup = skill_list_inverted[x];
      if (lookup != undefined) {
        output.push(lookup);
      }
    })
    return output;
  }

  var relocate = function(target_list_id, skill_list, _hint) {
    $.each(skill_list, function(i, x) {

      var target_object = $('li[skill-name="' + x + '"]');
      if (target_object.length == 0) {
        target_object = $('li[skill-name="' + decrypt_psionic_skills(x) + '"]');
      }

      if (target_object) {
        append_lexicographically(target_list_id, target_object, _hint);
      }
    })
  }

  var reset_all_skills = function(list_id) {
    var entries = new Array();

    $('#' + list_id + ' .list-group-item').each(function() {
      var entry = $(this).attr('skill-name');
      entries.push(entry);
    })

    relocate('#graphical-list', entries, 'bypass_check_adjusted_cost');
  }

  //var unpack = current_profile == 'dummy' ? '0|0||||' : Cookies.get(current_profile);
  var unpack = Cookies.get(current_profile);
  console.log('Unpack from ' + current_profile + ': ' + unpack);

  if (unpack != undefined) {
    defer_update_beyond_basic(function() {

      var p0 = unpack.split('|');
      var hp = parseInt(p0[0]) || 0;
      var mp = parseInt(p0[1]) || 0;
      var strain = p0[2];

      $('#strain-selector').multiselect('select', 'Select Strain').multiselect('refresh');
      reset_all_skills('acquired-list');
      reset_all_skills('planned-list');
      set_stat_build($('#hp-addition'), 'hp-total', hp, false);
      set_stat_build($('#mp-addition'), 'mp-total', mp, false);
      initialize_stats_controller('#hp-sub');
      initialize_stats_controller('#mp-sub');
      var professions = p0[3].split(',');
      var acquired_skills = decrypt_skills(p0[4].split(','));
      var planned_skills = decrypt_skills(p0[5].split(','));

      if (p0[6] != undefined) {
        advanced_acknowledged = p0[6] == '1' ? true : false;
      } else {
        advanced_acknowledged = false;
      }
      set_advanced_acknowledgement(advanced_acknowledged);

      skill_cost_adjusted = {};
      if (p0[7] != undefined) {
        $.each(p0[7].split(','), function(i, x) {
          var coded = x[0] + x[1];
          var value = parseInt(x[2] + (x[3] || ''));
          skill_cost_adjusted[coded] = value;
        })
      }

      selected_strain = strain || 'Select Strain';

      selected_professions = new Array();
      selected_advanced_profession = undefined;
      $.each(professions, function(i, x) {
        if (advanced_profession_struct[x] == undefined) {
          selected_professions.push(x);
        } else {
          selected_advanced_profession = x;
          if (!advanced_acknowledged) {
            $('#advanced-existance-warning').modal('show');
          }
        }
      })

      //selected_professions = professions;
      //$('#strain-selector').val(strain).multiselect('refresh');

      $('#strain-selector').multiselect('select', selected_strain);
      $('#profession-selector')
        .multiselect('deselectAll', false)
        .multiselect('updateButtonText');
      $.each(professions, function(i, x) {
        $('#profession-selector').multiselect('select', x, true);
      });

      //$('#profession-selector').val(professions).multiselect('refresh');

      apply_strain_restrictions();
      restrict_profession_selector();
      update_profession_cost();
      update_strain_specs();
      update_strain_stats();
      recalculate();
      replan();
      //update_availability();

      relocate('#planned-list', planned_skills);
      relocate('#acquired-list', acquired_skills);
      apply_skill_cost_adjusted();
      //pack_state();
      update_xp_count('#planned');
      update_xp_count('#acquired');
      generate_constraints();
      update_selected_skills(0);
    })

    check_advanced_profession_constraints();
    pack_state();
  } else {
    generate_constraints();
  }

  post_process_advanced_professions();
}

function extract_skills() {
  var psion_regex = /^Psi [I]+ - /;

  var extract_skills_from_region = function(id) {
    var s = new Array();
    $(id + ' .skill-draggable').each(function() {
      if ($(this).hasClass('faded')) { return true; }
      var skill_name = $(this).attr('skill-name');

      if (skill_name.match(psion_regex)) {
        skill_name = skill_name.replace(psion_regex, '');
      }
      s.push(skill_list[skill_name]);
    })

    return s;
  }

  var acqs = extract_skills_from_region('#acquired');
  var plans = extract_skills_from_region('#planned');

  return {
    planned: plans,
    acquired: acqs
  }
}

function generate_inverted_skills() {
  return new Promise(
    function(resolve, reject) {
      skill_list_inverted = new Object();
      skill_list_special_group = new Object();

      $.each(skill_list, function(key, val) {
        skill_list_inverted[val] = key;
      })

      $.each(skill_groups, function(group_name, d) {
        $.each(d, function(skill_name, _junk) {
          skill_list_special_group[skill_name] = group_name;
        });
      });

      resolve();
    }
  )
}

$(function() {
  $.when(get_json_strain(),
         get_json_profession(),
         get_json_skill_cat(),
         get_json_strain_stats(),
         get_json_strain_specs(),
         get_json_profession_advanced(),
         get_json_profession_concentration()).done(function() {
    $.when(get_json_advanced_cat()).done(function() {
      $.when(get_json_skill_group()).done(function() {
        generate_skill_cat();
        $('#init-skill').show();

        $.when(get_json_strain_restriction()).done(function() {
          $.when(get_json_skill_list()).done(function() {
            $('#init-postprocess').show();

            resize_graphical();
            generate_professions_select_box();
            generate_inverted_skills().then(function() {
              build_advanced_profession().then(function() {
                load_first_available_profile();
                update_all_alternators();
                calculate_xp_sum();
                $('#init-completed').show();
                init_completed = true;

                ok_to_trigger_advanced_profession_removal = true;
                //update_beyond_basic();
                $('#loading [data-dismissible]').hide(500, function() {
                  $('#loading').hide(1000);
                });
              });
            })
          });
        })
      })
    })

  })
  // $.when(get_json_strain(),
  //        get_json_profession(),
  //        get_json_skill_cat(),
  //        get_json_strain_restriction()).done(function() {
  //
  // })

});<|endoftext|>


<commit_before>(function (timespanSelection, graphFactory, utils, jobColors, dataSource) {
    var margin = {top: 10, right: 0, bottom: 30, left: 60},
        width = graphFactory.size - margin.left - margin.right,
        height = graphFactory.size - margin.top - margin.bottom;

    var x = d3.time.scale()
            .range([0, width]);

    var y = d3.scale.linear()
            .range([height, 0])
            .nice();

    var xAxis = d3.svg.axis()
            .scale(x)
            .orient("bottom");

    var yAxis = d3.svg.axis()
            .scale(y)
            .outerTickSize(0)
            .tickFormat(function (d) {
                return utils.formatTimeInMs(d * 1000);
            })
            .orient("left");

    var line = d3.svg.line()
            .interpolate("basis")
            .x(function(d) { return x(d.date); })
            .y(function(d) { return y(d.runtime); });

    var saneDayTicks = function (axis, scale) {
        var dayCount = (x.domain()[1] - x.domain()[0]) / (24 * 60 * 60 * 1000);
        if (dayCount < 10) {
            axis.ticks(d3.time.days, 1);
        }
        return axis;
    };

    var renderData = function (data, svg) {
        if (data.length < 2) {
            return;
        }

        var jobNames = d3.keys(data[0]).filter(function(key) { return key !== "date"; }),
            color = jobColors.colors(jobNames);

        data.forEach(function (d) {
            d.date = new Date(d.date);
        });

        var runtimes = jobNames.map(function (jobName) {
            return {
                jobName: jobName,
                values: data
                    .map(function (d) {
                        return {
                            date: d.date,
                            runtime: d[jobName] ? (new Number(d[jobName]) * 24 * 60 * 60) : undefined
                        };
                    }).filter(function (d) {
                        return d.runtime !== undefined;
                    })
            };
        });

        x.domain(d3.extent(data, function(d) { return d.date; }));
        y.domain([
            0,
            d3.max(runtimes, function(c) { return d3.max(c.values, function(v) { return v.runtime; }); })
        ]);

        var g = svg
                .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        g.append("g")
            .attr("class", "x axis")
            .attr("transform", "translate(0," + height + ")")
            .call(saneDayTicks(xAxis, x));

        g.append("g")
            .attr("class", "y axis")
            .call(yAxis)
            .append("text")
            .attr("transform", "rotate(-90)")
            .attr("y", 6)
            .attr("dy", ".71em")
            .style("text-anchor", "end")
            .text("Runtime");

        g.selectAll(".job")
            .data(runtimes)
            .enter()
            .append("g")
            .attr("class", "job")
            .attr('data-id', function (d) {
                return 'jobname-' + d.jobName;
            })
            .append("path")
            .attr("class", "line")
            .attr("d", function (d) {
                return line(d.values);
            })
            .style('stroke', function (d) {
                return color(d.jobName);
            })
            .append('title')
            .text(function (d) {
                return d.jobName;
            });
    };

    var timespanSelector = timespanSelection.create(timespanSelection.timespans.twoMonths),
        graph = graphFactory.create({
            id: 'jobRuntime',
            headline: "Job runtime",
            description: "
Is the pipeline getting faster? Has a job gotten considerably slower?
Color: job",
            csvUrl: "/pipelineruntime.csv",
            noDataReason: "provided start and end times for your builds over at least two consecutive days",
            widgets: [timespanSelector.widget]
        });

    timespanSelector.load(function (selectedTimespan) {
        var fromTimestamp = timespanSelection.startingFromTimestamp(selectedTimespan);

        graph.loading();

        dataSource.loadCSV('/pipelineruntime?from=' + fromTimestamp, function (data) {
            graph.loaded();

            renderData(data, graph.svg);
        });
    });
}(timespanSelection, graphFactory, utils, jobColors, dataSource));
<commit_msg>Correctly update job runtime on timespan selection<commit_after>(function (timespanSelection, graphFactory, utils, jobColors, dataSource) {
    var margin = {top: 10, right: 0, bottom: 30, left: 60},
        width = graphFactory.size - margin.left - margin.right,
        height = graphFactory.size - margin.top - margin.bottom;

    var x = d3.time.scale()
            .range([0, width]);

    var y = d3.scale.linear()
            .range([height, 0])
            .nice();

    var xAxis = d3.svg.axis()
            .scale(x)
            .orient("bottom");

    var yAxis = d3.svg.axis()
            .scale(y)
            .outerTickSize(0)
            .tickFormat(function (d) {
                return utils.formatTimeInMs(d * 1000);
            })
            .orient("left");

    var line = d3.svg.line()
            .interpolate("basis")
            .x(function(d) { return x(d.date); })
            .y(function(d) { return y(d.runtime); });

    var saneDayTicks = function (axis, scale) {
        var dayCount = (x.domain()[1] - x.domain()[0]) / (24 * 60 * 60 * 1000);
        if (dayCount < 10) {
            axis.ticks(d3.time.days, 1);
        } else {
            axis.ticks(10);
        }
        return axis;
    };

    var axesPane,
        getOrCreateAxesPane = function (svg) {
            if (axesPane === undefined) {
                 axesPane = svg
                        .append("g")
                        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

                axesPane.append("g")
                    .attr("class", "x axis")
                    .attr("transform", "translate(0," + height + ")");

                axesPane.append("g")
                    .attr("class", "y axis")
                    .append("text")
                    .attr("transform", "rotate(-90)")
                    .attr("y", 6)
                    .attr("dy", ".71em")
                    .style("text-anchor", "end")
                    .text("Runtime");
            }

            return axesPane;
        },
        removeAxesPane = function (svg) {
            svg.select('g').remove();
            axesPane = undefined;
        };

    var renderData = function (data, svg) {
        if (data.length < 2) {
            removeAxesPane(svg);
            return;
        }

        var jobNames = d3.keys(data[0]).filter(function(key) { return key !== "date"; }),
            color = jobColors.colors(jobNames);

        data.forEach(function (d) {
            d.date = new Date(d.date);
        });

        var runtimes = jobNames.map(function (jobName) {
            return {
                jobName: jobName,
                values: data
                    .map(function (d) {
                        return {
                            date: d.date,
                            runtime: d[jobName] ? (new Number(d[jobName]) * 24 * 60 * 60) : undefined
                        };
                    }).filter(function (d) {
                        return d.runtime !== undefined;
                    })
            };
        });

        x.domain(d3.extent(data, function(d) { return d.date; }));
        y.domain([
            0,
            d3.max(runtimes, function(c) { return d3.max(c.values, function(v) { return v.runtime; }); })
        ]);

        var g = getOrCreateAxesPane(svg);

        g.selectAll('.x.axis')
            .call(saneDayTicks(xAxis, x));

        g.selectAll('.y.axis')
            .call(yAxis);

        var selection = g.selectAll(".job")
            .data(runtimes,
                  function (d) {
                      return d.jobName;
                  });

        selection.exit()
            .remove();

        selection.enter()
            .append("g")
            .attr("class", "job")
            .attr('data-id', function (d) {
                return 'jobname-' + d.jobName;
            })
            .append("path")
            .attr("class", "line")
            .style('stroke', function (d) {
                return color(d.jobName);
            })
            .append('title')
            .text(function (d) {
                return d.jobName;
            });

        selection.select('path')
            .attr("d", function (d) {
                return line(d.values);
            });
    };

    var timespanSelector = timespanSelection.create(timespanSelection.timespans.twoMonths),
        graph = graphFactory.create({
            id: 'jobRuntime',
            headline: "Job runtime",
            description: "
Is the pipeline getting faster? Has a job gotten considerably slower?
Color: job",
            csvUrl: "/pipelineruntime.csv",
            noDataReason: "provided start and end times for your builds over at least two consecutive days",
            widgets: [timespanSelector.widget]
        });

    timespanSelector.load(function (selectedTimespan) {
        var fromTimestamp = timespanSelection.startingFromTimestamp(selectedTimespan);

        graph.loading();

        dataSource.loadCSV('/pipelineruntime?from=' + fromTimestamp, function (data) {
            graph.loaded();

            renderData(data, graph.svg);
        });
    });
}(timespanSelection, graphFactory, utils, jobColors, dataSource));
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, FormattedMessage } from 'react-intl';
import { get } from 'lodash';
import Markdown from 'react-markdown';

import withIntl from '../../../lib/withIntl';
import { getCurrencySymbol } from '../../../lib/utils';

import InputField from '../../../components/InputField';
import SignInForm from '../../../components/SignInForm';
import categories from '../../../constants/categories';
import Button from '../../../components/Button';
import { P } from '../../../components/Text';
import Container from '../../../components/Container';

class CreateExpenseForm extends React.Component {
  static propTypes = {
    collective: PropTypes.object,
    LoggedInUser: PropTypes.object,
    onSubmit: PropTypes.func,
  };

  constructor(props) {
    super(props);
    this.getOptions = this.getOptions.bind(this);
    this.renderForm = this.renderForm.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.onSubmit = this.onSubmit.bind(this);

    this.messages = defineMessages({
      paypal: {
        id: 'expense.payoutMethod.paypal',
        defaultMessage: 'PayPal ({paypalEmail, select, missing {missing} other {{paypalEmail}}})',
      },
      'newExpense.paypal.label': {
        id: 'newExpense.paypal.label',
        defaultMessage: 'Please provide address',
      },
      other: {
        id: 'expense.payoutMethod.manual',
        defaultMessage: 'Other (see instructions)',
      },
      donation: {
        id: 'expense.payoutMethod.donation',
        defaultMessage: 'Donation',
      },
      'error.descriptionMissing': {
        id: 'expense.error.descriptionMissing',
        defaultMessage: 'Missing description',
      },
      'error.amountMissing': {
        id: 'expense.error.amountMissing',
        defaultMessage: 'Amount must be greater than 0',
      },
      'error.privateMessageMissing': {
        id: 'expense.error.privateMessageMissing',
        defaultMessage: "Please provide instructions on how you'd like to be reimbursed as a private note",
      },
      'error.paypalEmailMissing': {
        id: 'expense.error.paypalEmailMissing',
        defaultMessage: 'Please provide your PayPal email address (or change the payout method)',
      },
      'error.attachmentMissing': {
        id: 'expense.error.attachmentMissing',
        defaultMessage: 'Missing attachment',
      },
    });

    this.categoriesOptions = categories(props.collective.slug).map(category => {
      return { [category]: category };
    });

    this.state = {
      modified: false,
      expense: {
        category: Object.keys(this.categoriesOptions[0])[0],
        payoutMethod: 'paypal',
        paypalEmail: (props.LoggedInUser && props.LoggedInUser.paypalEmail) || null,
      },
      isExpenseValid: false,
      loading: false,
    };
  }

  getOptions(arr, intlVars) {
    return arr.map(key => {
      const obj = {};
      obj[key] = this.props.intl.formatMessage(this.messages[key], intlVars);
      return obj;
    });
  }

  validate(expense) {
    const { intl } = this.props;
    if (!expense.description) {
      this.setState({
        error: intl.formatMessage(this.messages['error.descriptionMissing']),
      });
      return false;
    }
    if (!expense.amount > 0) {
      this.setState({
        error: intl.formatMessage(this.messages['error.amountMissing']),
      });
      return false;
    }
    if (!expense.attachment) {
      this.setState({
        error: intl.formatMessage(this.messages['error.attachmentMissing']),
      });
      return false;
    }
    if (expense.payoutMethod === 'other' && !expense.privateMessage) {
      this.setState({
        error: intl.formatMessage(this.messages['error.privateMessageMissing']),
      });
      return false;
    }
    if (expense.payoutMethod === 'paypal' && !expense.paypalEmail) {
      this.setState({
        error: intl.formatMessage(this.messages['error.paypalEmailMissing']),
      });
      return false;
    }
    this.setState({ error: null });
    return true;
  }

  handleChange(attr, value) {
    const expense = {
      ...this.state.expense,
      [attr]: value,
    };
    const newState = {
      modified: true,
      expense,
      isExpenseValid: this.validate(expense),
    };
    this.setState(newState);
    this.props.onChange && this.props.onChange(expense);
  }

  UNSAFE_componentWillReceiveProps(newProps) {
    if (!this.props.LoggedInUser && newProps.LoggedInUser && !this.state.expense.paypalEmail) {
      this.handleChange('paypalEmail', newProps.LoggedInUser.paypalEmail);
    }
  }

  async onSubmit(e) {
    if (e) {
      e.preventDefault();
    }
    try {
      await this.props.onSubmit(this.state.expense);
      this.setState({
        modified: false,
        isExpenseValid: false,
        expense: {},
        loading: false,
      });
    } catch (e) {
      this.setState({ loading: false });
      console.error('CreateExpenseForm onSubmit error', e);
    }
    return false;
  }

  renderForm() {
    const { LoggedInUser, intl, collective } = this.props;
    const { expense } = this.state;

    const payoutMethods = this.getOptions(['paypal', 'other', 'donation'], {
      paypalEmail: get(expense, 'user.paypalEmail') || intl.formatMessage(this.messages['newExpense.paypal.label']),
    });

    return (
      <div className={`CreateExpenseForm ${this.props.mode}`}>

        <Container as="form" onSubmit={this.onSubmit} maxWidth={[500, null, 800]} mx="auto">
          {!collective.expensePolicy && LoggedInUser && LoggedInUser.canEditCollective(collective) && (

                <FormattedMessage id="collective.expensePolicy.label" defaultMessage="Expense policy" />

                <FormattedMessage
                  id="collective.expensePolicy.description"
                  defaultMessage="It can be daunting for the community to file an expense. Help them by providing a clear expense policy to explain what they can expense."
                />

              <Button className="blue" href={`/${collective.slug}/edit/expenses`}>
                <FormattedMessage id="expense.expensePolicy.add" defaultMessage="add an expense policy" />
              </Button>

          )}
          {(collective.expensePolicy || get(collective, 'host.expensePolicy')) && (

                <FormattedMessage id="expense.expensePolicy" defaultMessage="Expense policy" />

              {collective.expensePolicy && <Markdown source={collective.expensePolicy} />}
              {get(collective, 'host.expensePolicy') && <Markdown source={get(collective, 'host.expensePolicy')} />}

          )}

            <FormattedMessage
              id="expense.disclaimer"
              defaultMessage="You must upload a valid receipt or invoice. We should be able to see clearly on the picture (or PDF) the total amount paid, the date, the items purchased and the legal address."
            />

              <FormattedMessage id="needHelp" defaultMessage="Need help?" />

              <InputField
                type="dropzone"
                options={{ accept: 'image/jpeg, image/png, application/pdf' }}
                name="attachment"
                className="attachmentField"
                onChange={attachment => this.handleChange('attachment', attachment)}
                defaultValue={expense.attachment}
                placeholder={'/static/images/receipt.svg'}
                description={
                  <FormattedMessage
                    id="expense.attachment.description"
                    defaultMessage="Upload receipt or invoice (photo or PDF)"
                  />
                }
              />

                  <FormattedMessage id="expense.description" defaultMessage="description" />

                    <InputField
                      type="text"
                      name="description"
                      defaultValue={expense.description}
                      className="descriptionField"
                      maxLength={255}
                      onChange={description => this.handleChange('description', description)}
                    />

                <FormattedMessage id="expense.amount" defaultMessage="amount" />

                  <InputField
                    defaultValue={expense.amount}
                    pre={getCurrencySymbol(collective.currency)}
                    type="currency"
                    name="amount"
                    className="amountField"
                    onChange={amount => this.handleChange('amount', amount)}
                  />

                <FormattedMessage id="expense.incurredAt" defaultMessage="Date" />

                  <InputField
                    defaultValue={new Date()}
                    type="date"
                    name="incurredAt"
                    className="incurredAtField"
                    onChange={incurredAt => this.handleChange('incurredAt', incurredAt)}
                  />

                <FormattedMessage id="expense.category" defaultMessage="category" />

                  <InputField
                    type="select"
                    options={this.categoriesOptions}
                    defaultValue={expense.category}
                    name="category"
                    className="categoryField"
                    onChange={category => this.handleChange('category', category)}
                  />

                <FormattedMessage id="expense.payoutMethod" defaultMessage="payout method" />

              <InputField
                type="select"
                name="payoutMethod"
                options={payoutMethods}
                defaultValue={expense.payoutMethod}
                onChange={payoutMethod => this.handleChange('payoutMethod', payoutMethod)}
              />

            {this.state.expense.payoutMethod === 'paypal' && (

                  <FormattedMessage id="expense.payoutMethod.paypal.label" defaultMessage="PayPal address" />

                <InputField
                  type="email"
                  name="paypalEmail"
                  key={`paypalEmail-${get(LoggedInUser, 'id')}`}
                  defaultValue={this.state.expense.paypalEmail}
                  onChange={paypalEmail => this.handleChange('paypalEmail', paypalEmail)}
                />

            )}

                <FormattedMessage id="expense.privateMessage" defaultMessage="Private instructions" />

              <InputField
                type="textarea"
                name="privateMessage"
                onChange={privateMessage => this.handleChange('privateMessage', privateMessage)}
                defaultValue={expense.privateMessage}
                description={
                  <FormattedMessage
                    id="expense.privateMessage.description"
                    defaultMessage="Private instructions for the host to reimburse your expense"
                  />
                }
              />

                <Button className="blue" type="submit" disabled={this.state.loading || !this.state.isExpenseValid}>
                  {this.state.loading && <FormattedMessage id="form.processing" defaultMessage="processing" />}
                  {!this.state.loading && <FormattedMessage id="expense.new.submit" defaultMessage="Submit Expense" />}
                </Button>

              {this.state.error &&
{this.state.error}
}

        </Container>

    );
  }

  render() {
    const { LoggedInUser, collective } = this.props;

    if (!LoggedInUser) {
      return (

            <FormattedMessage id="expenses.create.login" defaultMessage="Sign up or login to submit an expense." />

          <SignInForm next={`/${collective.slug}/expenses/new`} />

      );
    } else {
      return this.renderForm();
    }
  }
}

export default withIntl(CreateExpenseForm);
<commit_msg>fix: use a route for the link to the FAQ<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, FormattedMessage } from 'react-intl';
import { get } from 'lodash';
import Markdown from 'react-markdown';

import withIntl from '../../../lib/withIntl';
import { getCurrencySymbol } from '../../../lib/utils';
import { Link } from '../../../server/pages';

import InputField from '../../../components/InputField';
import SignInForm from '../../../components/SignInForm';
import categories from '../../../constants/categories';
import Button from '../../../components/Button';
import { P } from '../../../components/Text';
import Container from '../../../components/Container';

class CreateExpenseForm extends React.Component {
  static propTypes = {
    collective: PropTypes.object,
    LoggedInUser: PropTypes.object,
    onSubmit: PropTypes.func,
  };

  constructor(props) {
    super(props);
    this.getOptions = this.getOptions.bind(this);
    this.renderForm = this.renderForm.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.onSubmit = this.onSubmit.bind(this);

    this.messages = defineMessages({
      paypal: {
        id: 'expense.payoutMethod.paypal',
        defaultMessage: 'PayPal ({paypalEmail, select, missing {missing} other {{paypalEmail}}})',
      },
      'newExpense.paypal.label': {
        id: 'newExpense.paypal.label',
        defaultMessage: 'Please provide address',
      },
      other: {
        id: 'expense.payoutMethod.manual',
        defaultMessage: 'Other (see instructions)',
      },
      donation: {
        id: 'expense.payoutMethod.donation',
        defaultMessage: 'Donation',
      },
      'error.descriptionMissing': {
        id: 'expense.error.descriptionMissing',
        defaultMessage: 'Missing description',
      },
      'error.amountMissing': {
        id: 'expense.error.amountMissing',
        defaultMessage: 'Amount must be greater than 0',
      },
      'error.privateMessageMissing': {
        id: 'expense.error.privateMessageMissing',
        defaultMessage: "Please provide instructions on how you'd like to be reimbursed as a private note",
      },
      'error.paypalEmailMissing': {
        id: 'expense.error.paypalEmailMissing',
        defaultMessage: 'Please provide your PayPal email address (or change the payout method)',
      },
      'error.attachmentMissing': {
        id: 'expense.error.attachmentMissing',
        defaultMessage: 'Missing attachment',
      },
    });

    this.categoriesOptions = categories(props.collective.slug).map(category => {
      return { [category]: category };
    });

    this.state = {
      modified: false,
      expense: {
        category: Object.keys(this.categoriesOptions[0])[0],
        payoutMethod: 'paypal',
        paypalEmail: (props.LoggedInUser && props.LoggedInUser.paypalEmail) || null,
      },
      isExpenseValid: false,
      loading: false,
    };
  }

  getOptions(arr, intlVars) {
    return arr.map(key => {
      const obj = {};
      obj[key] = this.props.intl.formatMessage(this.messages[key], intlVars);
      return obj;
    });
  }

  validate(expense) {
    const { intl } = this.props;
    if (!expense.description) {
      this.setState({
        error: intl.formatMessage(this.messages['error.descriptionMissing']),
      });
      return false;
    }
    if (!expense.amount > 0) {
      this.setState({
        error: intl.formatMessage(this.messages['error.amountMissing']),
      });
      return false;
    }
    if (!expense.attachment) {
      this.setState({
        error: intl.formatMessage(this.messages['error.attachmentMissing']),
      });
      return false;
    }
    if (expense.payoutMethod === 'other' && !expense.privateMessage) {
      this.setState({
        error: intl.formatMessage(this.messages['error.privateMessageMissing']),
      });
      return false;
    }
    if (expense.payoutMethod === 'paypal' && !expense.paypalEmail) {
      this.setState({
        error: intl.formatMessage(this.messages['error.paypalEmailMissing']),
      });
      return false;
    }
    this.setState({ error: null });
    return true;
  }

  handleChange(attr, value) {
    const expense = {
      ...this.state.expense,
      [attr]: value,
    };
    const newState = {
      modified: true,
      expense,
      isExpenseValid: this.validate(expense),
    };
    this.setState(newState);
    this.props.onChange && this.props.onChange(expense);
  }

  UNSAFE_componentWillReceiveProps(newProps) {
    if (!this.props.LoggedInUser && newProps.LoggedInUser && !this.state.expense.paypalEmail) {
      this.handleChange('paypalEmail', newProps.LoggedInUser.paypalEmail);
    }
  }

  async onSubmit(e) {
    if (e) {
      e.preventDefault();
    }
    try {
      await this.props.onSubmit(this.state.expense);
      this.setState({
        modified: false,
        isExpenseValid: false,
        expense: {},
        loading: false,
      });
    } catch (e) {
      this.setState({ loading: false });
      console.error('CreateExpenseForm onSubmit error', e);
    }
    return false;
  }

  renderForm() {
    const { LoggedInUser, intl, collective } = this.props;
    const { expense } = this.state;

    const payoutMethods = this.getOptions(['paypal', 'other', 'donation'], {
      paypalEmail: get(expense, 'user.paypalEmail') || intl.formatMessage(this.messages['newExpense.paypal.label']),
    });

    return (
      <div className={`CreateExpenseForm ${this.props.mode}`}>

        <Container as="form" onSubmit={this.onSubmit} maxWidth={[500, null, 800]} mx="auto">
          {!collective.expensePolicy && LoggedInUser && LoggedInUser.canEditCollective(collective) && (

                <FormattedMessage id="collective.expensePolicy.label" defaultMessage="Expense policy" />

                <FormattedMessage
                  id="collective.expensePolicy.description"
                  defaultMessage="It can be daunting for the community to file an expense. Help them by providing a clear expense policy to explain what they can expense."
                />

              <Button className="blue" href={`/${collective.slug}/edit/expenses`}>
                <FormattedMessage id="expense.expensePolicy.add" defaultMessage="add an expense policy" />
              </Button>

          )}
          {(collective.expensePolicy || get(collective, 'host.expensePolicy')) && (

                <FormattedMessage id="expense.expensePolicy" defaultMessage="Expense policy" />

              {collective.expensePolicy && <Markdown source={collective.expensePolicy} />}
              {get(collective, 'host.expensePolicy') && <Markdown source={get(collective, 'host.expensePolicy')} />}

          )}

            <FormattedMessage
              id="expense.disclaimer"
              defaultMessage="You must upload a valid receipt or invoice. We should be able to see clearly on the picture (or PDF) the total amount paid, the date, the items purchased and the legal address."
            />

            <Link route="faq" params={{ path: 'faq', pageSlug: 'expenses' }}>

                <FormattedMessage id="needHelp" defaultMessage="Need help?" />

            </Link>

              <InputField
                type="dropzone"
                options={{ accept: 'image/jpeg, image/png, application/pdf' }}
                name="attachment"
                className="attachmentField"
                onChange={attachment => this.handleChange('attachment', attachment)}
                defaultValue={expense.attachment}
                placeholder={'/static/images/receipt.svg'}
                description={
                  <FormattedMessage
                    id="expense.attachment.description"
                    defaultMessage="Upload receipt or invoice (photo or PDF)"
                  />
                }
              />

                  <FormattedMessage id="expense.description" defaultMessage="description" />

                    <InputField
                      type="text"
                      name="description"
                      defaultValue={expense.description}
                      className="descriptionField"
                      maxLength={255}
                      onChange={description => this.handleChange('description', description)}
                    />

                <FormattedMessage id="expense.amount" defaultMessage="amount" />

                  <InputField
                    defaultValue={expense.amount}
                    pre={getCurrencySymbol(collective.currency)}
                    type="currency"
                    name="amount"
                    className="amountField"
                    onChange={amount => this.handleChange('amount', amount)}
                  />

                <FormattedMessage id="expense.incurredAt" defaultMessage="Date" />

                  <InputField
                    defaultValue={new Date()}
                    type="date"
                    name="incurredAt"
                    className="incurredAtField"
                    onChange={incurredAt => this.handleChange('incurredAt', incurredAt)}
                  />

                <FormattedMessage id="expense.category" defaultMessage="category" />

                  <InputField
                    type="select"
                    options={this.categoriesOptions}
                    defaultValue={expense.category}
                    name="category"
                    className="categoryField"
                    onChange={category => this.handleChange('category', category)}
                  />

                <FormattedMessage id="expense.payoutMethod" defaultMessage="payout method" />

              <InputField
                type="select"
                name="payoutMethod"
                options={payoutMethods}
                defaultValue={expense.payoutMethod}
                onChange={payoutMethod => this.handleChange('payoutMethod', payoutMethod)}
              />

            {this.state.expense.payoutMethod === 'paypal' && (

                  <FormattedMessage id="expense.payoutMethod.paypal.label" defaultMessage="PayPal address" />

                <InputField
                  type="email"
                  name="paypalEmail"
                  key={`paypalEmail-${get(LoggedInUser, 'id')}`}
                  defaultValue={this.state.expense.paypalEmail}
                  onChange={paypalEmail => this.handleChange('paypalEmail', paypalEmail)}
                />

            )}

                <FormattedMessage id="expense.privateMessage" defaultMessage="Private instructions" />

              <InputField
                type="textarea"
                name="privateMessage"
                onChange={privateMessage => this.handleChange('privateMessage', privateMessage)}
                defaultValue={expense.privateMessage}
                description={
                  <FormattedMessage
                    id="expense.privateMessage.description"
                    defaultMessage="Private instructions for the host to reimburse your expense"
                  />
                }
              />

                <Button className="blue" type="submit" disabled={this.state.loading || !this.state.isExpenseValid}>
                  {this.state.loading && <FormattedMessage id="form.processing" defaultMessage="processing" />}
                  {!this.state.loading && <FormattedMessage id="expense.new.submit" defaultMessage="Submit Expense" />}
                </Button>

              {this.state.error &&
{this.state.error}
}

        </Container>

    );
  }

  render() {
    const { LoggedInUser, collective } = this.props;

    if (!LoggedInUser) {
      return (

            <FormattedMessage id="expenses.create.login" defaultMessage="Sign up or login to submit an expense." />

          <SignInForm next={`/${collective.slug}/expenses/new`} />

      );
    } else {
      return this.renderForm();
    }
  }
}

export default withIntl(CreateExpenseForm);
<|endoftext|>


<commit_before>var L = A.Lang,
	isString = L.isString,
	isNumber = L.isNumber,
	isObject = L.isObject,
	isBoolean = L.isBoolean,

	isNodeList = function(v) {
		return (v instanceof A.NodeList);
	},

	ALIGN = 'align',
	BL = 'bl',
	BOUNDING_BOX = 'boundingBox',
	CANCELLABLE_HIDE = 'cancellableHide',
	CONTEXT_OVERLAY = 'contextoverlay',
	CURRENT_NODE = 'currentNode',
	FOCUSED = 'focused',
	HIDE = 'hide',
	HIDE_DELAY = 'hideDelay',
	HIDE_ON = 'hideOn',
	HIDE_ON_DOCUMENT_CLICK = 'hideOnDocumentClick',
	MOUSEDOWN = 'mousedown',
	SHOW = 'show',
	SHOW_DELAY = 'showDelay',
	SHOW_ON = 'showOn',
	TL = 'tl',
	TRIGGER = 'trigger',
	VISIBLE = 'visible';

function ContextOverlay(config) {
	var instance = this;

	instance._hideTask = new A.DelayedTask(instance.hide, instance);
	instance._showTask = new A.DelayedTask(instance.show, instance);

	instance._showCallback = null;
	instance._hideCallback = null;

 	ContextOverlay.superclass.constructor.apply(this, arguments);
}

A.mix(ContextOverlay, {
	NAME: CONTEXT_OVERLAY,

	ATTRS: {
		align: {
            value: { node: null, points: [ TL, BL ] }
        },

		cancellableHide: {
			value: true,
			validator: isBoolean
		},

		currentNode: {
			valueFn: function() {
				// define default currentNode as the first item from trigger
				return this.get(TRIGGER).item(0);
			}
		},

		delay: {
			value: null,
			validator: isObject
		},

		hideOn: {
			lazyAdd: false,
			value: 'mouseout',
			validator: isString,
			setter: function(v) {
				return this._setHideOn(v);
			}
		},

		hideOnDocumentClick: {
			lazyAdd: false,
			setter: function(v) {
				return this._setHideOnDocumentClick(v);
			},
			value: true,
			validator: isBoolean
		},

		hideDelay: {
			value: 0
		},

		showOn: {
			lazyAdd: false,
			value: 'mouseover',
			validator: isString,
			setter: function(v) {
				return this._setShowOn(v);
			}
		},

		showDelay: {
			value: 0,
			validator: isNumber
		},

		trigger: {
			lazyAdd: false,
			setter: function(v) {
				if (isNodeList(v)) {
					return v;
				}
				else if (isString(v)) {
					return A.all(v);
				}

				return new A.NodeList([v]);
			}
		},

		visible: {
			value: false
		}
	}
});

A.extend(ContextOverlay, A.ComponentOverlay, {
	/*
	* Lifecycle
	*/
	bindUI: function(){
		var instance = this;
		var boundingBox = instance.get(BOUNDING_BOX);

		boundingBox.on(MOUSEDOWN, instance._stopTriggerEventPropagation);

		instance.before('triggerChange', instance._beforeTriggerChange);
		instance.before('showOnChange', instance._beforeShowOnChange);
		instance.before('hideOnChange', instance._beforeHideOnChange);

		instance.after('triggerChange', instance._afterTriggerChange);
		instance.after('showOnChange', instance._afterShowOnChange);
		instance.after('hideOnChange', instance._afterHideOnChange);

		boundingBox.on('click', A.bind(instance._cancelAutoHide, instance));
		boundingBox.on('mouseenter', A.bind(instance._cancelAutoHide, instance));
		boundingBox.on('mouseleave', A.bind(instance._invokeHideTaskOnInteraction, instance));
		instance.after('focusedChange', A.bind(instance._invokeHideTaskOnInteraction, instance));
	},

	destructor: function() {
		var instance = this;

		instance.get(BOUNDING_BOX).remove();
	},

	/*
	* Methods
	*/
	hide: function() {
		var instance = this;

		instance.clearIntervals();

		instance.fire('hide');

		ContextOverlay.superclass.hide.apply(instance, arguments);
	},

	show: function(event) {
		var instance = this;

		instance.clearIntervals();

		instance.updateCurrentNode(event);

		instance.fire('show');

		ContextOverlay.superclass.show.apply(instance, arguments);

		instance.refreshAlign();
	},

	toggle: function(event) {
		var instance = this;

		if (instance.get(VISIBLE)) {
			instance._hideTask.delay( instance.get(HIDE_DELAY), null, null, [event] );
		}
		else {
			instance._showTask.delay( instance.get(SHOW_DELAY), null, null, [event] );
		}
	},

	clearIntervals: function() {
		this._hideTask.cancel();
		this._showTask.cancel();
	},

	refreshAlign: function() {
		var instance = this;
		var align = instance.get(ALIGN);
		var currentNode = instance.get(CURRENT_NODE);

		if (currentNode) {
			instance._uiSetAlign(currentNode, align.points);
		}
	},

	updateCurrentNode: function(event) {
		var instance = this;
		var align = instance.get(ALIGN);
		var trigger = instance.get(TRIGGER);
		var currentTarget = null;

		if (event) {
			currentTarget = event.currentTarget;
		}

		var node = align.node || currentTarget || trigger.item(0);

		if (node) {
			instance.set(CURRENT_NODE, node);
		}
	},

	_toggle: function(event) {
		var instance = this;
		var currentTarget = event.currentTarget;

		// check if the target is different and simulate a .hide() before toggle
		if (instance._lastTarget != currentTarget) {
			instance.hide();
		}

		instance.toggle(event);

		event.stopPropagation();

		instance._lastTarget = currentTarget;
	},

	/*
	* Attribute Listeners
	*/
	_afterShowOnChange: function(event) {
		var instance = this;
		var wasToggle = event.prevVal == instance.get(HIDE_ON);

		if (wasToggle) {
			var trigger = instance.get(TRIGGER);

			// if wasToggle remove the toggle callback
			trigger.detach(event.prevVal, instance._hideCallback);
			// and re attach the hide event
			instance._setHideOn( instance.get(HIDE_ON) );
		}
	},

	_afterHideOnChange: function(event) {
		var instance = this;
		var wasToggle = event.prevVal == instance.get(SHOW_ON);

		if (wasToggle) {
			var trigger = instance.get(TRIGGER);

			// if wasToggle remove the toggle callback
			trigger.detach(event.prevVal, instance._showCallback);
			// and re attach the show event
			instance._setShowOn( instance.get(SHOW_ON) );
		}
	},

	_afterTriggerChange: function(event) {
		var instance = this;

		instance._setShowOn( instance.get(SHOW_ON) );
		instance._setHideOn( instance.get(HIDE_ON) );
	},

	_beforeShowOnChange: function(event) {
		var instance = this;
		var trigger = instance.get(TRIGGER);

		// detach the old callback
		trigger.detach(event.prevVal, instance._showCallback);
	},

	_beforeHideOnChange: function(event) {
		var instance = this;
		var trigger = instance.get(TRIGGER);

		// detach the old callback
		trigger.detach(event.prevVal, instance._hideCallback);
	},

	_beforeTriggerChange: function(event) {
		var instance = this;
		var trigger = instance.get(TRIGGER);
		var showOn = instance.get(SHOW_ON);
		var hideOn = instance.get(HIDE_ON);

		trigger.detach(showOn, instance._showCallback);
		trigger.detach(hideOn, instance._hideCallback);
		trigger.detach(MOUSEDOWN, instance._stopTriggerEventPropagation);
	},

	// cancel hide if the user does some interaction with the tooltip
	// interaction = focus, click, mouseover
	_cancelAutoHide: function(event) {
		var instance = this;

		if (instance.get(CANCELLABLE_HIDE)) {
			instance.clearIntervals();
		}

		event.stopPropagation();
	},

	_invokeHideTaskOnInteraction: function(event) {
		var instance = this;
		var cancellableHide = instance.get(CANCELLABLE_HIDE);
		var focused = instance.get(FOCUSED);

		if (!focused && !cancellableHide) {
			instance._hideTask.delay( instance.get(HIDE_DELAY) );
		}
	},

	_stopTriggerEventPropagation: function(event) {
		event.stopPropagation();
	},

	/*
	* Setters
	*/
	_setHideOn: function(eventType) {
		var instance = this;
		var trigger = instance.get(TRIGGER);
		var toggle = eventType == instance.get(SHOW_ON);

		if (toggle) {
			instance._hideCallback = A.bind(instance._toggle, instance);

			// only one attached event is enough for toggle
			trigger.detach(eventType, instance._showCallback);
		}
		else {
			var delay = instance.get(HIDE_DELAY);

			instance._hideCallback = function(event) {
				instance._hideTask.delay(delay, null, null, [event]);

				event.stopPropagation();
			};
		}

		trigger.on(eventType, instance._hideCallback);

		return eventType;
	},

	_setHideOnDocumentClick: function(value) {
		var instance = this;

		if (value) {
			A.ContextOverlayManager.register(instance);
		}
		else {
			A.ContextOverlayManager.remove(instance);
		}

		return value;
	},

	_setShowOn: function(eventType) {
		var instance = this;
		var trigger = instance.get(TRIGGER);
		var toggle = eventType == instance.get(HIDE_ON);

		if (toggle) {
			instance._showCallback = A.bind(instance._toggle, instance);

			// only one attached event is enough for toggle
			trigger.detach(eventType, instance._hideCallback);
		}
		else {
			var delay = instance.get(SHOW_DELAY);

			instance._showCallback = function(event) {
				instance._showTask.delay(delay, null, null, [event]);

				event.stopPropagation();
			};
		}

		if (eventType != MOUSEDOWN) {
			trigger.on(MOUSEDOWN, instance._stopTriggerEventPropagation);
		}
		else {
			trigger.detach(MOUSEDOWN, instance._stopTriggerEventPropagation);
		}

		trigger.on(eventType, instance._showCallback);

		return eventType;
	}
});

A.ContextOverlay = ContextOverlay;

A.ContextOverlayManager = new A.OverlayManager({});

A.on(MOUSEDOWN, function() { A.ContextOverlayManager.hideAll(); }, A.getDoc());<commit_msg>Documentation - ContextOverlay<commit_after>/**
 * The ContextOverlay Utility
 *
 * @module aui-context-overlay
 */

var L = A.Lang,
	isString = L.isString,
	isNumber = L.isNumber,
	isObject = L.isObject,
	isBoolean = L.isBoolean,

	isNodeList = function(v) {
		return (v instanceof A.NodeList);
	},

	ALIGN = 'align',
	BL = 'bl',
	BOUNDING_BOX = 'boundingBox',
	CANCELLABLE_HIDE = 'cancellableHide',
	CONTEXT_OVERLAY = 'contextoverlay',
	CURRENT_NODE = 'currentNode',
	FOCUSED = 'focused',
	HIDE = 'hide',
	HIDE_DELAY = 'hideDelay',
	HIDE_ON = 'hideOn',
	HIDE_ON_DOCUMENT_CLICK = 'hideOnDocumentClick',
	MOUSEDOWN = 'mousedown',
	SHOW = 'show',
	SHOW_DELAY = 'showDelay',
	SHOW_ON = 'showOn',
	TL = 'tl',
	TRIGGER = 'trigger',
	VISIBLE = 'visible';

/**
 *

 *
 * A base class for ContextOverlay, providing:
 *

 *
Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)

 *
Able to display an Overlay at a specified corner of an element trigger

 *

 *
 * Quick Example:

 *
 *
var instance = new A.ContextOverlay({
 *  boundingBox: '#OverlayBoundingBox',
 *  hideOn: 'mouseleave',
 *  showOn: 'mouseenter',
 *	trigger: '.menu-trigger'
 * }).render();
 *

 *
 * Check the list of Configuration Attributes available for
 * ContextOverlay.
 *
 * @class ContextOverlay
 * @param config {Object} Object literal specifying widget configuration properties.
 * @constructor
 * @extends ComponentOverlay
 */
function ContextOverlay(config) {
	var instance = this;

	instance._hideTask = new A.DelayedTask(instance.hide, instance);
	instance._showTask = new A.DelayedTask(instance.show, instance);

	instance._showCallback = null;
	instance._hideCallback = null;

 	ContextOverlay.superclass.constructor.apply(this, arguments);
}

A.mix(ContextOverlay, {
	/**
	 * Static property provides a string to identify the class.
	 *
	 * @property ContextOverlay.NAME
	 * @type String
	 * @static
	 */
	NAME: CONTEXT_OVERLAY,

	/**
	 * Static property used to define the default attribute
	 * configuration for the ContextOverlay.
	 *
	 * @property ContextOverlay.ATTRS
	 * @type Object
	 * @static
	 */
	ATTRS: {
		/**
		 * Inherited from Overlay.
		 *
		 * @attribute align
		 * @default { node: null, points: [ TL, BL ] }
		 * @type Object
		 */
		align: {
            value: { node: null, points: [ TL, BL ] }
        },

		/**
		 * Cancel auto hide delay if the user interact with the Overlay
         * (focus, click, mouseover)
		 *
		 * @attribute cancellableHide
		 * @default true
		 * @type boolean
		 */
		cancellableHide: {
			value: true,
			validator: isBoolean
		},

		/**
		 * ContextOverlay allow multiple elements to be the
         * trigger, the
         * currentNode stores the current active one.
		 *
		 * @attribute currentNode
		 * @default First item of the
         * trigger NodeList.
		 * @type Node
		 */
		currentNode: {
			valueFn: function() {
				// define default currentNode as the first item from trigger
				return this.get(TRIGGER).item(0);
			}
		},

		delay: {
			value: null,
			validator: isObject
		},

		/**
		 * The event which is responsible to hide the ContextOverlay.
		 *
		 * @attribute hideOn
		 * @default mouseout
		 * @type String
		 */
		hideOn: {
			lazyAdd: false,
			value: 'mouseout',
			validator: isString,
			setter: function(v) {
				return this._setHideOn(v);
			}
		},

		/**
		 * If true the instance is registered on the
         * ContextOverlayManager static
         * class and will be hide when the user click on document.
		 *
		 * @attribute hideOnDocumentClick
		 * @default true
		 * @type boolean
		 */
		hideOnDocumentClick: {
			lazyAdd: false,
			setter: function(v) {
				return this._setHideOnDocumentClick(v);
			},
			value: true,
			validator: isBoolean
		},

		/**
		 * Number of milliseconds after the hide method is invoked to hide the
         * ContextOverlay.
		 *
		 * @attribute hideDelay
		 * @default 0
		 * @type Number
		 */
		hideDelay: {
			value: 0
		},

		/**
		 * The event which is responsible to show the ContextOverlay.
		 *
		 * @attribute showOn
		 * @default mouseover
		 * @type String
		 */
		showOn: {
			lazyAdd: false,
			value: 'mouseover',
			validator: isString,
			setter: function(v) {
				return this._setShowOn(v);
			}
		},

		/**
		 * Number of milliseconds after the show method is invoked to show the
         * ContextOverlay.
		 *
		 * @attribute showDelay
		 * @default 0
		 * @type Number
		 */
		showDelay: {
			value: 0,
			validator: isNumber
		},

		/**
		 * Node, NodeList or Selector which will be used as trigger elements
         * to show or hide the ContextOverlay.
		 *
		 * @attribute trigger
		 * @default null
		 * @type Node | NodeList | String
		 */
		trigger: {
			lazyAdd: false,
			setter: function(v) {
				if (isNodeList(v)) {
					return v;
				}
				else if (isString(v)) {
					return A.all(v);
				}

				return new A.NodeList([v]);
			}
		},

		/**
		 * If true the ContextOverlay is visible by default after the render phase.
         * Inherited from Overlay.
		 *
		 * @attribute visible
		 * @default false
		 * @type boolean
		 */
		visible: {
			value: false
		}
	}
});

A.extend(ContextOverlay, A.ComponentOverlay, {
	/**
	 * Bind the events on the ContextOverlay UI. Lifecycle.
	 *
	 * @method bindUI
	 * @protected
	 */
	bindUI: function(){
		var instance = this;
		var boundingBox = instance.get(BOUNDING_BOX);

		boundingBox.on(MOUSEDOWN, instance._stopTriggerEventPropagation);

		instance.before('triggerChange', instance._beforeTriggerChange);
		instance.before('showOnChange', instance._beforeShowOnChange);
		instance.before('hideOnChange', instance._beforeHideOnChange);

		instance.after('triggerChange', instance._afterTriggerChange);
		instance.after('showOnChange', instance._afterShowOnChange);
		instance.after('hideOnChange', instance._afterHideOnChange);

		boundingBox.on('click', A.bind(instance._cancelAutoHide, instance));
		boundingBox.on('mouseenter', A.bind(instance._cancelAutoHide, instance));
		boundingBox.on('mouseleave', A.bind(instance._invokeHideTaskOnInteraction, instance));
		instance.after('focusedChange', A.bind(instance._invokeHideTaskOnInteraction, instance));
	},

	/**
	 * Descructor lifecycle implementation for the ContextOverlay class.
	 * Purges events attached to the node (and all child nodes).
	 *
	 * @method destructor
	 * @protected
	 */
	destructor: function() {
		var instance = this;

		instance.get(BOUNDING_BOX).remove();
	},

	/**
	 * Hides the ContextOverlay.
	 *
	 * @method hide
	 */
	hide: function() {
		var instance = this;

		instance.clearIntervals();

		instance.fire('hide');

		ContextOverlay.superclass.hide.apply(instance, arguments);
	},

	/**
	 * Shows the ContextOverlay.
	 *
	 * @method hide
	 */
	show: function(event) {
		var instance = this;

		instance.clearIntervals();

		instance.updateCurrentNode(event);

		instance.fire('show');

		ContextOverlay.superclass.show.apply(instance, arguments);

		instance.refreshAlign();
	},

	/**
	 * Toggles visibility of the ContextOverlay.
	 *
	 * @method toggle
	 * @param {EventFacade} event
	 */
	toggle: function(event) {
		var instance = this;

		if (instance.get(VISIBLE)) {
			instance._hideTask.delay( instance.get(HIDE_DELAY), null, null, [event] );
		}
		else {
			instance._showTask.delay( instance.get(SHOW_DELAY), null, null, [event] );
		}
	},

	/**
	 * Clear the intervals to show or hide the ContextOverlay. See
     * hideDelay and
     * showDelay.
	 *
	 * @method clearIntervals
	 */
	clearIntervals: function() {
		this._hideTask.cancel();
		this._showTask.cancel();
	},

	/**
	 * Refreshes the alignment of the ContextOverlay with the
     * currentNode. See
     * also align.
	 *
	 * @method refreshAlign
	 */
	refreshAlign: function() {
		var instance = this;
		var align = instance.get(ALIGN);
		var currentNode = instance.get(CURRENT_NODE);

		if (currentNode) {
			instance._uiSetAlign(currentNode, align.points);
		}
	},

	/**
	 * Update the
     * currentNode with the
     * align node or the
     * event.currentTarget and in last case with the first item of the
     * trigger.
	 *
	 * @method updateCurrentNode
	 * @param {EventFacade} event
	 */
	updateCurrentNode: function(event) {
		var instance = this;
		var align = instance.get(ALIGN);
		var trigger = instance.get(TRIGGER);
		var currentTarget = null;

		if (event) {
			currentTarget = event.currentTarget;
		}

		var node = align.node || currentTarget || trigger.item(0);

		if (node) {
			instance.set(CURRENT_NODE, node);
		}
	},

	/**
	 * Handles the logic for the
     * toggle.
	 *
	 * @method _toggle
	 * @param {EventFacade} event
	 * @protected
	 */
	_toggle: function(event) {
		var instance = this;
		var currentTarget = event.currentTarget;

		// check if the target is different and simulate a .hide() before toggle
		if (instance._lastTarget != currentTarget) {
			instance.hide();
		}

		instance.toggle(event);

		event.stopPropagation();

		instance._lastTarget = currentTarget;
	},

	/**
	 * Fires after the showOn
     * attribute change.
	 *
	 * @method _afterShowOnChange
	 * @param {EventFacade} event
	 * @protected
	 */
	_afterShowOnChange: function(event) {
		var instance = this;
		var wasToggle = event.prevVal == instance.get(HIDE_ON);

		if (wasToggle) {
			var trigger = instance.get(TRIGGER);

			// if wasToggle remove the toggle callback
			trigger.detach(event.prevVal, instance._hideCallback);
			// and re attach the hide event
			instance._setHideOn( instance.get(HIDE_ON) );
		}
	},

	/**
	 * Fires after the hideOn
     * attribute change.
	 *
	 * @method _afterHideOnChange
	 * @param {EventFacade} event
	 * @protected
	 */
	_afterHideOnChange: function(event) {
		var instance = this;
		var wasToggle = event.prevVal == instance.get(SHOW_ON);

		if (wasToggle) {
			var trigger = instance.get(TRIGGER);

			// if wasToggle remove the toggle callback
			trigger.detach(event.prevVal, instance._showCallback);
			// and re attach the show event
			instance._setShowOn( instance.get(SHOW_ON) );
		}
	},

	/**
	 * Fires after the trigger
     * attribute change.
	 *
	 * @method _afterTriggerChange
	 * @param {EventFacade} event
	 * @protected
	 */
	_afterTriggerChange: function(event) {
		var instance = this;

		instance._setShowOn( instance.get(SHOW_ON) );
		instance._setHideOn( instance.get(HIDE_ON) );
	},

	/**
	 * Fires before the showOn
     * attribute change.
	 *
	 * @method _beforeShowOnChange
	 * @param {EventFacade} event
	 * @protected
	 */
	_beforeShowOnChange: function(event) {
		var instance = this;
		var trigger = instance.get(TRIGGER);

		// detach the old callback
		trigger.detach(event.prevVal, instance._showCallback);
	},

	/**
	 * Fires before the hideOn
     * attribute change.
	 *
	 * @method _beforeHideOnChange
	 * @param {EventFacade} event
	 * @protected
	 */
	_beforeHideOnChange: function(event) {
		var instance = this;
		var trigger = instance.get(TRIGGER);

		// detach the old callback
		trigger.detach(event.prevVal, instance._hideCallback);
	},

	/**
	 * Fires before the trigger
     * attribute change.
	 *
	 * @method _beforeTriggerChange
	 * @param {EventFacade} event
	 * @protected
	 */
	_beforeTriggerChange: function(event) {
		var instance = this;
		var trigger = instance.get(TRIGGER);
		var showOn = instance.get(SHOW_ON);
		var hideOn = instance.get(HIDE_ON);

		trigger.detach(showOn, instance._showCallback);
		trigger.detach(hideOn, instance._hideCallback);
		trigger.detach(MOUSEDOWN, instance._stopTriggerEventPropagation);
	},

	/**
	 * Cancel hide event if the user does some interaction with the
     * ContextOverlay (focus, click or mouseover).
	 *
	 * @method _cancelAutoHide
	 * @param {EventFacade} event
	 * @protected
	 */
	_cancelAutoHide: function(event) {
		var instance = this;

		if (instance.get(CANCELLABLE_HIDE)) {
			instance.clearIntervals();
		}

		event.stopPropagation();
	},

	/**
	 * Invoke the hide event when the ContextOverlay looses the focus.
	 *
	 * @method _invokeHideTaskOnInteraction
	 * @param {EventFacade} event
	 * @protected
	 */
	_invokeHideTaskOnInteraction: function(event) {
		var instance = this;
		var cancellableHide = instance.get(CANCELLABLE_HIDE);
		var focused = instance.get(FOCUSED);

		if (!focused && !cancellableHide) {
			instance._hideTask.delay( instance.get(HIDE_DELAY) );
		}
	},

	/**
	 * Helper method to invoke event.stopPropagation().
	 *
	 * @method _stopTriggerEventPropagation
	 * @param {EventFacade} event
	 * @protected
	 */
	_stopTriggerEventPropagation: function(event) {
		event.stopPropagation();
	},

	/**
	 * Setter for the hideOn
     * attribute.
	 *
	 * @method _setHideOn
	 * @param {String} eventType Event type
	 * @protected
	 * @return String
	 */
	_setHideOn: function(eventType) {
		var instance = this;
		var trigger = instance.get(TRIGGER);
		var toggle = eventType == instance.get(SHOW_ON);

		if (toggle) {
			instance._hideCallback = A.bind(instance._toggle, instance);

			// only one attached event is enough for toggle
			trigger.detach(eventType, instance._showCallback);
		}
		else {
			var delay = instance.get(HIDE_DELAY);

			instance._hideCallback = function(event) {
				instance._hideTask.delay(delay, null, null, [event]);

				event.stopPropagation();
			};
		}

		trigger.on(eventType, instance._hideCallback);

		return eventType;
	},

	/**
	 * Setter for the
     * hideOnDocumentClick
     * attribute.
	 *
	 * @method _setHideOn
	 * @param {boolean} value
	 * @protected
	 * @return boolean
	 */
	_setHideOnDocumentClick: function(value) {
		var instance = this;

		if (value) {
			A.ContextOverlayManager.register(instance);
		}
		else {
			A.ContextOverlayManager.remove(instance);
		}

		return value;
	},

	/**
	 * Setter for the showOn
     * attribute.
	 *
	 * @method _setShowOn
	 * @param {String} eventType Event type
	 * @protected
	 * @return String
	 */
	_setShowOn: function(eventType) {
		var instance = this;
		var trigger = instance.get(TRIGGER);
		var toggle = eventType == instance.get(HIDE_ON);

		if (toggle) {
			instance._showCallback = A.bind(instance._toggle, instance);

			// only one attached event is enough for toggle
			trigger.detach(eventType, instance._hideCallback);
		}
		else {
			var delay = instance.get(SHOW_DELAY);

			instance._showCallback = function(event) {
				instance._showTask.delay(delay, null, null, [event]);

				event.stopPropagation();
			};
		}

		if (eventType != MOUSEDOWN) {
			trigger.on(MOUSEDOWN, instance._stopTriggerEventPropagation);
		}
		else {
			trigger.detach(MOUSEDOWN, instance._stopTriggerEventPropagation);
		}

		trigger.on(eventType, instance._showCallback);

		return eventType;
	}
});

A.ContextOverlay = ContextOverlay;

/**
 * A base class for ContextOverlayManager:
 *
 * @param config {Object} Object literal specifying widget configuration properties.
 *
 * @class ContextOverlayManager
 * @constructor
 * @extends OverlayManager
 * @static
 */
A.ContextOverlayManager = new A.OverlayManager({});

A.on(MOUSEDOWN, function() { A.ContextOverlayManager.hideAll(); }, A.getDoc());<|endoftext|>


<commit_before>'use strict';

module.exports = function (t, a) {
	var expected, inputMap = {
		"2016-01-02": {
			serviceA: {
				businessProcess: {
					pending: 33,
					sentBack: 40,
					rejected: 2
				},
				certificate: {
					certA: {
						submitted: 20,
						pending: 32
					},
					certB: {
						submitted: 25,
						pending: 14
					}
				},
				processingStep: {
					stepA: {
						pending: {
							businessProcess: 16,
							certificate: {
								certA: 5,
								certB: 6
							}
						},
						byProcessor: {
							// processor id
							'7464567456': {
								approved: {
									businessProcess: 13,
									certificate: {
										certA: 2,
										certB: 4
									}
								},
								rejected: {
									businessProcess: 0,
									certificate: {
										certA: 0,
										certB: 0
									}
								}
							},
							'52345234': {
								approved: {
									businessProcess: 12,
									certificate: {
										certA: 10,
										certB: 2
									}
								},
								rejected: {
									businessProcess: 1,
									certificate: {
										certA: 1,
										certB: 0
									}
								}
							}
						}
					}
				}
			}
		}
	};
	expected = {
		"2016-01-02": {
			serviceA: {
				processingStep: {
					stepA: {
						pending: {
							businessProcess: 16,
							certificate: {
								certA: 5,
								certB: 6
							}
						},
						approved: {
							businessProcess: 25,
							certificate: {
								certA: 12,
								certB: 6
							}
						},
						rejected: {
							businessProcess: 1,
							certificate: {
								certA: 1,
								certB: 0
							}
						}
					}
				}
			}
		}
	};
	a.deep(t(inputMap), expected);
};
<commit_msg>#1764 adjusted test case<commit_after>'use strict';

module.exports = function (t, a) {
	var expected, inputMap = {
		"2016-01-02": {
			serviceA: {
				businessProcess: {
					pending: 33,
					sentBack: 40,
					rejected: 2
				},
				certificate: {
					certA: {
						submitted: 20,
						pending: 32
					},
					certB: {
						submitted: 25,
						pending: 14
					}
				},
				processingStep: {
					stepA: {
						pending: {
							businessProcess: 16,
							certificate: {
								certA: 5,
								certB: 6
							}
						},
						byProcessor: {
							// processor id
							'7464567456': {
								approved: {
									businessProcess: 13,
									certificate: {
										certA: 2,
										certB: 4
									}
								},
								rejected: {
									businessProcess: 0,
									certificate: {
										certA: 0,
										certB: 0
									}
								}
							},
							'52345234': {
								approved: {
									businessProcess: 12,
									certificate: {
										certA: 10,
										certB: 2
									}
								},
								rejected: {
									businessProcess: 1,
									certificate: {
										certA: 1,
										certB: 0
									}
								}
							}
						}
					}
				}
			}
		}
	};
	expected = {
		"2016-01-02": {
			serviceA: {
				stepA: {
					pending: {
						businessProcess: 16,
						certificate: {
							certA: 5,
							certB: 6
						}
					},
					approved: {
						businessProcess: 25,
						certificate: {
							certA: 12,
							certB: 6
						}
					},
					rejected: {
						businessProcess: 1,
						certificate: {
							certA: 1,
							certB: 0
						}
					}
				}
			}
		}
	};
	a.deep(t(inputMap), expected);
};
<|endoftext|>


<commit_before>/* global $: false, document: false, EventSource:false */

'use strict';

function elapsedTime(t) {
  var result = '', diff;
  diff = Math.round(Date.now() / 1000) - t;
  if (diff / 86400 >= 1) {
    result += Math.floor(diff / 86400) + 'd';
  }
  diff %= 86400;
  if (diff / 3600 >= 1) {
    result += Math.floor(diff / 3600) + 'h';
  }
  diff %= 3600;
  if (diff / 60 >= 1) {
    result += Math.floor(diff / 60) + 'm';
  }
  diff %= 60;
  result += Math.floor(diff) + 's';
  return result;
}

var timestamps = {
  'ff': 0,
  'listeners': 0,
  'weather': 0,
  'status': 0
};

setInterval(function () {
  if (timestamps.ff !== 0) {
    $('.lastupdateff').text(elapsedTime(timestamps.ff));
  }
  if (timestamps.spaceDevices !== 0) {
    $('.lastupdatespaceDevices').text(elapsedTime(timestamps.spaceDevices));
  }
  if (timestamps.weather !== 0) {
    $('.lastupdateweather').text(elapsedTime(timestamps.weather));
  }
  if (timestamps.status !== 0) {
    $('.lastupdate').text(elapsedTime(timestamps.status));
  }
}, 1000);

function onDomReady() {
  var source = new EventSource('/api/statusStream?spaceOpen=1&spaceDevices=1&freifunk=1&weather=1');
  var lastkeepalive = +new Date();

  source.onopen = function () {
    $('.status .listeners').css('background-color', '#4ddb4d');
  };

  source.onerror = function (err) {
    $('.status .listeners').css('background-color', '#ff5c33');
  };

  source.addEventListener('spaceOpen', function (e) {
    function updateBox(text, color, until) {
      $('.status .mainstatus .mainstatus').text(text);
      $('.status .mainstatus').css('background-color', color);
      $('.status .until').text(until);
    }

    var data = $.parseJSON(e.data);
    switch (data.state) {
    case 'off':
      updateBox('ZU!', '#ff5c33', '');
      break;
    case 'on':
      updateBox('AUF!', '#4ddb4d', '');
      break;
    case 'closing':
      var until = '';
//      var ud = new Date(data.until * 1000);
//      if (data.until > 0) {
//        until = 'Bis ' + ud.getHours() + ':' + (ud.getMinutes() < 10 ? '0' : '') + ud.getMinutes() + ' Uhr';
//      }
      updateBox('GLEICH ZU!', '#F89238', until);
      break;
    }
    timestamps.status = data.timestamp;
  }, false);

  source.addEventListener('spaceDevices', function (e) {
    var data = $.parseJSON(e.data);

    var text = data.peopleCount + ' + ' + data.deviceCount;
    var title = data.people.length > 0 ? data.people.join(', ') : 'No public people';
    $('#pis').text(text).attr('title', title);
    timestamps.spaceDevices = data.timestamp;
  }, false);

  source.addEventListener('freifunk', function (e) {
    var ff = $.parseJSON(e.data);
    $('.status .ff .ff').text(ff.client_count);
    timestamps.ff = ff.timestamp;
  }, false);

  function tempcolor(t) {
    var result = t < 0 ? '#0066ff' : t < 100 ? '#75a3ff' : t < 200 ? '#9966cc' : t < 300 ? '#ffcc00' : '#ff6600';
    return result;
  }

  source.addEventListener('weather', function (e) {
    var weather = $.parseJSON(e.data);
    var Wds = [ 'N', 'NNO', 'NO', 'ONO', 'O', 'OSO', 'SO', 'SSO', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW' ];
    timestamps.weather = weather.timestamp;
    $('.Tin').html(weather.Tin / 10 + '°');
    $('.Tout').html(weather.Tout / 10 + '°');
    $('.Hin').text(weather.Hin + '%');
    $('.Hout').text(weather.Hout + '%');
    $('.Wg').text(weather.Wg / 10 + ' m/s');
    $('.Wd').text(Wds[weather.Wd]);
    $('.Ws').text(weather.Ws / 10 + ' m/s');
    $('.R').text(weather.R);
    $('.P').text(weather.P / 10 + ' hPa');
    $('.weatherin').css('background-color', tempcolor(weather.Tin));
    $('.weatherout').css('background-color', tempcolor(weather.Tout));
  }, false);

  source.addEventListener('keepalive', function (e) {
    lastkeepalive = +new Date();
  }, false);

  // check whether we have seen a keepalive event within the last 70 minutes or are disconnected; reconnect if necessary
  var checkinterval = setInterval(function () {
    if ((new Date() - lastkeepalive > 65 * 60 * 1000) || source.readyState === 2) {
      source.close();
      clearInterval(checkinterval);
      setTimeout(onDomReady, 1000);
    }
  }, 5 * 60 * 1000);
}

if (typeof (EventSource) === 'function') {
  document.addEventListener('DOMContentLoaded', onDomReady, false);
} else {
  $.getScript('../lib/eventsource.js', function () {
    onDomReady();
  });
}
<commit_msg>old files removed<commit_after><|endoftext|>