<commit_before>/*
 Copyright (c) 2015, GEM Foundation.

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as
 published by the Free Software Foundation, either version 3 of the
 License, or (at your option) any later version.

 This program 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 <https://www.gnu.org/licenses/agpl.html>.
 */

(function($, Backbone, _) {
    var calculation_table;

    var progressHandlingFunction = function(progress) {
        var percent = progress.loaded / progress.total * 100;
        $('.bar').css('width', percent + '%');
        if (percent == 100) {
            dialog.hide();
        }
    };

    var dialog = (function ()
                  {
                      var pleaseWaitDiv = $('

Processing...

');
                      return {
                          show: function(msg, progress) {
                          $('h1', pleaseWaitDiv).text(msg);
                          if (progress) {
                          progressHandlingFunction({loaded: 0, total: 1});
                          } else {
                          progressHandlingFunction({loaded: 1, total: 1});
                      }
                              pleaseWaitDiv.modal();
                          },
                          hide: function () {
                              pleaseWaitDiv.modal('hide');
                          }
                      };
                  })();

    var diaerror = (function ()
                  {
                      var errorDiv = $('
\

\

\

\

Calculation not accepted: traceback
\

\

\

\

\
                      <input type="checkbox" id="diaerror_scroll_enabled" checked>\
                      Auto Scroll   \
                      <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>\

\

\

\

');
                      errorDiv.bind('hide', function() { calculation_table.hide_log(); });
                      return {
                          getdiv: function() {
                              return errorDiv;
                          },

                          show: function(title, msg) {
                              if (title != null) {
                                  $('.modal-title', errorDiv).html(title);
                              }
                              if (msg != null) {
                                  $('.modal-body-pre', errorDiv).html(msg);
                              }
                              errorDiv.modal();
                          },

                          append: function(title, msg) {
                              if (title != null) {
                                  $('.modal-title', errorDiv).html(title);
                              }
                              $( msg ).appendTo( $('.modal-body-pre', errorDiv) );
                          },

                          scroll_to_bottom: function(ctx) {
                              ctx.scrollTop(ctx[0].scrollHeight);
                          },

                          hide: function () {
                              errorDiv.modal('hide');
                          }
                      };
                  })();

    var CalculationTable = Backbone.View.extend(
        {
            /* the html element where the table is rendered */
            el: $('#my-calculations'),

            logXhr: null,
            logId: -1,
            logIsNew: false,
            logLinesAll: 0,
            logLines: 0,
            logTimeout: null,

            initialize: function(options) {

                /* whatever happens to any calculation, re-render the table */
                _.bindAll(this, 'render');
                this.calculations = options.calculations;
                this.calculations.bind('reset', this.render);
                this.calculations.bind('add', this.render);
                this.calculations.bind('remove', this.render);

                /* if false, it prevents the table to be refreshed */
                this.can_be_rendered = true;

                this.render();
            },

            events: {
                "click .btn-danger": "remove_calculation",
                "click .btn-traceback": "show_traceback",
                "click .btn-log": "show_log",
                "click .btn-file": "on_run_risk_clicked",
                "change .btn-file input": "on_run_risk_queued"
            },

            /* When an input dialog is opened, it is very important to not re-render the table */
            on_run_risk_clicked: function(e) {
                /* if a file input dialog has been opened do not refresh the calc table */
                this.can_be_rendered = false;
            },

            on_run_risk_queued: function(e) {
                this.can_be_rendered = true;
            },

            remove_calculation: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');
                var view = this;
                diaerror.show("Removing calculation " + calc_id, "...");
                $.post(gem_oq_server_url + "/v1/calc/" + calc_id + "/remove"
                     ).success(
                         function(data, textStatus, jqXHR)
                         {
                             diaerror.show("Removing calculation " + calc_id, "Calculation " + calc_id + " removed.");
                             view.calculations.remove([view.calculations.get(calc_id)]);
                         }
                     ).error(
                         function(jqXHR, textStatus, errorThrown)
                         {
                             if (jqXHR.status == 404) {
                                 diaerror.show("Removing calculation " + calc_id, "Failed: calculation " + calc_id + " not found.");
                             }
                             else {
                                 diaerror.show("Removing calculation " + calc_id, "Failed: " + textStatus);
                             }
                         }
                     );
            },

            show_traceback: function(e) {
                var calc_id = $(e.target).attr('data-calc-id');

                var myXhr = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/traceback",
                                    error: function (jqXHR, textStatus, errorThrown) {
                                        if (jqXHR.status == 404) {
                                            diaerror.show("Calculation " + calc_id + " not found.");
                                        }
                                        else {
                                            diaerror.show("Error retrieving traceback for calculation " + calc_id, textStatus);
                                        }
                                        // alert("Error: " + textStatus);
                                    },
                                    success: function (data, textStatus, jqXHR) {
                                        if (data.length == 0) {
                                            diaerror.show("Traceback not found for calculation " + calc_id, []);
                                        }
                                        else {
                                            var out = "";
                                            var ct = 0;
                                            for (s in data) {
                                                if (data[s] == "")
                                                    continue;
                                                out += '
' + data[s] + '
';
                                                ct++;
                                            }
                                            diaerror.show("Traceback of calculation " + calc_id, out);
                                        }
                                        // alert("Success: " + textStatus);
                                    }});
            },

            _show_log_priv: function(is_new, calc_id, is_running, from) {
                var was_running = is_running;

                // TO CHECK hide_log method enable console.log and take a look
                // console.log("_show_log_priv: begin");
                if (this.logXhr != null) {
                    this.logXhr.abort();
                    this.logXhr = null;
                }
                if (is_new) {
                    if (this.logTimeout != null) {
                        window.clearTimeout(this.logTimeout);
                        this.logTimeout = null;
                    }
                }
                var obj = this;

                this.logXhr = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/log/" + from + ":",
                                      error: function (jqXHR, textStatus, errorThrown) {
                                          if (jqXHR.status == 404) {
                                              diaerror.show("Log of calculation " + calc_id + " not found.");
                                          }
                                          else {
                                              diaerror.show("Error retrieving log for calculation " + calc_id, textStatus);
                                          }
                                          obj.logIsNew = false;
                                      },
                                      success: function (data, textStatus, jqXHR) {
                                          var delay = 250;

                                          if (is_new) {
                                              obj.logLines = 0;
                                              obj.logLinesAll = 0;
                                          }
                                          else {
                                              // if data is empty check if job is still running
                                              if (is_running) {
                                                  if (data.length == 0) {
                                                      var ajax, status;

                                                      delay = 1000;

                                                      ajax = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/status",
                                                                     async: false}).done(function (data) { status = data.is_running; });
                                                      if (status !== true) {
                                                          is_running = false;
                                                      }
                                                  }
                                              }
                                          }
                                          var out = "";

                                          for (s in data) {
                                              if (data[s] == "") {
                                                  obj.logLinesAll++;
                                                  continue;
                                              }
                                              out += '
' + data[s] + '
';
                                              obj.logLines++;
                                              obj.logLinesAll++;
                                          }

                                          var title;
                                          if (is_running) {
                                              var dt;
                                              dt = new Date();
                                              title = "Log of calculation " + calc_id + " - " + dt.toString();
                                          }
                                          else if (was_running != is_running) {
                                              title = "Log of calculation " + calc_id + " - finished";
                                          }
                                          else {
                                              title = "Log of calculation " + calc_id;
                                          }

                                          if (obj.logIsNew) {
                                              diaerror.show(title, out);
                                          }
                                          else {
                                              diaerror.append(title, out);
                                          }
                                          if ($("#diaerror_scroll_enabled").prop( "checked" )) {
                                              diaerror.scroll_to_bottom($('.modal-body', diaerror.getdiv()));
                                          }

                                          if (is_running) {
                                              function log_update(obj)
                                              {
                                                  obj._show_log_priv(false, obj.logId, true, obj.logLinesAll);
                                              }

                                              obj.logTimeout = window.setTimeout(log_update, delay, obj);
                                          }
                                          else {
                                              $('#diaerror_scroll_enabled_box').hide();
                                          }

                                          obj.logIsNew = false;
                                      }});
            },

            show_log: function(e) {
                var calc_id = $(e.target).attr('data-calc-id');
                var is_running = ($(e.target).attr('is-running') == "true");

                this.logId = calc_id;
                this.logIsNew = true;

                this._show_log_priv(true, calc_id, is_running, "0");
                $('#diaerror_scroll_enabled_box').show();
            },

            hide_log: function(e) {
                if (this.logTimeout != null) {
                    window.clearTimeout(this.logTimeout);
                    this.logTimeout = null;
                }
                if (this.logXhr != null) {
                    this.logXhr.abort();
                    this.logXhr = null;
                }
                $('#diaerror_scroll_enabled_box').hide();
            },

            render: function() {
                if (!this.can_be_rendered) {
                    return;
                };
                this.$el.html(_.template($('#calculation-table-template').html(),
                                         { calculations: this.calculations.models }));
            }
        });

    var Calculation = Backbone.Model.extend({
        defaults: {
            map: null,
            layers: []
        }
    });

    var Calculations = Backbone.Collection.extend(
        {
            model: Calculation,
            url: gem_oq_server_url + "/v1/calc/list?relevant=true"
        });
    var calculations = new Calculations();

    var refresh_calcs;

    function setTimer() {
        refresh_calcs = setInterval(function() { calculations.fetch({reset: true}) }, 3000);
    }

    /* classic event management */
    $(document).ready(
        function() {
            calculation_table = new CalculationTable({ calculations: calculations });
            calculations.fetch({reset: true});
            setTimer();

            /* XXX. Reset the input file value to ensure the change event
               will be always triggered */
            $(document).on("click", 'input[name=archive]',
                           function(e) { this.value = null; });
            $(document).on("change", 'input[name=archive]',
                           function(e) {
                               dialog.show('Uploading calculation', true);
                               var input = $(e.target);
                               var form = input.parents('form')[0];

                               $(form).ajaxSubmit(
                                   {
                                    xhr: function() {  // custom xhr to add progress bar management
                                        var myXhr = $.ajaxSettings.xhr();
                                        if(myXhr.upload){ // if upload property exists
                                            myXhr.upload.addEventListener('progress', progressHandlingFunction, false);
                                        }
                                        return myXhr;
                                    },
                                    success: function(data) {
                                        calculations.add(new Calculation(data), {at: 0});
                                    },
                                    error: function(xhr) {
                                        dialog.hide();
                                        var s, out, data = $.parseJSON(xhr.responseText);
                                        var out = "";
                                        var ct = 0;
                                        for (s in data) {
                                            if (data[s] == "")
                                                continue;
                                            out += '
' + data[s] + '
';
                                            ct++;
                                        }
                                        diaerror.show("Calculation not accepted: traceback", out);
                                    }});
                           });

            $(document).on('hidden.bs.modal', 'div[id^=traceback-]',
                           function(e) {
                               setTimer();
                           });

        });
})($, Backbone, _, gem_oq_server_url);
<commit_msg>webui: fixed source misalignment<commit_after>/*
 Copyright (c) 2015, GEM Foundation.

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as
 published by the Free Software Foundation, either version 3 of the
 License, or (at your option) any later version.

 This program 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 <https://www.gnu.org/licenses/agpl.html>.
 */

(function($, Backbone, _) {
    var calculation_table;

    var progressHandlingFunction = function(progress) {
        var percent = progress.loaded / progress.total * 100;
        $('.bar').css('width', percent + '%');
        if (percent == 100) {
            dialog.hide();
        }
    };

    var dialog = (function ()
                  {
                      var pleaseWaitDiv = $('

Processing...

');
                      return {
                          show: function(msg, progress) {
                              $('h1', pleaseWaitDiv).text(msg);
                              if (progress) {
                                  progressHandlingFunction({loaded: 0, total: 1});
                              } else {
                                  progressHandlingFunction({loaded: 1, total: 1});
                              }
                              pleaseWaitDiv.modal('show');
                          },
                          hide: function () {
                              pleaseWaitDiv.modal('hide');
                          }
                      };
                  })();

    var diaerror = (function ()
                  {
                      var errorDiv = $('
\

\

\

\

Calculation not accepted: traceback
\

\

\

\

\
                      <input type="checkbox" id="diaerror_scroll_enabled" checked>\
                      Auto Scroll   \
                      <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>\

\

\

\

');
                      errorDiv.bind('hide', function() { calculation_table.hide_log(); });
                      return {
                          getdiv: function() {
                              return errorDiv;
                          },

                          show: function(title, msg) {
                              if (title != null) {
                                  $('.modal-title', errorDiv).html(title);
                              }
                              if (msg != null) {
                                  $('.modal-body-pre', errorDiv).html(msg);
                              }
                              errorDiv.modal();
                          },

                          append: function(title, msg) {
                              if (title != null) {
                                  $('.modal-title', errorDiv).html(title);
                              }
                              $( msg ).appendTo( $('.modal-body-pre', errorDiv) );
                          },

                          scroll_to_bottom: function(ctx) {
                              ctx.scrollTop(ctx[0].scrollHeight);
                          },

                          hide: function () {
                              errorDiv.modal('hide');
                          }
                      };
                  })();

    var CalculationTable = Backbone.View.extend(
        {
            /* the html element where the table is rendered */
            el: $('#my-calculations'),

            logXhr: null,
            logId: -1,
            logIsNew: false,
            logLinesAll: 0,
            logLines: 0,
            logTimeout: null,

            initialize: function(options) {

                /* whatever happens to any calculation, re-render the table */
                _.bindAll(this, 'render');
                this.calculations = options.calculations;
                this.calculations.bind('reset', this.render);
                this.calculations.bind('add', this.render);
                this.calculations.bind('remove', this.render);

                /* if false, it prevents the table to be refreshed */
                this.can_be_rendered = true;

                this.render();
            },

            events: {
                "click .btn-danger": "remove_calculation",
                "click .btn-traceback": "show_traceback",
                "click .btn-log": "show_log",
                "click .btn-file": "on_run_risk_clicked",
                "change .btn-file input": "on_run_risk_queued"
            },

            /* When an input dialog is opened, it is very important to not re-render the table */
            on_run_risk_clicked: function(e) {
                /* if a file input dialog has been opened do not refresh the calc table */
                this.can_be_rendered = false;
            },

            on_run_risk_queued: function(e) {
                this.can_be_rendered = true;
            },

            remove_calculation: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');
                var view = this;
                diaerror.show("Removing calculation " + calc_id, "...");
                $.post(gem_oq_server_url + "/v1/calc/" + calc_id + "/remove"
                     ).success(
                         function(data, textStatus, jqXHR)
                         {
                             diaerror.show("Removing calculation " + calc_id, "Calculation " + calc_id + " removed.");
                             view.calculations.remove([view.calculations.get(calc_id)]);
                         }
                     ).error(
                         function(jqXHR, textStatus, errorThrown)
                         {
                             if (jqXHR.status == 404) {
                                 diaerror.show("Removing calculation " + calc_id, "Failed: calculation " + calc_id + " not found.");
                             }
                             else {
                                 diaerror.show("Removing calculation " + calc_id, "Failed: " + textStatus);
                             }
                         }
                     );
            },

            show_traceback: function(e) {
                var calc_id = $(e.target).attr('data-calc-id');

                var myXhr = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/traceback",
                                    error: function (jqXHR, textStatus, errorThrown) {
                                        if (jqXHR.status == 404) {
                                            diaerror.show("Calculation " + calc_id + " not found.");
                                        }
                                        else {
                                            diaerror.show("Error retrieving traceback for calculation " + calc_id, textStatus);
                                        }
                                        // alert("Error: " + textStatus);
                                    },
                                    success: function (data, textStatus, jqXHR) {
                                        if (data.length == 0) {
                                            diaerror.show("Traceback not found for calculation " + calc_id, []);
                                        }
                                        else {
                                            var out = "";
                                            var ct = 0;
                                            for (s in data) {
                                                if (data[s] == "")
                                                    continue;
                                                out += '
' + data[s] + '
';
                                                ct++;
                                            }
                                            diaerror.show("Traceback of calculation " + calc_id, out);
                                        }
                                        // alert("Success: " + textStatus);
                                    }});
            },

            _show_log_priv: function(is_new, calc_id, is_running, from) {
                var was_running = is_running;

                // TO CHECK hide_log method enable console.log and take a look
                // console.log("_show_log_priv: begin");
                if (this.logXhr != null) {
                    this.logXhr.abort();
                    this.logXhr = null;
                }
                if (is_new) {
                    if (this.logTimeout != null) {
                        window.clearTimeout(this.logTimeout);
                        this.logTimeout = null;
                    }
                }
                var obj = this;

                this.logXhr = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/log/" + from + ":",
                                      error: function (jqXHR, textStatus, errorThrown) {
                                          if (jqXHR.status == 404) {
                                              diaerror.show("Log of calculation " + calc_id + " not found.");
                                          }
                                          else {
                                              diaerror.show("Error retrieving log for calculation " + calc_id, textStatus);
                                          }
                                          obj.logIsNew = false;
                                      },
                                      success: function (data, textStatus, jqXHR) {
                                          var delay = 250;

                                          if (is_new) {
                                              obj.logLines = 0;
                                              obj.logLinesAll = 0;
                                          }
                                          else {
                                              // if data is empty check if job is still running
                                              if (is_running) {
                                                  if (data.length == 0) {
                                                      var ajax, status;

                                                      delay = 1000;

                                                      ajax = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/status",
                                                                     async: false}).done(function (data) { status = data.is_running; });
                                                      if (status !== true) {
                                                          is_running = false;
                                                      }
                                                  }
                                              }
                                          }
                                          var out = "";

                                          for (s in data) {
                                              if (data[s] == "") {
                                                  obj.logLinesAll++;
                                                  continue;
                                              }
                                              out += '
' + data[s] + '
';
                                              obj.logLines++;
                                              obj.logLinesAll++;
                                          }

                                          var title;
                                          if (is_running) {
                                              var dt;
                                              dt = new Date();
                                              title = "Log of calculation " + calc_id + " - " + dt.toString();
                                          }
                                          else if (was_running != is_running) {
                                              title = "Log of calculation " + calc_id + " - finished";
                                          }
                                          else {
                                              title = "Log of calculation " + calc_id;
                                          }

                                          if (obj.logIsNew) {
                                              diaerror.show(title, out);
                                          }
                                          else {
                                              diaerror.append(title, out);
                                          }
                                          if ($("#diaerror_scroll_enabled").prop( "checked" )) {
                                              diaerror.scroll_to_bottom($('.modal-body', diaerror.getdiv()));
                                          }

                                          if (is_running) {
                                              function log_update(obj)
                                              {
                                                  obj._show_log_priv(false, obj.logId, true, obj.logLinesAll);
                                              }

                                              obj.logTimeout = window.setTimeout(log_update, delay, obj);
                                          }
                                          else {
                                              $('#diaerror_scroll_enabled_box').hide();
                                          }

                                          obj.logIsNew = false;
                                      }});
            },

            show_log: function(e) {
                var calc_id = $(e.target).attr('data-calc-id');
                var is_running = ($(e.target).attr('is-running') == "true");

                this.logId = calc_id;
                this.logIsNew = true;

                this._show_log_priv(true, calc_id, is_running, "0");
                $('#diaerror_scroll_enabled_box').show();
            },

            hide_log: function(e) {
                if (this.logTimeout != null) {
                    window.clearTimeout(this.logTimeout);
                    this.logTimeout = null;
                }
                if (this.logXhr != null) {
                    this.logXhr.abort();
                    this.logXhr = null;
                }
                $('#diaerror_scroll_enabled_box').hide();
            },

            render: function() {
                if (!this.can_be_rendered) {
                    return;
                };
                this.$el.html(_.template($('#calculation-table-template').html(),
                                         { calculations: this.calculations.models }));
            }
        });

    var Calculation = Backbone.Model.extend({
        defaults: {
            map: null,
            layers: []
        }
    });

    var Calculations = Backbone.Collection.extend(
        {
            model: Calculation,
            url: gem_oq_server_url + "/v1/calc/list?relevant=true"
        });
    var calculations = new Calculations();

    var refresh_calcs;

    function setTimer() {
        refresh_calcs = setInterval(function() { calculations.fetch({reset: true}) }, 3000);
    }

    /* classic event management */
    $(document).ready(
        function() {
            calculation_table = new CalculationTable({ calculations: calculations });
            calculations.fetch({reset: true});
            setTimer();

            /* XXX. Reset the input file value to ensure the change event
               will be always triggered */
            $(document).on("click", 'input[name=archive]',
                           function(e) { this.value = null; });
            $(document).on("change", 'input[name=archive]',
                           function(e) {
                               dialog.show('Uploading calculation', true);
                               var input = $(e.target);
                               var form = input.parents('form')[0];

                               $(form).ajaxSubmit(
                                   {
                                    xhr: function() {  // custom xhr to add progress bar management
                                        var myXhr = $.ajaxSettings.xhr();
                                        if(myXhr.upload){ // if upload property exists
                                            myXhr.upload.addEventListener('progress', progressHandlingFunction, false);
                                        }
                                        return myXhr;
                                    },
                                    success: function(data) {
                                        calculations.add(new Calculation(data), {at: 0});
                                    },
                                    error: function(xhr) {
                                        dialog.hide();
                                        var s, out, data = $.parseJSON(xhr.responseText);
                                        var out = "";
                                        var ct = 0;
                                        for (s in data) {
                                            if (data[s] == "")
                                                continue;
                                            out += '
' + data[s] + '
';
                                            ct++;
                                        }
                                        diaerror.show("Calculation not accepted: traceback", out);
                                    }});
                           });

            $(document).on('hidden.bs.modal', 'div[id^=traceback-]',
                           function(e) {
                               setTimer();
                           });

        });
})($, Backbone, _, gem_oq_server_url);
<|endoftext|>


<commit_before>import 'babel-polyfill'
import _ from 'lodash'
import test from 'tape'
import tempWrite from 'temp-write'
import fs from 'fs'
import glob from 'glob'
import tempfile from 'tempfile'
import path from 'path'
// eslint-disable-next-line max-len
import ProductTypeExport, { sortAttributes } from '../../src/product-type-export'
import * as utils from '../utils'

const mockProductTypes = utils.generateTestProductTypes()
const mockAttributes = utils.getMockAttributes(mockProductTypes)

let client
let productTypeExport
let sphereClientConfig
let mockKeys = []

let OUTPUT_FOLDER

async function before () {
  sphereClientConfig = {
    config: await utils.getClientConfig(),
  }
  OUTPUT_FOLDER = tempfile()
  fs.mkdirSync(OUTPUT_FOLDER)

  client = await utils.getClient()

  productTypeExport = new ProductTypeExport({
    sphereClientConfig,
    config: { outputFolder: OUTPUT_FOLDER },
  })

  mockKeys = _.map(mockProductTypes, 'key')
  return utils.deleteAll('productTypes', client)
    .return(mockProductTypes)
    .map(productType => client.productTypes.create(productType))
}

test(`productType export module
  should download all product types into a file`, (t) => {
    t.timeoutAfter(30000) // 30s

    before().then(() => {
      const downloadFolder = tempWrite.sync()
      productTypeExport.downloadProductTypes(downloadFolder)
        .then(() => {
          // check file
          const file = fs.readFileSync(downloadFolder, { encoding: 'utf8' })
          const productTypes = JSON.parse(file)
          const actualKeys = productTypes.map(({ key }) => key)
          const expectedKeys = mockProductTypes.map(({ key }) => key)
          expectedKeys.forEach((key) => {
            t.ok(
              actualKeys.includes(key),
              `ProductType key '${key}' is present in the file`
            )
          })
          t.end()
        })
    }).catch(t.end)
  })

test(`productType export module
  should download only selected product types into a file`, (t) => {
    t.timeoutAfter(15000) // 15s

    before().then(() => {
      const expectedKeys = mockKeys.slice(0, 2)
      const downloadFolder = tempWrite.sync()

      const productTypeExportCompress = new ProductTypeExport({
        sphereClientConfig,
        config: {
          outputFolder: OUTPUT_FOLDER,
          where: `key IN ("${expectedKeys.join('","')}")`,
        },
      })

      productTypeExportCompress.downloadProductTypes(downloadFolder)
        .then(() => {
          // check file
          const file = fs.readFileSync(downloadFolder, { encoding: 'utf8' })
          const productTypes = JSON.parse(file)
          const actualKeys = _.map(productTypes, 'key')

          t.equal(
            actualKeys.length,
            expectedKeys.length,
            'Correct count of exported ProductTypes'
          )

          expectedKeys.forEach((key) => {
            t.ok(
              actualKeys.includes(key),
              `ProductType key '${key}' is present in the file`
            )
          })
          t.end()
        })
    }).catch(t.end)
  })

test(`productType export module
  should read product types one by one from downloaded file`, (t) => {
    t.timeoutAfter(15000) // 15s
    before().then(() => {
      const downloadFolder = tempWrite.sync()
      productTypeExport.downloadProductTypes(downloadFolder)
        .then(() => productTypeExport.collectTypesAndAttributes(downloadFolder))
        .then(({ productTypes, attributes }) => {
          const actualTypes = []
          const actualAttributes = []
          productTypes.on('data', (data) => {
            actualTypes.push(data)
          })
          attributes.on('data', (data) => {
            actualAttributes.push(data)
          })
          productTypes.on('end', () => {
            attributes.on('end', () => {
              mockProductTypes.forEach((
                { name, key, description, attributes: typeAttrs }
              ) => {
                const expectedType = {
                  name,
                  key,
                  description,
                  attributes: typeAttrs.map(attr => attr.name),
                }
                const actualType = actualTypes.find(type => type.name === name)
                t.ok(actualType, `ProductType '${actualType}' is present`)
                t.deepEqual(
                  actualType,
                  expectedType,
                  `downloaded productTypes ${JSON.stringify(actualType)}
              matches mockProductTypes ${JSON.stringify(actualType)}`
                )
                // all of the types attributes should have been collected
                typeAttrs.forEach((typeAttr) => {
                  const collectedAttribute = actualAttributes.some(
                    attr => attr.name === typeAttr.name
                  )
                  t.ok(
                    collectedAttribute,
                    `attribute ${collectedAttribute} is present`
                  )
                })
              })
              t.end()
            })
          })
        })
    })
      .catch(t.end)
  })
test(`productType export module
  should return all unique attributes in the product types`, (t) => {
    t.timeoutAfter(15000) // 15s
    before().then(() => {
      const downloadFolder = tempWrite.sync()
      productTypeExport.downloadProductTypes(downloadFolder)
        .then(() => productTypeExport.collectAttributes(downloadFolder))
        .then(({ attributeNames, attributeKeys }) => {
          // should have collected all attributes
          mockProductTypes.forEach((type) => {
            type.attributes.forEach((attr) => {
              t.ok(
                attributeNames.includes(attr.name),
                `Attribute name ${attr.name} is present`
              )
            })
          })

          const expectedKeys = [
            'type.elementType.name',
            'type.elementType.values.0.key',
            'type.elementType.values.0.label.de',
            'type.elementType.values.0.label.en',
            'type.elementType.values.1.key',
            'type.elementType.values.1.label.de',
            'type.elementType.values.1.label.en',
            'type.values.0.label.de',
            'type.values.0.label.en',
            'type.values.1.label.de',
            'type.values.1.label.en',
            'name',
            'label.en',
            'label.de',
            'isRequired',
            'type.name',
            'type.values.0.key',
            'type.values.0.label',
            'type.values.1.key',
            'type.values.1.label',
            'type.values.2.key',
            'type.values.2.label',
            'type.values.3.key',
            'type.values.3.label',
            'type.values.4.key',
            'type.values.4.label',
            'type.values.5.key',
            'type.values.5.label',
            'type.values.6.key',
            'type.values.6.label',
            'type.values.7.key',
            'type.values.7.label',
            'type.values.8.key',
            'type.values.8.label',
            'type.values.9.key',
            'type.values.9.label',
            'type.values.10.key',
            'type.values.10.label',
            'type.values.11.key',
            'type.values.11.label',
            'attributeConstraint',
            'isSearchable',
            'inputHint',
            'displayGroup',
          ]
          expectedKeys.forEach((attr) => {
            t.ok(
              attributeKeys.includes(attr),
              `Attribute key ${attr} is present exported downloadProductTypes`
            )
          })
          t.end()
        })
    })
      .catch(t.end)
  })

test(`writeProductTypes
  should write to product type to attribute mapping to file`, (t) => {
    t.timeoutAfter(15000) // 15s
    before().then(() => {
      const download = tempWrite.sync()
      const destination = tempWrite.sync()
      productTypeExport.downloadProductTypes(download)
        .then(() => productTypeExport.collectAttributes(download))
        .then(({ attributeNames }) => {
          productTypeExport.attributeNames = attributeNames
          return productTypeExport.collectTypesAndAttributes(download)
        })
        .then(({ productTypes }) =>
          productTypeExport.writeProductTypes(productTypes, destination)
        )
        .then(() => {
          const file = fs.readFileSync(destination, 'utf-8')
          const attributes = productTypeExport.attributeNames.join(',')
          file.split('\n').forEach((row, i) => {
            if (i === 0)
              t.equal(
                row,
                `name,key,description,${attributes}`,
                `exported row ${row} is equal`
              )
            // only testing the header row, the rest can be unit tested
          })
          t.end()
        })
    })
      .catch(t.end)
  })

test(`productType export module
  should write to attributes with all properties to file`, (t) => {
    t.timeoutAfter(15000) // 15s
    before().then(() => {
      const download = tempWrite.sync()
      const destination = tempWrite.sync(null, 'output.csv')
      productTypeExport.downloadProductTypes(download)
        .then(() => productTypeExport.collectAttributes(download))
        .then(({ attributeNames, attributeKeys }) => {
          productTypeExport.attributeNames = sortAttributes(attributeNames)
          productTypeExport.attributeKeys = sortAttributes(attributeKeys)
          return productTypeExport.collectTypesAndAttributes(download)
        })
        .then(({ attributes }) =>
          productTypeExport.writeAttributes(attributes, destination)
        )
        .then(() => {
          const file = fs.readFileSync(destination, 'utf-8').split('\n')
          const header = file[0].split(',')
          const getColIndex = key => header.indexOf(key)
          const getRow = index => file[index].split(',')
          // check if all the product types have been exported
          productTypeExport.attributeNames.reduce((rowIndex, attrName) => {
            const attrDef = mockAttributes.find(mock => mock.name === attrName)
            const {
              name, type, label, attributeConstraint, inputHint, displayGroup,
              isRequired, isSearchable,
            } = attrDef
            const row = getRow(rowIndex)
            t.equal(row[0], name, `row ${name} is equal to name`)
            // if the type is a set, element type of the set needs to appended
            const typeName = type.name === 'set' ? `set:${
              type.elementType.name
            }` : type.name
            t.equal(row[1], typeName, `attribute ${typeName} type is equal`)
            // check for all the localizations of the label
            Object.keys(label).forEach((locale) => {
              t.equal(
                row[getColIndex(`label.${locale}`)],
                label[locale],
                `locale ${label[locale]} is equal`
              )
            })
            t.equal(
              row[getColIndex('attributeConstraint')],
              attributeConstraint,
              `attributeConstraint ${attributeConstraint} is equal`
            )
            if (inputHint)
              t.equal(
                row[getColIndex('textInputHint')],
                inputHint,
                `inputHint ${inputHint} is equal`
              )

            if (displayGroup)
              t.equal(
                row[getColIndex('displayGroup')],
                displayGroup,
                `Display Group ${displayGroup} is equal`
              )

            if (isRequired)
              t.equal(
                row[getColIndex('isRequired')],
                isRequired,
                'isRequired flag is equal'
              )

            if (isSearchable)
              t.equal(
                row[getColIndex('isSearchable')],
                isSearchable,
                `isSearchable flag ${isSearchable} is present`
              )

            let additionalRowsForValues = 0
            // check if the type contains multiple values
            if (
              'values' in type ||
          ('elementType' in type && 'values' in type.elementType)
            ) {
              const values = (type.values || type.elementType.values)
              // store the number of rows needed for the current attributes
              // according to the length of the list of values
              additionalRowsForValues = values.length - 1
              values.forEach((attrVal, index) => {
                const valueRow = getRow(rowIndex + index)
                // check for the enum key
                t.equal(
                  valueRow[getColIndex('enumKey')],
                  attrVal.key,
                  `enumKey ${attrVal.key} is equal`,
                )
                // check for enum label
                if (typeof attrVal.label === 'object')
                  return Object.keys(attrVal.label).forEach((locale) => {
                    t.equal(
                      valueRow[getColIndex(`enumLabel.${locale}`)],
                      attrVal.label[locale],
                      `enumLabel ${attrVal.label[locale]} is equal`,
                    )
                  })

                return t.equal(
                  valueRow[getColIndex('enumLabel')],
                  attrVal.label,
                  `enumLabel ${attrVal.label} is present`,
                )
              })
            }
            return rowIndex + additionalRowsForValues + 1
          }, 1 /* start at 1 to skip the header row */)
          t.end()
        })
    })
      .catch(t.end)
  })

test(`productType export module
  should output a product types and an attributes file`, (t) => {
    t.timeoutAfter(25000) // 25s
    before().then(() => {
      productTypeExport.run()
        .then(() => {
          glob(path.join(OUTPUT_FOLDER, '*'), (err, files) => {
            const expectedFileName1 = 'attributes.csv'
            const expectedFileName2 = 'products-to-attributes.csv'
            t.equal(files.length, 2, 'files length is 2')
            t.equal(
              files[0].split('/').pop(),
              expectedFileName1,
              `file name is ${expectedFileName1}`,
            )
            t.equal(
              files[1].split('/').pop(),
              expectedFileName2,
              `file name is ${expectedFileName2}`,
            )
            t.end()
          })
        })
    })
      .catch(t.end)
  })

test(`productType export module
  should generate a report`, (t) => {
    t.timeoutAfter(15000) // 15s
    before().then(() => {
      productTypeExport.run()
        .then(() => {
          const summary = JSON.parse(productTypeExport.summaryReport())
          t.deepEqual(summary.errors, [], 'there is no error')
          t.deepEqual(
            summary.exported,
            {
              productTypes: mockProductTypes.length,
              attributes: mockAttributes.length,
            },
            'Summary report is valid',
          )
          t.end()
        })
    })
      .catch(t.end)
  })

test(`productType export module
  should list all errors in the report`, (t) => {
    t.timeoutAfter(15000) // 15s
    before().then(() => {
      productTypeExport.downloadProductTypes = () => Promise.reject(
        'some-error'
      )

      return productTypeExport.run()
        .then(() => {
          const summary = JSON.parse(productTypeExport.summaryReport())
          t.deepEqual(summary.errors, ['some-error'], 'Error is present')
          t.deepEqual(
            summary.exported,
            {
              productTypes: 0,
              attributes: 0,
            },
            'Summary report is correct'
          )
          t.end()
        })
    })
      .catch(t.end)
  })

test(`productType export module
  should output a zip file`, (t) => {
    t.timeoutAfter(15000) // 15s
    before().then(() => {
      const productTypeExportCompress = new ProductTypeExport({
        sphereClientConfig,
        config: { outputFolder: OUTPUT_FOLDER, compressOutput: true },
      })
      productTypeExportCompress.run()
        .then(() => {
          glob(path.join(OUTPUT_FOLDER, '*'), (err, files) => {
            t.equal(files.length, 1, 'files length is 1')
            t.equal(
              files[0].split('/').pop(),
              'product-types.zip',
              'file name is product-types.zip'
            )
            t.end()
          })
        })
    })
      .catch(t.end)
  })

test(`productType export module
  should export also productTypes which have no key`, async (t) => {
    t.timeoutAfter(30000) // 30s

    const expectedContent = `name,key,description
test-product-type,,Test product type
`
    try {
      await before()
      await utils.deleteAll('productTypes', client)

      const productType = {
        name: 'test-product-type',
        description: 'Test product type',
        attributes: [],
      }

      await client.productTypes.create(productType)
      await productTypeExport.run()

      const filePath = path.join(OUTPUT_FOLDER, 'products-to-attributes.csv')
      const fileContent = fs.readFileSync(filePath, { encoding: 'utf8' })

      t.equal(
        fileContent,
        expectedContent,
        'Exported row should have an empty key'
      )

      t.end()
    } catch (e) {
      t.end(e)
    }
  })
<commit_msg>refactor(naming): Refactor variable name so it is more understandable<commit_after>import 'babel-polyfill'
import _ from 'lodash'
import test from 'tape'
import tempWrite from 'temp-write'
import fs from 'fs'
import glob from 'glob'
import tempfile from 'tempfile'
import path from 'path'
// eslint-disable-next-line max-len
import ProductTypeExport, { sortAttributes } from '../../src/product-type-export'
import * as utils from '../utils'

const mockProductTypes = utils.generateTestProductTypes()
const mockAttributes = utils.getMockAttributes(mockProductTypes)

let client
let productTypeExport
let sphereClientConfig
let mockKeys = []

let OUTPUT_FOLDER

async function before () {
  sphereClientConfig = {
    config: await utils.getClientConfig(),
  }
  OUTPUT_FOLDER = tempfile()
  fs.mkdirSync(OUTPUT_FOLDER)

  client = await utils.getClient()

  productTypeExport = new ProductTypeExport({
    sphereClientConfig,
    config: { outputFolder: OUTPUT_FOLDER },
  })

  mockKeys = _.map(mockProductTypes, 'key')
  return utils.deleteAll('productTypes', client)
    .return(mockProductTypes)
    .map(productType => client.productTypes.create(productType))
}

test(`productType export module
  should download all product types into a file`, (t) => {
    t.timeoutAfter(30000) // 30s

    before().then(() => {
      const downloadFile = tempWrite.sync()
      productTypeExport.downloadProductTypes(downloadFile)
        .then(() => {
          // check file
          const file = fs.readFileSync(downloadFile, { encoding: 'utf8' })
          const productTypes = JSON.parse(file)
          const actualKeys = productTypes.map(({ key }) => key)
          const expectedKeys = mockProductTypes.map(({ key }) => key)
          expectedKeys.forEach((key) => {
            t.ok(
              actualKeys.includes(key),
              `ProductType key '${key}' is present in the file`
            )
          })
          t.end()
        })
    }).catch(t.end)
  })

test(`productType export module
  should download only selected product types into a file`, (t) => {
    t.timeoutAfter(15000) // 15s

    before().then(() => {
      const expectedKeys = mockKeys.slice(0, 2)
      const downloadFile = tempWrite.sync()

      const productTypeExportCompress = new ProductTypeExport({
        sphereClientConfig,
        config: {
          outputFolder: OUTPUT_FOLDER,
          where: `key IN ("${expectedKeys.join('","')}")`,
        },
      })

      productTypeExportCompress.downloadProductTypes(downloadFile)
        .then(() => {
          // check file
          const file = fs.readFileSync(downloadFile, { encoding: 'utf8' })
          const productTypes = JSON.parse(file)
          const actualKeys = _.map(productTypes, 'key')

          t.equal(
            actualKeys.length,
            expectedKeys.length,
            'Correct count of exported ProductTypes'
          )

          expectedKeys.forEach((key) => {
            t.ok(
              actualKeys.includes(key),
              `ProductType key '${key}' is present in the file`
            )
          })
          t.end()
        })
    }).catch(t.end)
  })

test(`productType export module
  should read product types one by one from downloaded file`, (t) => {
    t.timeoutAfter(15000) // 15s
    before().then(() => {
      const downloadFile = tempWrite.sync()
      productTypeExport.downloadProductTypes(downloadFile)
        .then(() => productTypeExport.collectTypesAndAttributes(downloadFile))
        .then(({ productTypes, attributes }) => {
          const actualTypes = []
          const actualAttributes = []
          productTypes.on('data', (data) => {
            actualTypes.push(data)
          })
          attributes.on('data', (data) => {
            actualAttributes.push(data)
          })
          productTypes.on('end', () => {
            attributes.on('end', () => {
              mockProductTypes.forEach((
                { name, key, description, attributes: typeAttrs }
              ) => {
                const expectedType = {
                  name,
                  key,
                  description,
                  attributes: typeAttrs.map(attr => attr.name),
                }
                const actualType = actualTypes.find(type => type.name === name)
                t.ok(actualType, `ProductType '${actualType}' is present`)
                t.deepEqual(
                  actualType,
                  expectedType,
                  `downloaded productTypes ${JSON.stringify(actualType)}
              matches mockProductTypes ${JSON.stringify(actualType)}`
                )
                // all of the types attributes should have been collected
                typeAttrs.forEach((typeAttr) => {
                  const collectedAttribute = actualAttributes.some(
                    attr => attr.name === typeAttr.name
                  )
                  t.ok(
                    collectedAttribute,
                    `attribute ${collectedAttribute} is present`
                  )
                })
              })
              t.end()
            })
          })
        })
    })
      .catch(t.end)
  })
test(`productType export module
  should return all unique attributes in the product types`, (t) => {
    t.timeoutAfter(15000) // 15s
    before().then(() => {
      const downloadFile = tempWrite.sync()
      productTypeExport.downloadProductTypes(downloadFile)
        .then(() => productTypeExport.collectAttributes(downloadFile))
        .then(({ attributeNames, attributeKeys }) => {
          // should have collected all attributes
          mockProductTypes.forEach((type) => {
            type.attributes.forEach((attr) => {
              t.ok(
                attributeNames.includes(attr.name),
                `Attribute name ${attr.name} is present`
              )
            })
          })

          const expectedKeys = [
            'type.elementType.name',
            'type.elementType.values.0.key',
            'type.elementType.values.0.label.de',
            'type.elementType.values.0.label.en',
            'type.elementType.values.1.key',
            'type.elementType.values.1.label.de',
            'type.elementType.values.1.label.en',
            'type.values.0.label.de',
            'type.values.0.label.en',
            'type.values.1.label.de',
            'type.values.1.label.en',
            'name',
            'label.en',
            'label.de',
            'isRequired',
            'type.name',
            'type.values.0.key',
            'type.values.0.label',
            'type.values.1.key',
            'type.values.1.label',
            'type.values.2.key',
            'type.values.2.label',
            'type.values.3.key',
            'type.values.3.label',
            'type.values.4.key',
            'type.values.4.label',
            'type.values.5.key',
            'type.values.5.label',
            'type.values.6.key',
            'type.values.6.label',
            'type.values.7.key',
            'type.values.7.label',
            'type.values.8.key',
            'type.values.8.label',
            'type.values.9.key',
            'type.values.9.label',
            'type.values.10.key',
            'type.values.10.label',
            'type.values.11.key',
            'type.values.11.label',
            'attributeConstraint',
            'isSearchable',
            'inputHint',
            'displayGroup',
          ]
          expectedKeys.forEach((attr) => {
            t.ok(
              attributeKeys.includes(attr),
              `Attribute key ${attr} is present exported downloadProductTypes`
            )
          })
          t.end()
        })
    })
      .catch(t.end)
  })

test(`writeProductTypes
  should write to product type to attribute mapping to file`, (t) => {
    t.timeoutAfter(15000) // 15s
    before().then(() => {
      const download = tempWrite.sync()
      const destination = tempWrite.sync()
      productTypeExport.downloadProductTypes(download)
        .then(() => productTypeExport.collectAttributes(download))
        .then(({ attributeNames }) => {
          productTypeExport.attributeNames = attributeNames
          return productTypeExport.collectTypesAndAttributes(download)
        })
        .then(({ productTypes }) =>
          productTypeExport.writeProductTypes(productTypes, destination)
        )
        .then(() => {
          const file = fs.readFileSync(destination, 'utf-8')
          const attributes = productTypeExport.attributeNames.join(',')
          file.split('\n').forEach((row, i) => {
            if (i === 0)
              t.equal(
                row,
                `name,key,description,${attributes}`,
                `exported row ${row} is equal`
              )
            // only testing the header row, the rest can be unit tested
          })
          t.end()
        })
    })
      .catch(t.end)
  })

test(`productType export module
  should write to attributes with all properties to file`, (t) => {
    t.timeoutAfter(15000) // 15s
    before().then(() => {
      const download = tempWrite.sync()
      const destination = tempWrite.sync(null, 'output.csv')
      productTypeExport.downloadProductTypes(download)
        .then(() => productTypeExport.collectAttributes(download))
        .then(({ attributeNames, attributeKeys }) => {
          productTypeExport.attributeNames = sortAttributes(attributeNames)
          productTypeExport.attributeKeys = sortAttributes(attributeKeys)
          return productTypeExport.collectTypesAndAttributes(download)
        })
        .then(({ attributes }) =>
          productTypeExport.writeAttributes(attributes, destination)
        )
        .then(() => {
          const file = fs.readFileSync(destination, 'utf-8').split('\n')
          const header = file[0].split(',')
          const getColIndex = key => header.indexOf(key)
          const getRow = index => file[index].split(',')
          // check if all the product types have been exported
          productTypeExport.attributeNames.reduce((rowIndex, attrName) => {
            const attrDef = mockAttributes.find(mock => mock.name === attrName)
            const {
              name, type, label, attributeConstraint, inputHint, displayGroup,
              isRequired, isSearchable,
            } = attrDef
            const row = getRow(rowIndex)
            t.equal(row[0], name, `row ${name} is equal to name`)
            // if the type is a set, element type of the set needs to appended
            const typeName = type.name === 'set' ? `set:${
              type.elementType.name
            }` : type.name
            t.equal(row[1], typeName, `attribute ${typeName} type is equal`)
            // check for all the localizations of the label
            Object.keys(label).forEach((locale) => {
              t.equal(
                row[getColIndex(`label.${locale}`)],
                label[locale],
                `locale ${label[locale]} is equal`
              )
            })
            t.equal(
              row[getColIndex('attributeConstraint')],
              attributeConstraint,
              `attributeConstraint ${attributeConstraint} is equal`
            )
            if (inputHint)
              t.equal(
                row[getColIndex('textInputHint')],
                inputHint,
                `inputHint ${inputHint} is equal`
              )

            if (displayGroup)
              t.equal(
                row[getColIndex('displayGroup')],
                displayGroup,
                `Display Group ${displayGroup} is equal`
              )

            if (isRequired)
              t.equal(
                row[getColIndex('isRequired')],
                isRequired,
                'isRequired flag is equal'
              )

            if (isSearchable)
              t.equal(
                row[getColIndex('isSearchable')],
                isSearchable,
                `isSearchable flag ${isSearchable} is present`
              )

            let additionalRowsForValues = 0
            // check if the type contains multiple values
            if (
              'values' in type ||
          ('elementType' in type && 'values' in type.elementType)
            ) {
              const values = (type.values || type.elementType.values)
              // store the number of rows needed for the current attributes
              // according to the length of the list of values
              additionalRowsForValues = values.length - 1
              values.forEach((attrVal, index) => {
                const valueRow = getRow(rowIndex + index)
                // check for the enum key
                t.equal(
                  valueRow[getColIndex('enumKey')],
                  attrVal.key,
                  `enumKey ${attrVal.key} is equal`,
                )
                // check for enum label
                if (typeof attrVal.label === 'object')
                  return Object.keys(attrVal.label).forEach((locale) => {
                    t.equal(
                      valueRow[getColIndex(`enumLabel.${locale}`)],
                      attrVal.label[locale],
                      `enumLabel ${attrVal.label[locale]} is equal`,
                    )
                  })

                return t.equal(
                  valueRow[getColIndex('enumLabel')],
                  attrVal.label,
                  `enumLabel ${attrVal.label} is present`,
                )
              })
            }
            return rowIndex + additionalRowsForValues + 1
          }, 1 /* start at 1 to skip the header row */)
          t.end()
        })
    })
      .catch(t.end)
  })

test(`productType export module
  should output a product types and an attributes file`, (t) => {
    t.timeoutAfter(25000) // 25s
    before().then(() => {
      productTypeExport.run()
        .then(() => {
          glob(path.join(OUTPUT_FOLDER, '*'), (err, files) => {
            const expectedFileName1 = 'attributes.csv'
            const expectedFileName2 = 'products-to-attributes.csv'
            t.equal(files.length, 2, 'files length is 2')
            t.equal(
              files[0].split('/').pop(),
              expectedFileName1,
              `file name is ${expectedFileName1}`,
            )
            t.equal(
              files[1].split('/').pop(),
              expectedFileName2,
              `file name is ${expectedFileName2}`,
            )
            t.end()
          })
        })
    })
      .catch(t.end)
  })

test(`productType export module
  should generate a report`, (t) => {
    t.timeoutAfter(15000) // 15s
    before().then(() => {
      productTypeExport.run()
        .then(() => {
          const summary = JSON.parse(productTypeExport.summaryReport())
          t.deepEqual(summary.errors, [], 'there is no error')
          t.deepEqual(
            summary.exported,
            {
              productTypes: mockProductTypes.length,
              attributes: mockAttributes.length,
            },
            'Summary report is valid',
          )
          t.end()
        })
    })
      .catch(t.end)
  })

test(`productType export module
  should list all errors in the report`, (t) => {
    t.timeoutAfter(15000) // 15s
    before().then(() => {
      productTypeExport.downloadProductTypes = () => Promise.reject(
        'some-error'
      )

      return productTypeExport.run()
        .then(() => {
          const summary = JSON.parse(productTypeExport.summaryReport())
          t.deepEqual(summary.errors, ['some-error'], 'Error is present')
          t.deepEqual(
            summary.exported,
            {
              productTypes: 0,
              attributes: 0,
            },
            'Summary report is correct'
          )
          t.end()
        })
    })
      .catch(t.end)
  })

test(`productType export module
  should output a zip file`, (t) => {
    t.timeoutAfter(15000) // 15s
    before().then(() => {
      const productTypeExportCompress = new ProductTypeExport({
        sphereClientConfig,
        config: { outputFolder: OUTPUT_FOLDER, compressOutput: true },
      })
      productTypeExportCompress.run()
        .then(() => {
          glob(path.join(OUTPUT_FOLDER, '*'), (err, files) => {
            t.equal(files.length, 1, 'files length is 1')
            t.equal(
              files[0].split('/').pop(),
              'product-types.zip',
              'file name is product-types.zip'
            )
            t.end()
          })
        })
    })
      .catch(t.end)
  })

test(`productType export module
  should export also productTypes which have no key`, async (t) => {
    t.timeoutAfter(30000) // 30s

    const expectedContent = `name,key,description
test-product-type,,Test product type
`
    try {
      await before()
      await utils.deleteAll('productTypes', client)

      const productType = {
        name: 'test-product-type',
        description: 'Test product type',
        attributes: [],
      }

      await client.productTypes.create(productType)
      await productTypeExport.run()

      const filePath = path.join(OUTPUT_FOLDER, 'products-to-attributes.csv')
      const fileContent = fs.readFileSync(filePath, { encoding: 'utf8' })

      t.equal(
        fileContent,
        expectedContent,
        'Exported row should have an empty key'
      )

      t.end()
    } catch (e) {
      t.end(e)
    }
  })
<|endoftext|>


<commit_before>"use strict";

var $ = require("jquery"),
    _ = require("lodash"),
    L = require('leaflet'),
    urlLib = require('url'),
    Search = require("treemap/search"),
    format = require('util').format,

    MAX_ZOOM_OPTION = exports.MAX_ZOOM_OPTION = {maxZoom: 21},
    // Min zoom level for detail layers
    MIN_ZOOM_OPTION = exports.MIN_ZOOM_OPTION = {minZoom: 15},

    BASE_LAYER_OPTION = exports.BASE_LAYER_OPTION = {zIndex: 0},
    BOUNDARY_LAYER_OPTION = {zIndex: 1},
    CUSTOM_LAYER_OPTION = {zIndex: 2},
    FEATURE_LAYER_OPTION = {zIndex: 3};

////////////////////////////////////////////////
// public functions
////////////////////////////////////////////////

exports.createBoundariesTileLayer = function (config) {
    // we will never update boundaries based on a revision, so
    // safe to use the same url permanently.
    var revToUrl = getUrlMaker(config, 'treemap_boundary', 'png'),
        url = revToUrl(config.instance.geoRevHash),
        options = _.extend({}, MAX_ZOOM_OPTION, MIN_ZOOM_OPTION, BOUNDARY_LAYER_OPTION);
    return L.tileLayer(url, options);
};

exports.createPlotTileLayer = function (config) {
    var options = _.extend({}, MAX_ZOOM_OPTION, FEATURE_LAYER_OPTION);
    return filterableLayer(
        'treemap_mapfeature', 'png', config, options);
};

exports.createPolygonTileLayer = function (config) {
    var options = _.extend({}, MAX_ZOOM_OPTION, MIN_ZOOM_OPTION, FEATURE_LAYER_OPTION);
    return filterableLayer(
        'stormwater_polygonalmapfeature', 'png', config, options);
};

exports.createPlotUTFLayer = function (config) {
    var layer,
        revToUrl = getUrlMaker(
            config, 'treemap_mapfeature', 'grid.json'),
        url = revToUrl(config.instance.geoRevHash),
        options = _.extend({resolution: 4}, MAX_ZOOM_OPTION, FEATURE_LAYER_OPTION);

    // Need to use JSONP on on browsers that do not support CORS (IE9)
    // Only applies to plot layer because only UtfGrid is using XmlHttpRequest
    // for cross-site requests
    if (!$.support.cors) {
        url += '&callback={cb}';
        options.useJsonP = true;
    } else {
        options.useJsonP = false;
    }

    layer = new L.UtfGrid(url, options);

    layer.setHashes = function (hashes) {
        layer.setUrl(revToUrl(hashes.geoRevHash));
    };

    // TODO: I don't think this is used anywhere. can we delete it?
    layer.setUrl = function(url) {
        // Poke some internals
        // Update the url
        layer._url = url;
        // bust the cache
        layer._cache = {};

        // Trigger update
        layer._update();
    };

    return layer;
};

exports.createCustomLayer = function(layerInfo, config) {
    if (layerInfo.type === 'tile') {
        var options = _.extend({}, CUSTOM_LAYER_OPTION);
        options.maxZoom = layerInfo.maxZoom || MAX_ZOOM_OPTION.maxZoom;
        if (layerInfo.maxNativeZoom) {
            // NOTE: this won't work until we upgrade to Leaflet > 0.7
            options.maxNativeZoom = layerInfo.maxNativeZoom;
        }
        if (layerInfo.opacity) {
            options.opacity = layerInfo.opacity;
        }
        return L.tileLayer(layerInfo.url, options);
    }
};

////////////////////////////////////////////////
// internal functions
////////////////////////////////////////////////

function getUrlMaker(config, table, extension) {
    return function revToUrl(rev) {
        return format(
            '%s/tile/%s/database/otm/table/%s/{z}/{x}/{y}.%s%s',
            config.tileHost || '', rev, table, extension,
            urlLib.format({query: {
                'instance_id': config.instance.id,
                'restrict': JSON.stringify(config.instance.mapFeatureTypes)
            }}));
    };
}

function filterableLayer (table, extension, config, layerOptions) {
    var revToUrl = getUrlMaker(config, table, extension),
        noSearchUrl = revToUrl(config.instance.geoRevHash),
        searchBaseUrl = revToUrl(config.instance.universalRevHash),
        layer = L.tileLayer(noSearchUrl, layerOptions);

    layer.setHashes = function(response) {
        noSearchUrl = revToUrl(response.geoRevHash);
        searchBaseUrl = revToUrl(response.universalRevHash);

        // TODO: this is cryptic. This method gets called even when a
        // search is activated, so setting the URL to the noSearchUrl
        // would seem to clear the search, only it doesn't. I guess
        // setFilter is being called again, afterward, somewhere else.
        // unfortunately, this is necessary because without it new
        // tiles don't get requested, and we haven't closed over filters
        // so we can't simply make another call to `setFilter`.
        layer.setUrl(noSearchUrl);
    };

    layer.setFilter = function(filters) {
        var fullUrl;
        if (Search.isEmpty(filters)) {
            fullUrl = noSearchUrl;
        } else {
            var query = Search.makeQueryStringFromFilters(config, filters);
            var suffix = query ? '&' + query : '';
            fullUrl = searchBaseUrl + suffix;
        }
        layer.setUrl(fullUrl);
    };

    return layer;
}
<commit_msg>Reapply search filters after quick edit<commit_after>"use strict";

var $ = require("jquery"),
    _ = require("lodash"),
    L = require('leaflet'),
    urlLib = require('url'),
    Search = require("treemap/search"),
    format = require('util').format,

    MAX_ZOOM_OPTION = exports.MAX_ZOOM_OPTION = {maxZoom: 21},
    // Min zoom level for detail layers
    MIN_ZOOM_OPTION = exports.MIN_ZOOM_OPTION = {minZoom: 15},

    BASE_LAYER_OPTION = exports.BASE_LAYER_OPTION = {zIndex: 0},
    BOUNDARY_LAYER_OPTION = {zIndex: 1},
    CUSTOM_LAYER_OPTION = {zIndex: 2},
    FEATURE_LAYER_OPTION = {zIndex: 3};

////////////////////////////////////////////////
// public functions
////////////////////////////////////////////////

exports.createBoundariesTileLayer = function (config) {
    // we will never update boundaries based on a revision, so
    // safe to use the same url permanently.
    var revToUrl = getUrlMaker(config, 'treemap_boundary', 'png'),
        url = revToUrl(config.instance.geoRevHash),
        options = _.extend({}, MAX_ZOOM_OPTION, MIN_ZOOM_OPTION, BOUNDARY_LAYER_OPTION);
    return L.tileLayer(url, options);
};

exports.createPlotTileLayer = function (config) {
    var options = _.extend({}, MAX_ZOOM_OPTION, FEATURE_LAYER_OPTION);
    return filterableLayer(
        'treemap_mapfeature', 'png', config, options);
};

exports.createPolygonTileLayer = function (config) {
    var options = _.extend({}, MAX_ZOOM_OPTION, MIN_ZOOM_OPTION, FEATURE_LAYER_OPTION);
    return filterableLayer(
        'stormwater_polygonalmapfeature', 'png', config, options);
};

exports.createPlotUTFLayer = function (config) {
    var layer,
        revToUrl = getUrlMaker(
            config, 'treemap_mapfeature', 'grid.json'),
        url = revToUrl(config.instance.geoRevHash),
        options = _.extend({resolution: 4}, MAX_ZOOM_OPTION, FEATURE_LAYER_OPTION);

    // Need to use JSONP on on browsers that do not support CORS (IE9)
    // Only applies to plot layer because only UtfGrid is using XmlHttpRequest
    // for cross-site requests
    if (!$.support.cors) {
        url += '&callback={cb}';
        options.useJsonP = true;
    } else {
        options.useJsonP = false;
    }

    layer = new L.UtfGrid(url, options);

    layer.setHashes = function (hashes) {
        layer.setUrl(revToUrl(hashes.geoRevHash));
    };

    // TODO: I don't think this is used anywhere. can we delete it?
    layer.setUrl = function(url) {
        // Poke some internals
        // Update the url
        layer._url = url;
        // bust the cache
        layer._cache = {};

        // Trigger update
        layer._update();
    };

    return layer;
};

exports.createCustomLayer = function(layerInfo, config) {
    if (layerInfo.type === 'tile') {
        var options = _.extend({}, CUSTOM_LAYER_OPTION);
        options.maxZoom = layerInfo.maxZoom || MAX_ZOOM_OPTION.maxZoom;
        if (layerInfo.maxNativeZoom) {
            // NOTE: this won't work until we upgrade to Leaflet > 0.7
            options.maxNativeZoom = layerInfo.maxNativeZoom;
        }
        if (layerInfo.opacity) {
            options.opacity = layerInfo.opacity;
        }
        return L.tileLayer(layerInfo.url, options);
    }
};

////////////////////////////////////////////////
// internal functions
////////////////////////////////////////////////

function getUrlMaker(config, table, extension) {
    return function revToUrl(rev) {
        return format(
            '%s/tile/%s/database/otm/table/%s/{z}/{x}/{y}.%s%s',
            config.tileHost || '', rev, table, extension,
            urlLib.format({query: {
                'instance_id': config.instance.id,
                'restrict': JSON.stringify(config.instance.mapFeatureTypes)
            }}));
    };
}

// Combine base from `newBaseUrl` with querystring from `url`.
//
// The purpose of this is to generate a new search url for tile layers
// from an updated base url (because of revision hash changes), while
// also preserving the current querystring filters.
//
// Ref: https://github.com/OpenTreeMap/otm-core/issues/2437
function updateBaseUrl(url, newBaseUrl) {
    var oldQueryString = url.split('?')[1],
        newBase = newBaseUrl.split('?')[0];
    return newBase + '?' + oldQueryString;
}

function filterableLayer (table, extension, config, layerOptions) {
    var revToUrl = getUrlMaker(config, table, extension),
        noSearchUrl = revToUrl(config.instance.geoRevHash),
        searchBaseUrl = revToUrl(config.instance.universalRevHash),
        layer = L.tileLayer(noSearchUrl, layerOptions);

    layer.setHashes = function(response) {
        noSearchUrl = revToUrl(response.geoRevHash);
        searchBaseUrl = revToUrl(response.universalRevHash);

        // Update tiles to reflect content changes.
        var newLayerUrl = updateBaseUrl(layer._url, searchBaseUrl);
        layer.setUrl(newLayerUrl);
    };

    layer.setFilter = function(filters) {
        var fullUrl;
        if (Search.isEmpty(filters)) {
            fullUrl = noSearchUrl;
        } else {
            var query = Search.makeQueryStringFromFilters(config, filters);
            var suffix = query ? '&' + query : '';
            fullUrl = searchBaseUrl + suffix;
        }
        layer.setUrl(fullUrl);
    };

    return layer;
}
<|endoftext|>


<commit_before>var O = require('./output.js');

module.exports.output = O.makeOutput(
  'output-crime-burst-central-london', // name

  'monitor the crime stats from central London. Trigger when there is a burst ' +
  'of 10 percents over the 6 months preceding the time of the check.', // description

  ['police-uk'], // array containing the names of datasources used by output

  function check(datasourcesDict, callback) {
    // TODO in fact, should choose the right datasources from the given array
    // (return false or throw if needed sources are absent), query them for the
    // needed data (over the good range of time, with the right lat/lng etc.),
    // make stats and return true/false based on the outcome.

    // check that all the datasources that we need are here.
    for (var i = 0; i < this.sources.length; i++) {
      if (!(sources[i] in datasourcesDict)) {
        callback("Datasource '" + sources[i] + "' not found in " + datasourcesDict);
        return;
      }
    }

    var police_uk = datasourcesDict['police-uk'];
    var to = new Date(Date.now());
    var from = new Date(to);
    from.setMonth(from.getMonth() - 5); // query data for the past 6 months.

    // TODO that's just for the example. There should a nice way to input these
    // from a GUI.
    var lat = 51.529251355189885;
    var lng = -0.1490020751953125;

    police_uk.getData(from, to, lat, lng, processStats);

    function processStats(crimeStats) {
      // compute the total amount of crime for each crime
      var totCrimes = [];
      for (var i = 0; i < crimeStats.length; i++) {
        var totalCrime = 0;
        var types = Object.keys(crimeStats[i]);
        for (var t = 0; t < types.length; t++) {
          totalCrime += crimeStats[i][t];
        }
        totCrimes.push(totalCrime);
      }

      // compute the average of crimes per month
      var avg = 0;
      for (var m = 0; m < totCrimes.length; m++) {
        avg += totCrimes[m];
      }
      avg /= totCrimes.length;

      var dif = (totCrimes[totCrimes.length - 1] / avg * 100) - 100;

      // TODO that 10 percents also are here for the sake of the example and
      // should be parametrized somehow.
      if (Math.abs(dif) >= 10) {
        callback(undefined, dif);
      }
    }
  }
);

<commit_msg>forgot a this<commit_after>var O = require('./output.js');

module.exports.output = O.makeOutput(
  'output-crime-burst-central-london', // name

  'monitor the crime stats from central London. Trigger when there is a burst ' +
  'of 10 percents over the 6 months preceding the time of the check.', // description

  ['police-uk'], // array containing the names of datasources used by output

  function check(datasourcesDict, callback) {
    // TODO in fact, should choose the right datasources from the given array
    // (return false or throw if needed sources are absent), query them for the
    // needed data (over the good range of time, with the right lat/lng etc.),
    // make stats and return true/false based on the outcome.

    // check that all the datasources that we need are here.
    for (var i = 0; i < this.sources.length; i++) {
      if (!(this.sources[i] in datasourcesDict)) {
        callback("Datasource '" + sources[i] + "' not found in " + datasourcesDict);
        return;
      }
    }

    var police_uk = datasourcesDict['police-uk'];
    var to = new Date(Date.now());
    var from = new Date(to);
    from.setMonth(from.getMonth() - 5); // query data for the past 6 months.

    // TODO that's just for the example. There should a nice way to input these
    // from a GUI.
    var lat = 51.529251355189885;
    var lng = -0.1490020751953125;

    police_uk.getData(from, to, lat, lng, processStats);

    function processStats(crimeStats) {
      // compute the total amount of crime for each crime
      var totCrimes = [];
      for (var i = 0; i < crimeStats.length; i++) {
        var totalCrime = 0;
        var types = Object.keys(crimeStats[i]);
        for (var t = 0; t < types.length; t++) {
          totalCrime += crimeStats[i][t];
        }
        totCrimes.push(totalCrime);
      }

      // compute the average of crimes per month
      var avg = 0;
      for (var m = 0; m < totCrimes.length; m++) {
        avg += totCrimes[m];
      }
      avg /= totCrimes.length;

      var dif = (totCrimes[totCrimes.length - 1] / avg * 100) - 100;

      // TODO that 10 percents also are here for the sake of the example and
      // should be parametrized somehow.
      if (Math.abs(dif) >= 10) {
        callback(undefined, dif);
      }
    }
  }
);

<|endoftext|>


<commit_before>/* eslint-env node */
'use strict';

module.exports = {
  allowedVersions: {
    'ember-in-element-polyfill': '*',
    'ember-get-config': '0.2.4 || 0.3.0',
    '@embroider/util': '*',
    '@ember/test-waiters': '2.4.5 || 3.0.0',
  },
};
<commit_msg>Allow dependency lint to float for ember-get-config<commit_after>/* eslint-env node */
'use strict';

module.exports = {
  allowedVersions: {
    'ember-in-element-polyfill': '*',
    'ember-get-config': '0.3.0 || 0.4.0',
    '@embroider/util': '*',
    '@ember/test-waiters': '2.4.5 || 3.0.0',
  },
};
<|endoftext|>


<commit_before>module.exports = getServer

var defaults = require('lodash/defaultsDeep')
var Hapi = require('hapi')
var nock = require('nock')
var PouchDB = require('pouchdb').defaults({
  prefix: 'http://localhost:5984/'
})

var hapiAccount = require('../../../plugin')

function getServer (options, callback) {
  if (!callback) {
    callback = options
    options = {}
  }
  var server = new Hapi.Server({
    // easy debug!
    // debug: {
    //   request: ['error'],
    //   log: ['error']
    // }
  })
  server.connection({ host: 'localhost', port: 80 })

  nock('http://localhost:5984')
    // PouchDB sends a request to see if db exists
    .get('/_users/')
    .reply(200, {})
    // mocks for bootstrapping design dock
    .put('/_users')
    .reply(201, {})
    .put('/_users/_design/byId')
    .reply(201)

  server.register({
    register: hapiAccount,
    options: defaults({
      PouchDB: PouchDB,
      secret: 'secret',
      admins: {
        // -<password scheme>-<derived key>,<salt>,<iterations>
        // password is "secret"
        admin: '-pbkdf2-a2ca9d3ee921c26d2e9d61e03a0801b11b8725c6,1081b31861bd1e91611341da16c11c16a12c13718d1f712e,10'
      },
      notifications: {}
    }, options)
  }, function (error) {
    callback(error, server)
  })
}
<commit_msg>test: adaptions for @hoodie/account-server-api@3<commit_after>module.exports = getServer

var defaults = require('lodash/defaultsDeep')
var Hapi = require('hapi')
var nock = require('nock')
var PouchDB = require('pouchdb').defaults({
  prefix: 'http://localhost:5984/'
})

var hapiAccount = require('../../../plugin')

function getServer (options, callback) {
  if (!callback) {
    callback = options
    options = {}
  }
  var server = new Hapi.Server({
    // easy debug!
    // debug: {
    //   request: ['error'],
    //   log: ['error']
    // }
  })
  server.connection({ host: 'localhost', port: 80 })

  nock('http://localhost:5984')
    // PouchDB sends a request to see if db exists
    .get('/_users/')
    .query({})
    .reply(200, {db_name: '_users'})
    // design docs
    .post('/_users/_bulk_docs')
    .reply(201, [
      {
        ok: true,
        id: '_design/byId',
        rev: '1-234'
      },
      {
        ok: true,
        id: '_design/byToken',
        rev: '1-234'
      }
    ])

  server.register({
    register: hapiAccount,
    options: defaults({
      PouchDB: PouchDB,
      secret: 'secret',
      admins: {
        // -<password scheme>-<derived key>,<salt>,<iterations>
        // password is "secret"
        admin: '-pbkdf2-a2ca9d3ee921c26d2e9d61e03a0801b11b8725c6,1081b31861bd1e91611341da16c11c16a12c13718d1f712e,10'
      },
      notifications: {}
    }, options)
  }, function (error) {
    callback(error, server)
  })
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// @ts-nocheck
// TODO(crbug.com/1011811): Enable TypeScript compiler checks

import * as Root from '../root/root.js';

/**
 * @interface
 */
// eslint-disable-next-line
const LighthousePort = class {
  /**
   * @param {!string} eventName, 'message', 'close'
   * @param {function(string|undefined)} cb
   */
  on(eventName, cb) {
  }

  /**
   * @param {string} message
   */
  send(message) {
  }

  close() {
  }
};

/**
 * @implements {LighthousePort}
 * @implements {Service}
 * @unrestricted
 */
class LighthouseService {  // eslint-disable-line
  /**
   * @override
   * @param {function(string)}
   */
  setNotify(notify) {
    this._notify = notify;
  }

  /**
   * @return {!Promise<!ReportRenderer.RunnerResult>}
   */
  start(params) {
    if (Root.Runtime.Runtime.queryParam('isUnderTest')) {
      this._disableLoggingForTest();
      params.flags.maxWaitForLoad = 2 * 1000;
    }

    self.listenForStatus(message => {
      this.statusUpdate(message[1]);
    });

    return this.fetchLocaleData(params.locales)
        .then(locale => {
          const flags = params.flags;
          flags.logLevel = flags.logLevel || 'info';
          flags.channel = 'devtools';
          flags.locale = locale;

          const connection = self.setUpWorkerConnection(this);
          const config = self.createConfig(params.categoryIDs, flags.emulatedFormFactor);
          const url = params.url;

          return self.runLighthouse(url, flags, config, connection);
        })
        .then(/** @type {!ReportRenderer.RunnerResult} */ result => {
          // Keep all artifacts on the result, no trimming
          return result;
        })
        .catch(err => ({
                 fatal: true,
                 message: err.message,
                 stack: err.stack,
               }));
  }

  /**
   * @param {string[]} locales
   * @return {string|undefined}
   */
  async fetchLocaleData(locales) {
    const locale = self.lookupLocale(locales);

    // If the locale is en-US, no need to fetch locale data.
    if (locale === 'en-US' || locale === 'en') {
      return;
    }

    // Try to load the locale data.
    const localeResource = `../third_party/lighthouse/locales/${locale}.json`;
    try {
      const module = Root.Runtime.Runtime.instance().module('lighthouse_worker');
      const localeDataText = await module.fetchResource(localeResource);
      const localeData = JSON.parse(localeDataText);
      self.registerLocaleData(locale, localeData);
      return locale;
    } catch (_) {
    }

    // If no locale was found, or fetching locale data fails, Lighthouse will use `en-US` by default.
  }

  /**
   * @return {!Promise}
   */
  stop() {
    this.close();
    return Promise.resolve();
  }

  /**
   * @param {!Object=} params
   * @return {!Promise}
   */
  dispatchProtocolMessage(params) {
    this._onMessage(params['message']);
    return Promise.resolve();
  }

  /**
   * @override
   * @return {!Promise}
   */
  dispose() {
    return Promise.resolve();
  }

  /**
   * @param {string} message
   */
  statusUpdate(message) {
    this._notify('statusUpdate', {message: message});
  }

  /**
   * @param {string} message
   */
  send(message) {
    this._notify('sendProtocolMessage', {message: message});
  }

  close() {
  }

  /**
   * @param {string} eventName
   * @param {function(string|undefined)} cb
   */
  on(eventName, cb) {
    if (eventName === 'message') {
      this._onMessage = cb;
    }
    if (eventName === 'close') {
      this._onClose = cb;
    }
  }

  _disableLoggingForTest() {
    console.log = () => undefined;  // eslint-disable-line no-console
  }
}

// Make lighthouse and traceviewer happy.
globalThis.global = self;
globalThis.global.isVinn = true;
globalThis.global.document = {};
globalThis.global.document.documentElement = {};
globalThis.global.document.documentElement.style = {
  WebkitAppearance: 'WebkitAppearance'
};
globalThis.global.LighthouseService = LighthouseService;
<commit_msg>Typecheck lighthouse_worker/LighthouseService.js with TypeScript<commit_after>// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import * as Root from '../root/root.js';

/**
 * @interface
 */
class LighthousePort {  // eslint-disable-line
  /**
   * @param {!string} eventName, 'message', 'close'
   * @param {function(string|undefined):void} cb
   */
  on(eventName, cb) {
  }

  /**
   * @param {string} message
   */
  send(message) {
  }

  close() {
  }
}

/**
 * @implements {LighthousePort}
 * @unrestricted
 */
class LighthouseService {  // eslint-disable-line
  /**
   * @override
   * @param {function(string,!Object=):void} notify
   */
  setNotify(notify) {
    this._notify = notify;
  }

  /**
   * @param {*} params
   * @return {!Promise<*>}
   */
  start(params) {
    if (Root.Runtime.Runtime.queryParam('isUnderTest')) {
      this._disableLoggingForTest();
      params.flags.maxWaitForLoad = 2 * 1000;
    }

    // @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
    self.listenForStatus(message => {
      this.statusUpdate(message[1]);
    });

    return this.fetchLocaleData(params.locales)
        .then(locale => {
          const flags = params.flags;
          flags.logLevel = flags.logLevel || 'info';
          flags.channel = 'devtools';
          flags.locale = locale;

          // @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
          const connection = self.setUpWorkerConnection(this);
          // @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
          const config = self.createConfig(params.categoryIDs, flags.emulatedFormFactor);
          const url = params.url;

          // @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
          return self.runLighthouse(url, flags, config, connection);
        })
        .then(result => {
          // Keep all artifacts on the result, no trimming
          return result;
        })
        .catch(err => ({
                 fatal: true,
                 message: err.message,
                 stack: err.stack,
               }));
  }

  /**
   * @param {string[]} locales
   * @return {!Promise<(string|undefined)>}
   */
  async fetchLocaleData(locales) {
    // @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
    const locale = self.lookupLocale(locales);

    // If the locale is en-US, no need to fetch locale data.
    if (locale === 'en-US' || locale === 'en') {
      return undefined;
    }

    // Try to load the locale data.
    const localeResource = `../third_party/lighthouse/locales/${locale}.json`;
    try {
      const module = Root.Runtime.Runtime.instance().module('lighthouse_worker');
      const localeDataText = await module.fetchResource(localeResource);
      const localeData = JSON.parse(localeDataText);
      // @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
      self.registerLocaleData(locale, localeData);
      return locale;
    } catch (_) {
    }

    // If no locale was found, or fetching locale data fails, Lighthouse will use `en-US` by default.
    return undefined;
  }

  async stop() {
    this.close();
  }

  /**
   * @param {*} params
   */
  async dispatchProtocolMessage(params) {
    if (this._onMessage) {
      this._onMessage(params['message']);
    }
  }

  /**
   * @override
   */
  async dispose() {
  }

  /**
   * @param {string} message
   */
  statusUpdate(message) {
    if (this._notify) {
      this._notify('statusUpdate', {message});
    }
  }

  /**
   * @param {string} message
   */
  send(message) {
    if (this._notify) {
      this._notify('sendProtocolMessage', {message});
    }
  }

  close() {
  }

  /**
   * @param {string} eventName
   * @param {function(string|undefined):void} cb
   */
  on(eventName, cb) {
    if (eventName === 'message') {
      this._onMessage = cb;
    }
    if (eventName === 'close') {
      this._onClose = cb;
    }
  }

  _disableLoggingForTest() {
    console.log = () => undefined;  // eslint-disable-line no-console
  }
}

// Make lighthouse and traceviewer happy.
globalThis.global = self;
// @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
globalThis.global.isVinn = true;
// @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
globalThis.global.document = {};
// @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
globalThis.global.document.documentElement = {};
// @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
globalThis.global.document.documentElement.style = {
  WebkitAppearance: 'WebkitAppearance'
};
// @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
globalThis.global.LighthouseService = LighthouseService;
<|endoftext|>