<commit_before>'use strict';

let angular = require('angular');

module.exports = angular
  .module('spinnaker.core.widget.spelAutocomplete', [
    require('./jsonListBuilder'),
    require('../../delivery/service/execution.service'),
  ])
  .factory('spelAutocomplete', function($q, jsonListBuilder, executionService) {
    let brackets = [{open: '(', close: ')'}, {open: '[', close: ']'}, ];
    let quotes = [{open: '\'', close: '\'' }, {open: '\"', close: '\"' }];
    let helperFunctions = ['alphanumerical', 'readJson', 'fromUrl', 'jsonFromUrl', 'judgment', 'stage', 'toBoolean', 'toFloat', 'toInt', 'toJson'];
    let helperParams = ['execution', 'parameters', 'trigger', 'scmInfo', 'scmInfo.sha1', 'scmInfo.branch', 'deployedServerGroups'];
    let codedHelperParams = helperParams.map((param) => {
      return {name: param, type: 'param'};
    });

    let textcompleteConfig = [
      {
        id: 'SpEL wrapper',
        match: /\$(\w*)$/,
        search: function(item, callback) {
          callback(['${...}']);
        },
        index: 1,
        replace: function replace() {
          return ['${ ', ' }'];
        }
      },
      {
        id: 'match quotes',
        match: /("|')(\w*)$/,
        index: 1,
        search: function(term, callback) {
          let found = quotes.filter((quote) => {
            if (quote.open.indexOf(term) === 0) {
              return quote;
            }
          });
          callback(found);
        },
        template: function() {
          return `'...'`;
        },
        replace: function replace() {
          return [`'` , `'`];
        }
      },
      {
        id: 'match brackets',
        match: /(\[|\(|')(\w*)$/,
        index: 1,
        search: function(term, callback) {
          let found = brackets.filter((bracket) => {
            if (bracket.open.indexOf(term) === 0) {
              return bracket;
            }
          });
          callback(found);
        },
        template: function(value) {
          return `${value.open}...${value.close}`;
        },
        replace: function replace(bracket) {
          return [`${bracket.open}  ` , `  ${bracket.close}`];
        }
      },
    ];

    let paramInList = (checkParam) => {
      return (testParam) => checkParam.name === testParam.name;
    };

    let addToTextcompleteConfig = (configList = [], textcompleteConfig) => {
      let textcompleteConfigCopy = textcompleteConfig.slice(0);

      configList.forEach((newConfig) => {
        if(textcompleteConfig.filter( (config) => config.id === newConfig.id ).length === 0) {
          return textcompleteConfigCopy.push(newConfig);
        }
      });

      return textcompleteConfigCopy;
    };

    const listSearchFn = (list) => {
      return (term, callback) => {
        callback(list.filter((item) => {
          if (item.leaf.indexOf(term) > -1) {
            return item;
          }
        }));
      };
    };

    const leafTemplateFn = (stage) => {
      return `${stage.leaf} <span class="glyphicon glyphicon-triangle-right spel-value-separator"></span> <span class="marker value">${stage.value}</span>`;
    };

    let addExecutionForAutocomplete = (pipeline, textcompleteConfig) => {
      if (pipeline) {
        let executionList = jsonListBuilder.convertJsonKeysToBracketedList(pipeline, ['task']);

        let configList = [{
          id: `execution: ${pipeline.id}`,
          match: /execution(\w*|\s*)$/,
          index: 1,
          search: listSearchFn(executionList),
          template: leafTemplateFn,
          replace: (value) => {
            return `execution${value.leaf}`;
          }
        }];
        return addToTextcompleteConfig(configList, textcompleteConfig);
      }
      return textcompleteConfig;
    };

    let addDeloyedServerGroupsForAutoComplete = (pipeline, textcompleteConfig) => {
      if (pipeline && pipeline.context && pipeline.context.deploymentDetails) {
        let deployList = jsonListBuilder.convertJsonKeysToBracketedList(pipeline.context.deploymentDetails);

        let configList = [{
          id: `deploymentServerGroups: ${pipeline.id}`,
          match: /deployedServerGroups(\w*|\s*)$/,
          index: 1,
          search: listSearchFn(deployList),
          template: leafTemplateFn,
          replace: (value) => {
            return `deployedServerGroups${value.leaf}`;
          }
        }];
        return addToTextcompleteConfig(configList, textcompleteConfig);
      }
      return textcompleteConfig;
    };

    let addStageDataForAutocomplete = (pipeline, textcompleteConfig) => {
      if(pipeline && pipeline.stages) {
        let configList = pipeline.stages.map((stage) => {

          let stageList = jsonListBuilder.convertJsonKeysToBracketedList(stage, ['task']);

          return {
            id: `stage config for ${stage.name}`,
            match: new RegExp(`#stage\\(\\s*'${jsonListBuilder.escapeForRegEx(stage.name)}'\\s*\\)(.*)$`),
            index: 1,
            search: listSearchFn(stageList),
            template: leafTemplateFn,
            replace: (param) => {
              return `#stage('${stage.name}')${param.leaf}`;
            }
          };

        });
        return addToTextcompleteConfig(configList, textcompleteConfig);
      }

      return textcompleteConfig;

    };

    let addManualJudgementConfigForAutocomplete = (pipeline, textcompleteConfig) => {
      if(pipeline && pipeline.stages) {

        let manualJudgementStageList = pipeline.stages.filter((stage) => stage.type === 'manualJudgment');

        let configList = manualJudgementStageList.map((stage) => {
          let stageList = jsonListBuilder.convertJsonKeysToBracketedList(stage);

          return {
            id: `judgement config for ${stage.name}`,
            match: new RegExp(`#judgment\\(\\s*'\\s*${jsonListBuilder.escapeForRegEx(stage.name)}'\\s*\\)(.*)$`),
            index: 1,
            search: listSearchFn(stageList),
            template: leafTemplateFn,
            replace: (param) => {
              return `#judgement('${stage.name}')${param.leaf}`;
            }
          };

        });

        return addToTextcompleteConfig(configList, textcompleteConfig);
      }

      return textcompleteConfig;

    };

    let addTriggerConfigForAutocomplete = (pipeline, textcompleteConfig) => {

      if(pipeline && pipeline.trigger) {
        let triggerAsList = [pipeline.trigger];
        let configList = triggerAsList.map((trigger) => {
          let triggerInfoList = jsonListBuilder.convertJsonKeysToBracketedList(trigger);
          return {
            id: `trigger config: ${trigger.type}`,
            match: /trigger(\w*|\s*)$/,
            index: 1,
            search: listSearchFn(triggerInfoList),
            template: leafTemplateFn,
            replace: function replace(value) {
              return `trigger${value.leaf}`;
            }
          };
        });

        return addToTextcompleteConfig(configList, textcompleteConfig);
      }

      return textcompleteConfig;
    };

    let addParameterConfigForAutocomplete = (pipeline, textcompleteConfig) => {
      if(pipeline && pipeline.parameterConfig) {
        let paramsAsList = [pipeline.parameterConfig];
        let configList = paramsAsList.map((params) => {
          let paramsInfoList = jsonListBuilder.convertJsonKeysToBracketedList(params);
          return {
            id: `parameter config: ${Object.keys(params).join(',')}`,
            match: /parameters(\w*|\s*)$/,
            index: 1,
            search: listSearchFn(paramsInfoList),
            template: leafTemplateFn,
            replace: function replace(value) {
              return `parameters${value.leaf}`;
            }
          };
        });

        return addToTextcompleteConfig(configList, textcompleteConfig);
      }

      return textcompleteConfig;
    };

    let addStageNamesToCodeHelperList = (pipeline, textcompleteConfig) => {
      if (pipeline && pipeline.stages) {

        let codedHelperParamsCopy = codedHelperParams.slice(0);

        let pipelineHasParameters = pipeline.parameterConfig && pipeline.parameterConfig.length;
        codedHelperParamsCopy = pipelineHasParameters
                                  ? codedHelperParamsCopy
                                  : codedHelperParamsCopy.filter((param) => param.name !== 'parameters');

        let hasJenkinsTriggerOrStage = pipeline.trigger.type === 'jenkins' || pipeline.stages.some((stage) => stage.type === 'jenkins');
        codedHelperParamsCopy = hasJenkinsTriggerOrStage
          ? codedHelperParamsCopy
          : codedHelperParamsCopy.filter((param) => param.name.indexOf('scmInfo') === -1);

        pipeline.stages.forEach((stage) => {
          let newParam = {name: stage.name, type: stage.type};
          if (codedHelperParamsCopy.filter(paramInList(newParam)).length === 0) {
            codedHelperParamsCopy.push({name: stage.name, type: 'stage'});
          }
        });

        let configList = [{
          id: 'params',
          match: /(\s*|\w*)\?(\s*|\w*|')$/,
          index: 2,
          search: function (term, callback) {
            callback(codedHelperParamsCopy.filter((param) => {
              if (param.name.indexOf(term) > -1 || param.type.indexOf(term) > -1) {
                return param;
              }
            }));
          },
          template: function (value) {
            return `<span class="marker ${value.type}"></span> ${value.name}`;
          },
          replace: function replace (param) {
            return `${param.name}`;
          }
        }];

        return addToTextcompleteConfig(configList, textcompleteConfig);
      }
      return textcompleteConfig;
    };

    let addHelperFunctionsBasedOnStages = (pipeline, textcompleteConfig) => {
      if (pipeline && pipeline.stages) {
        let helperFunctionsCopy = helperFunctions.slice(0);
        let hasManualJudmentStage = pipeline.stages.some((stage) => stage.type === 'manualJudgment');
        if(!hasManualJudmentStage) {
          helperFunctionsCopy = helperFunctions.filter((fnName) => fnName !== 'judgment');
        }

        let configList = [{
          id: 'helper functions',
          match: /#(\w*)$/,
          index: 1,
          search: function (term, callback) {
            callback(helperFunctionsCopy.filter((helper) => {
              if (helper.indexOf(term) === 0) {
                return helper;
              }
            }));
          },
          template: (value) => {
            return `<span class="marker function"></span> #${value}`;
          },

          replace: function replace (helper) {
            if (helper === 'toJson') {
              return [`#${helper}(`, ')'];
            }
            return [`#${helper}( '`, `' )`];
          }
        }];

        return addToTextcompleteConfig(configList, textcompleteConfig);

      }
      return textcompleteConfig;
    };

    let executionCache = {};

    let getLastExecutionByPipelineConfig = (pipelineConfig) => {
      if(executionCache[pipelineConfig.id]) {
        return $q.when(executionCache[pipelineConfig.id]);
      } else {
        return executionService
          .getLastExecutionForApplicationByConfigId(pipelineConfig.application, pipelineConfig.id)
          .then((execution) => {
            if(execution) {
              executionCache[pipelineConfig.id] = execution;
              return execution;
            } else {
              return null;
            }
          });
      }
    };

    let addPipelineInfo = (pipelineConfig) => {
      if(pipelineConfig && pipelineConfig.id) {
        return getLastExecutionByPipelineConfig(pipelineConfig)
          .then((lastExecution) => {
            return lastExecution || pipelineConfig;
          })
          .then((pipeline) => {
            return addStageNamesToCodeHelperList(
              pipeline,
              addStageDataForAutocomplete(
                pipeline,
                addManualJudgementConfigForAutocomplete(
                  pipeline,
                  addTriggerConfigForAutocomplete(
                    pipeline,
                    addParameterConfigForAutocomplete(
                      pipeline,
                      addHelperFunctionsBasedOnStages(
                        pipeline,
                        addExecutionForAutocomplete (
                          pipeline,
                          addDeloyedServerGroupsForAutoComplete(
                            pipeline,
                            textcompleteConfig.slice(0)
                          )
                        )
                      )
                    )
                  )
                )
              )
            );
          });
      } else {
        return $q.when(textcompleteConfig);
      }

    };

    return {
      textcompleteConfig: textcompleteConfig,
      addPipelineInfo: addPipelineInfo
    };

  });
<commit_msg>(core) guard against missing trigger in autocomplete (#2658)<commit_after>'use strict';

let angular = require('angular');

module.exports = angular
  .module('spinnaker.core.widget.spelAutocomplete', [
    require('./jsonListBuilder'),
    require('../../delivery/service/execution.service'),
  ])
  .factory('spelAutocomplete', function($q, jsonListBuilder, executionService) {
    let brackets = [{open: '(', close: ')'}, {open: '[', close: ']'}, ];
    let quotes = [{open: '\'', close: '\'' }, {open: '\"', close: '\"' }];
    let helperFunctions = ['alphanumerical', 'readJson', 'fromUrl', 'jsonFromUrl', 'judgment', 'stage', 'toBoolean', 'toFloat', 'toInt', 'toJson'];
    let helperParams = ['execution', 'parameters', 'trigger', 'scmInfo', 'scmInfo.sha1', 'scmInfo.branch', 'deployedServerGroups'];
    let codedHelperParams = helperParams.map((param) => {
      return {name: param, type: 'param'};
    });

    let textcompleteConfig = [
      {
        id: 'SpEL wrapper',
        match: /\$(\w*)$/,
        search: function(item, callback) {
          callback(['${...}']);
        },
        index: 1,
        replace: function replace() {
          return ['${ ', ' }'];
        }
      },
      {
        id: 'match quotes',
        match: /("|')(\w*)$/,
        index: 1,
        search: function(term, callback) {
          let found = quotes.filter((quote) => {
            if (quote.open.indexOf(term) === 0) {
              return quote;
            }
          });
          callback(found);
        },
        template: function() {
          return `'...'`;
        },
        replace: function replace() {
          return [`'` , `'`];
        }
      },
      {
        id: 'match brackets',
        match: /(\[|\(|')(\w*)$/,
        index: 1,
        search: function(term, callback) {
          let found = brackets.filter((bracket) => {
            if (bracket.open.indexOf(term) === 0) {
              return bracket;
            }
          });
          callback(found);
        },
        template: function(value) {
          return `${value.open}...${value.close}`;
        },
        replace: function replace(bracket) {
          return [`${bracket.open}  ` , `  ${bracket.close}`];
        }
      },
    ];

    let paramInList = (checkParam) => {
      return (testParam) => checkParam.name === testParam.name;
    };

    let addToTextcompleteConfig = (configList = [], textcompleteConfig) => {
      let textcompleteConfigCopy = textcompleteConfig.slice(0);

      configList.forEach((newConfig) => {
        if(textcompleteConfig.filter( (config) => config.id === newConfig.id ).length === 0) {
          return textcompleteConfigCopy.push(newConfig);
        }
      });

      return textcompleteConfigCopy;
    };

    const listSearchFn = (list) => {
      return (term, callback) => {
        callback(list.filter((item) => {
          if (item.leaf.indexOf(term) > -1) {
            return item;
          }
        }));
      };
    };

    const leafTemplateFn = (stage) => {
      return `${stage.leaf} <span class="glyphicon glyphicon-triangle-right spel-value-separator"></span> <span class="marker value">${stage.value}</span>`;
    };

    let addExecutionForAutocomplete = (pipeline, textcompleteConfig) => {
      if (pipeline) {
        let executionList = jsonListBuilder.convertJsonKeysToBracketedList(pipeline, ['task']);

        let configList = [{
          id: `execution: ${pipeline.id}`,
          match: /execution(\w*|\s*)$/,
          index: 1,
          search: listSearchFn(executionList),
          template: leafTemplateFn,
          replace: (value) => {
            return `execution${value.leaf}`;
          }
        }];
        return addToTextcompleteConfig(configList, textcompleteConfig);
      }
      return textcompleteConfig;
    };

    let addDeloyedServerGroupsForAutoComplete = (pipeline, textcompleteConfig) => {
      if (pipeline && pipeline.context && pipeline.context.deploymentDetails) {
        let deployList = jsonListBuilder.convertJsonKeysToBracketedList(pipeline.context.deploymentDetails);

        let configList = [{
          id: `deploymentServerGroups: ${pipeline.id}`,
          match: /deployedServerGroups(\w*|\s*)$/,
          index: 1,
          search: listSearchFn(deployList),
          template: leafTemplateFn,
          replace: (value) => {
            return `deployedServerGroups${value.leaf}`;
          }
        }];
        return addToTextcompleteConfig(configList, textcompleteConfig);
      }
      return textcompleteConfig;
    };

    let addStageDataForAutocomplete = (pipeline, textcompleteConfig) => {
      if(pipeline && pipeline.stages) {
        let configList = pipeline.stages.map((stage) => {

          let stageList = jsonListBuilder.convertJsonKeysToBracketedList(stage, ['task']);

          return {
            id: `stage config for ${stage.name}`,
            match: new RegExp(`#stage\\(\\s*'${jsonListBuilder.escapeForRegEx(stage.name)}'\\s*\\)(.*)$`),
            index: 1,
            search: listSearchFn(stageList),
            template: leafTemplateFn,
            replace: (param) => {
              return `#stage('${stage.name}')${param.leaf}`;
            }
          };

        });
        return addToTextcompleteConfig(configList, textcompleteConfig);
      }

      return textcompleteConfig;

    };

    let addManualJudgementConfigForAutocomplete = (pipeline, textcompleteConfig) => {
      if(pipeline && pipeline.stages) {

        let manualJudgementStageList = pipeline.stages.filter((stage) => stage.type === 'manualJudgment');

        let configList = manualJudgementStageList.map((stage) => {
          let stageList = jsonListBuilder.convertJsonKeysToBracketedList(stage);

          return {
            id: `judgement config for ${stage.name}`,
            match: new RegExp(`#judgment\\(\\s*'\\s*${jsonListBuilder.escapeForRegEx(stage.name)}'\\s*\\)(.*)$`),
            index: 1,
            search: listSearchFn(stageList),
            template: leafTemplateFn,
            replace: (param) => {
              return `#judgement('${stage.name}')${param.leaf}`;
            }
          };

        });

        return addToTextcompleteConfig(configList, textcompleteConfig);
      }

      return textcompleteConfig;

    };

    let addTriggerConfigForAutocomplete = (pipeline, textcompleteConfig) => {

      if(pipeline && pipeline.trigger) {
        let triggerAsList = [pipeline.trigger];
        let configList = triggerAsList.map((trigger) => {
          let triggerInfoList = jsonListBuilder.convertJsonKeysToBracketedList(trigger);
          return {
            id: `trigger config: ${trigger.type}`,
            match: /trigger(\w*|\s*)$/,
            index: 1,
            search: listSearchFn(triggerInfoList),
            template: leafTemplateFn,
            replace: function replace(value) {
              return `trigger${value.leaf}`;
            }
          };
        });

        return addToTextcompleteConfig(configList, textcompleteConfig);
      }

      return textcompleteConfig;
    };

    let addParameterConfigForAutocomplete = (pipeline, textcompleteConfig) => {
      if(pipeline && pipeline.parameterConfig) {
        let paramsAsList = [pipeline.parameterConfig];
        let configList = paramsAsList.map((params) => {
          let paramsInfoList = jsonListBuilder.convertJsonKeysToBracketedList(params);
          return {
            id: `parameter config: ${Object.keys(params).join(',')}`,
            match: /parameters(\w*|\s*)$/,
            index: 1,
            search: listSearchFn(paramsInfoList),
            template: leafTemplateFn,
            replace: function replace(value) {
              return `parameters${value.leaf}`;
            }
          };
        });

        return addToTextcompleteConfig(configList, textcompleteConfig);
      }

      return textcompleteConfig;
    };

    let addStageNamesToCodeHelperList = (pipeline, textcompleteConfig) => {
      if (pipeline && pipeline.stages) {

        let codedHelperParamsCopy = codedHelperParams.slice(0);

        let pipelineHasParameters = pipeline.parameterConfig && pipeline.parameterConfig.length;
        codedHelperParamsCopy = pipelineHasParameters
                                  ? codedHelperParamsCopy
                                  : codedHelperParamsCopy.filter((param) => param.name !== 'parameters');
        let trigger = pipeline.trigger || {};
        let hasJenkinsTriggerOrStage = trigger.type === 'jenkins' || pipeline.stages.some((stage) => stage.type === 'jenkins');
        codedHelperParamsCopy = hasJenkinsTriggerOrStage
          ? codedHelperParamsCopy
          : codedHelperParamsCopy.filter((param) => param.name.indexOf('scmInfo') === -1);

        pipeline.stages.forEach((stage) => {
          let newParam = {name: stage.name, type: stage.type};
          if (codedHelperParamsCopy.filter(paramInList(newParam)).length === 0) {
            codedHelperParamsCopy.push({name: stage.name, type: 'stage'});
          }
        });

        let configList = [{
          id: 'params',
          match: /(\s*|\w*)\?(\s*|\w*|')$/,
          index: 2,
          search: function (term, callback) {
            callback(codedHelperParamsCopy.filter((param) => {
              if (param.name.indexOf(term) > -1 || param.type.indexOf(term) > -1) {
                return param;
              }
            }));
          },
          template: function (value) {
            return `<span class="marker ${value.type}"></span> ${value.name}`;
          },
          replace: function replace (param) {
            return `${param.name}`;
          }
        }];

        return addToTextcompleteConfig(configList, textcompleteConfig);
      }
      return textcompleteConfig;
    };

    let addHelperFunctionsBasedOnStages = (pipeline, textcompleteConfig) => {
      if (pipeline && pipeline.stages) {
        let helperFunctionsCopy = helperFunctions.slice(0);
        let hasManualJudmentStage = pipeline.stages.some((stage) => stage.type === 'manualJudgment');
        if(!hasManualJudmentStage) {
          helperFunctionsCopy = helperFunctions.filter((fnName) => fnName !== 'judgment');
        }

        let configList = [{
          id: 'helper functions',
          match: /#(\w*)$/,
          index: 1,
          search: function (term, callback) {
            callback(helperFunctionsCopy.filter((helper) => {
              if (helper.indexOf(term) === 0) {
                return helper;
              }
            }));
          },
          template: (value) => {
            return `<span class="marker function"></span> #${value}`;
          },

          replace: function replace (helper) {
            if (helper === 'toJson') {
              return [`#${helper}(`, ')'];
            }
            return [`#${helper}( '`, `' )`];
          }
        }];

        return addToTextcompleteConfig(configList, textcompleteConfig);

      }
      return textcompleteConfig;
    };

    let executionCache = {};

    let getLastExecutionByPipelineConfig = (pipelineConfig) => {
      if(executionCache[pipelineConfig.id]) {
        return $q.when(executionCache[pipelineConfig.id]);
      } else {
        return executionService
          .getLastExecutionForApplicationByConfigId(pipelineConfig.application, pipelineConfig.id)
          .then((execution) => {
            if(execution) {
              executionCache[pipelineConfig.id] = execution;
              return execution;
            } else {
              return null;
            }
          });
      }
    };

    let addPipelineInfo = (pipelineConfig) => {
      if(pipelineConfig && pipelineConfig.id) {
        return getLastExecutionByPipelineConfig(pipelineConfig)
          .then((lastExecution) => {
            return lastExecution || pipelineConfig;
          })
          .then((pipeline) => {
            return addStageNamesToCodeHelperList(
              pipeline,
              addStageDataForAutocomplete(
                pipeline,
                addManualJudgementConfigForAutocomplete(
                  pipeline,
                  addTriggerConfigForAutocomplete(
                    pipeline,
                    addParameterConfigForAutocomplete(
                      pipeline,
                      addHelperFunctionsBasedOnStages(
                        pipeline,
                        addExecutionForAutocomplete (
                          pipeline,
                          addDeloyedServerGroupsForAutoComplete(
                            pipeline,
                            textcompleteConfig.slice(0)
                          )
                        )
                      )
                    )
                  )
                )
              )
            );
          });
      } else {
        return $q.when(textcompleteConfig);
      }

    };

    return {
      textcompleteConfig: textcompleteConfig,
      addPipelineInfo: addPipelineInfo
    };

  });
<|endoftext|>


<commit_before>$(document).ready(function() {
  //prolly dont need globals, but were good for 1.0
  var json = {};
  var cols = {};
  var datatable;
  $.ajax({
    url: "/api/web/fetch",
    type: "POST",
    dataType: "json",
    data: {},
    success: function(data) {
      json = data;
      updateServersTable(data);
      $('.table-links').on('click', 'a', function(e) {
          changeDataTable($(this).attr('data-id'), $(this).text());
      });
    },
    error: function(error) {
     console.log("Error:");
     console.log(error);
    }
  });

  //TODO make loading bar

  //make the header that lets you choose which table to render
  //choose server, choose stat to view
  function updateServersTable(data) {
    var servers = $('#servers');
    servers.append(
      $('

').addClass('row').addClass('server-list-row-header')
        .append(
          $('

').text('Server List').addClass('col-md-8 col-sm-8 col-xs-8')
        ).append(
          $('

').text('Actions').addClass('col-md-4 col-sm-4 col-xs-4')
        )
    );
    for (var i in data) { //for each server
      servers.append(
        $('

').addClass('row').addClass('server-list-row').append(
          $('

').text(data[i]['name']).addClass('col-md-8 col-sm-8 col-xs-8') //add the row for server
        ).append( //add actions
          $('

').addClass('table-links').addClass('col-md-4 col-sm-4 col-xs-4')
            .append($('').attr({'data-id':i}).text('Hours'))
            .append($('').text(' | '))
            .append($('').attr({'data-id':i}).text('Kills'))
            .append($('').text(' | '))
            .append($('').attr({'data-id':i}).text('Deaths'))
        )
      );
    }
    //console.log('updated servers table');
  }

  //get the nested ids to look in depending on what the user wants to see
  function getRelevantAttrs(stat) {
    switch (stat) {
      case 'Hours':
        return ['times'];
        break;
      case 'Kills':
        return ['kills','friendlyKills'];
        break;
      case 'Deaths':
        return ['losses'];
        break;
      default:
        return false;
    }
  }

   function inHelis(str) {
	  var helicopters = ["SA342M","SA342L","UH-1H","Ka-50","Mi-8MT","CA"];
	  return (helicopters.indexOf(str) != -1)
  }

  //return the 229th name if found
  function findOfficialName(names) {
    for (var i in names) {
      if (names[i].indexOf('229') >= 0) {
        return names[i];
      }
    }
    for (var i in names) {
      return names[i];
    }
  }

  //builds a list of columns to use in the table
  //based on relevance and lots of looping (should only run once)
  function buildColumns(attrs, id, stat) {
    var cols = []; //will hold column names
    cols.push('Pilot'); //get pilot without looping
    for (var pid in json[id]['stats']) { //run through every player node
      //console.log(pid);
      for (var attr in json[id]['stats'][pid]) { //for every id like 'times'
        //console.log('-> '+attr);
        for (var i in attrs) { //see if its in attrs
          //console.log('--> '+attrs[i]);
          if (attr == attrs[i]) { //see if we want something from here
            //console.log('we want everything from ' + attr);
            for (var realcol in json[id]['stats'][pid][attr]) { //push all cols
              //console.log('-------> ' + realcol);
              if (isNaN(realcol)) { //dont add it if its column name is going to be a number
                //console.log('not int');
				if (stat == 'Hours') {
					//console.log(realcol);
					if (inHelis(realcol)) { //only include whitelisted vehicles
						var add = true;
						for (var index in cols) { //for each column weve got so far
						  if (cols[index] == realcol) { //check if we already have it
							add = false;
							break;
						  }
						}
						if (add) { //if its not already in the list, add it
						  cols.push(realcol);
						}
					}
				} else {
					var add = true;
					for (var index in cols) { //for each column weve got so far
					  if (cols[index] == realcol) { //check if we already have it
						add = false;
						break;
					  }
					}
					if (add) { //if its not already in the list, add it
					  cols.push(realcol);
					}
				}

              } else {
                //console.log(realcol+' is a number');
              }
            }
          }
        }
      }
    }
    //console.log('cols');
    //console.log(cols);
    return cols;
  }

  function generateTablePlaceholderData(cols) {
    var data = []; //will make arrays within this array to hold table data
    for (var i in cols) {
      data[cols[i]] = [];
    }
    return data;
  }

  //loop through cols,
  function generateTableHeader(table, cols) {
    var colLabels = $('

');
    for (var i in cols) {
      colLabels.append($('	').text(cols[i]));
    }
    colLabels.wrap('

');
    table.append(colLabels);
  }

  //returns specific formatted values according to what data its trying to table
  function breakout(node, stat) {
    switch (stat) {
      case 'Hours':
        return (Math.round((100*node['total']/3600))/100);
        break;
      case 'Kills':
        return node['total'];
        break;
      default:
        console.log('wrong stat lookup');
        return '';
    }
  }

  //if node is not something stringable, deal with it
  function makeText(node, stat) {
    switch (typeof node) {
      case 'object':
        //return JSON.stringify(node);
        //node = removeNumberedIndices(node);
        return breakout(node, stat);
        break;
      case 'undefined':
        return '0';
        break;
      default:
        return node.toString();
    }
  }

  //format raw data as html for datatables
  function tableFromData(table, data, cols, stat) {
    table.append($('

'));
    for (var i = 0; i < data['Pilot'].length; i++) { //loop for each player
      var htmlRow = $('

');
      //console.log('round ' + i);
      for (var col in cols) {
        //console.log('appending stat ' + data[cols[col]][i] + ' into column [' + cols[col] + '] [' + i +']');
        htmlRow.append($('	').html(
          makeText(data[cols[col]][i], stat)
        ));
      }
      $('.datatable tbody').append(htmlRow);
    }
  }

  //makes json into an array of content for a table
  function generateTableContent(table, id, stat, cols) {
    var attrs = getRelevantAttrs(stat);
    //console.log('generating placeholder data');
    var data = generateTablePlaceholderData(cols);
    var incrementer = 0;
    //console.log('stats[]'+incrementer);
    //console.log(json[id]);
    for (var pid in json[id]['stats']) { //run through every player node
      //console.log('pid='+pid);
      var name = findOfficialName(json[id]['stats'][pid]['names']);
      //console.log('found official name');
      //console.log(name);
      data['Pilot'][incrementer] = name; //add their name to the list always
      for (var attr in json[id]['stats'][pid]) { //for every name like 'times'
        //console.log('-> '+attr);
        //see if its in attrs
        for (var i in attrs) {
          //console.log('--> '+attrs[i]);
          if (attr == attrs[i]) { //see if we want something from here
            //console.log('we want everything from ' + attr);
            for (var realcol in json[id]['stats'][pid][attr]) { //push 'all' cols
              if (isNaN(realcol)) { //as long as its not a number
				  if (stat == 'Hours') { //and if its for an hours table
					if (inHelis(realcol)) { //only include whitelisted vehicles
						//console.log('-------> ' + realcol);
						data[realcol][incrementer] = json[id]['stats'][pid][attr][realcol]; //make it so
					}
				  } else {
					  data[realcol][incrementer] = json[id]['stats'][pid][attr][realcol]; //not a number, not for hours, cool with me
				  }

              }
            }
          }
        }
      }
      incrementer++;
    }
    //console.log(data);
    return tableFromData(table, data, cols, stat);
  }

  //user clicked an action link, create appropriate table
  function changeDataTable(serverId, stat) {
    try {
      datatable.destroy();
    } catch(e) {
      //console.log('cant destroy');
    }
    var table = $('.datatable');
    var cols = buildColumns(getRelevantAttrs(stat), serverId, stat);
    table.html('').attr({'class': 'table datatable'});
    generateTableHeader(table, cols)
    generateTableContent(table, serverId, stat, cols)
    //something something datatable
    $('.datatable tr').first().wrap('

');
    datatable = table.DataTable();
  }

});
<commit_msg>stringent check for /229 in a place > 1 for official name<commit_after>$(document).ready(function() {
  //prolly dont need globals, but were good for 1.0
  var json = {};
  var cols = {};
  var datatable;
  $.ajax({
    url: "/api/web/fetch",
    type: "POST",
    dataType: "json",
    data: {},
    success: function(data) {
      json = data;
      updateServersTable(data);
      $('.table-links').on('click', 'a', function(e) {
          changeDataTable($(this).attr('data-id'), $(this).text());
      });
    },
    error: function(error) {
     console.log("Error:");
     console.log(error);
    }
  });

  //TODO make loading bar

  //make the header that lets you choose which table to render
  //choose server, choose stat to view
  function updateServersTable(data) {
    var servers = $('#servers');
    servers.append(
      $('

').addClass('row').addClass('server-list-row-header')
        .append(
          $('

').text('Server List').addClass('col-md-8 col-sm-8 col-xs-8')
        ).append(
          $('

').text('Actions').addClass('col-md-4 col-sm-4 col-xs-4')
        )
    );
    for (var i in data) { //for each server
      servers.append(
        $('

').addClass('row').addClass('server-list-row').append(
          $('

').text(data[i]['name']).addClass('col-md-8 col-sm-8 col-xs-8') //add the row for server
        ).append( //add actions
          $('

').addClass('table-links').addClass('col-md-4 col-sm-4 col-xs-4')
            .append($('').attr({'data-id':i}).text('Hours'))
            .append($('').text(' | '))
            .append($('').attr({'data-id':i}).text('Kills'))
            .append($('').text(' | '))
            .append($('').attr({'data-id':i}).text('Deaths'))
        )
      );
    }
    //console.log('updated servers table');
  }

  //get the nested ids to look in depending on what the user wants to see
  function getRelevantAttrs(stat) {
    switch (stat) {
      case 'Hours':
        return ['times'];
        break;
      case 'Kills':
        return ['kills','friendlyKills'];
        break;
      case 'Deaths':
        return ['losses'];
        break;
      default:
        return false;
    }
  }

   function inHelis(str) {
	  var helicopters = ["SA342M","SA342L","UH-1H","Ka-50","Mi-8MT","CA"];
	  return (helicopters.indexOf(str) != -1)
  }

  //return the 229th name if found
  function findOfficialName(names) {
    for (var i in names) {
      if (names[i].indexOf('/229)') > 1) {
        return names[i];
      }
    }
    for (var i in names) {
      return names[i];
    }
  }

  //builds a list of columns to use in the table
  //based on relevance and lots of looping (should only run once)
  function buildColumns(attrs, id, stat) {
    var cols = []; //will hold column names
    cols.push('Pilot'); //get pilot without looping
    for (var pid in json[id]['stats']) { //run through every player node
      //console.log(pid);
      for (var attr in json[id]['stats'][pid]) { //for every id like 'times'
        //console.log('-> '+attr);
        for (var i in attrs) { //see if its in attrs
          //console.log('--> '+attrs[i]);
          if (attr == attrs[i]) { //see if we want something from here
            //console.log('we want everything from ' + attr);
            for (var realcol in json[id]['stats'][pid][attr]) { //push all cols
              //console.log('-------> ' + realcol);
              if (isNaN(realcol)) { //dont add it if its column name is going to be a number
                //console.log('not int');
				if (stat == 'Hours') {
					//console.log(realcol);
					if (inHelis(realcol)) { //only include whitelisted vehicles
						var add = true;
						for (var index in cols) { //for each column weve got so far
						  if (cols[index] == realcol) { //check if we already have it
							add = false;
							break;
						  }
						}
						if (add) { //if its not already in the list, add it
						  cols.push(realcol);
						}
					}
				} else {
					var add = true;
					for (var index in cols) { //for each column weve got so far
					  if (cols[index] == realcol) { //check if we already have it
						add = false;
						break;
					  }
					}
					if (add) { //if its not already in the list, add it
					  cols.push(realcol);
					}
				}

              } else {
                //console.log(realcol+' is a number');
              }
            }
          }
        }
      }
    }
    //console.log('cols');
    //console.log(cols);
    return cols;
  }

  function generateTablePlaceholderData(cols) {
    var data = []; //will make arrays within this array to hold table data
    for (var i in cols) {
      data[cols[i]] = [];
    }
    return data;
  }

  //loop through cols,
  function generateTableHeader(table, cols) {
    var colLabels = $('

');
    for (var i in cols) {
      colLabels.append($('	').text(cols[i]));
    }
    colLabels.wrap('

');
    table.append(colLabels);
  }

  //returns specific formatted values according to what data its trying to table
  function breakout(node, stat) {
    switch (stat) {
      case 'Hours':
        return (Math.round((100*node['total']/3600))/100);
        break;
      case 'Kills':
        return node['total'];
        break;
      default:
        console.log('wrong stat lookup');
        return '';
    }
  }

  //if node is not something stringable, deal with it
  function makeText(node, stat) {
    switch (typeof node) {
      case 'object':
        //return JSON.stringify(node);
        //node = removeNumberedIndices(node);
        return breakout(node, stat);
        break;
      case 'undefined':
        return '0';
        break;
      default:
        return node.toString();
    }
  }

  //format raw data as html for datatables
  function tableFromData(table, data, cols, stat) {
    table.append($('

'));
    for (var i = 0; i < data['Pilot'].length; i++) { //loop for each player
      var htmlRow = $('

');
      //console.log('round ' + i);
      for (var col in cols) {
        //console.log('appending stat ' + data[cols[col]][i] + ' into column [' + cols[col] + '] [' + i +']');
        htmlRow.append($('	').html(
          makeText(data[cols[col]][i], stat)
        ));
      }
      $('.datatable tbody').append(htmlRow);
    }
  }

  //makes json into an array of content for a table
  function generateTableContent(table, id, stat, cols) {
    var attrs = getRelevantAttrs(stat);
    //console.log('generating placeholder data');
    var data = generateTablePlaceholderData(cols);
    var incrementer = 0;
    //console.log('stats[]'+incrementer);
    //console.log(json[id]);
    for (var pid in json[id]['stats']) { //run through every player node
      //console.log('pid='+pid);
      var name = findOfficialName(json[id]['stats'][pid]['names']);
      //console.log('found official name');
      //console.log(name);
      data['Pilot'][incrementer] = name; //add their name to the list always
      for (var attr in json[id]['stats'][pid]) { //for every name like 'times'
        //console.log('-> '+attr);
        //see if its in attrs
        for (var i in attrs) {
          //console.log('--> '+attrs[i]);
          if (attr == attrs[i]) { //see if we want something from here
            //console.log('we want everything from ' + attr);
            for (var realcol in json[id]['stats'][pid][attr]) { //push 'all' cols
              if (isNaN(realcol)) { //as long as its not a number
				  if (stat == 'Hours') { //and if its for an hours table
					if (inHelis(realcol)) { //only include whitelisted vehicles
						//console.log('-------> ' + realcol);
						data[realcol][incrementer] = json[id]['stats'][pid][attr][realcol]; //make it so
					}
				  } else {
					  data[realcol][incrementer] = json[id]['stats'][pid][attr][realcol]; //not a number, not for hours, cool with me
				  }

              }
            }
          }
        }
      }
      incrementer++;
    }
    //console.log(data);
    return tableFromData(table, data, cols, stat);
  }

  //user clicked an action link, create appropriate table
  function changeDataTable(serverId, stat) {
    try {
      datatable.destroy();
    } catch(e) {
      //console.log('cant destroy');
    }
    var table = $('.datatable');
    var cols = buildColumns(getRelevantAttrs(stat), serverId, stat);
    table.html('').attr({'class': 'table datatable'});
    generateTableHeader(table, cols)
    generateTableContent(table, serverId, stat, cols)
    //something something datatable
    $('.datatable tr').first().wrap('

');
    datatable = table.DataTable();
  }

});
<|endoftext|>


<commit_before>// nodejs
// npm install react react-dom

var React = require('react');
var ReactDOMServer = require('react-dom/server');

function Welcome(props) {
    return React.createElement(
        'div',
        {},
        'Hello, ' + props.name
    );
}

var element = React.createElement(
    Welcome, // 'div'
    {name: 'React ++'},
    null
)
//
Hello, React Guru

ReactDOMServer.renderToString(element);<commit_msg>Update index.js<commit_after>// nodejs
// npm install react react-dom

var React = require('react');
var ReactDOMServer = require('react-dom/server');

function Welcome(props) {
    return React.createElement(
        'div',
        {},
        'Hello, ' + props.name
    );
}

var element = React.createElement(
    Welcome, // 'div'
    {name: 'React Guru'},
    null
)
//
Hello, React Guru

ReactDOMServer.renderToString(element);
<|endoftext|>


<commit_before>$('#tiny_inner_image').bind('change', function (event) {
    formElement = document.getElementById("tinymce_file_uploader");
    data = new FormData(formElement);
    ed = tinymce.get('news_type_text');

    var tmpMsg = flashes.info('', 'Bild wird hochgeladen');
    ed.getBody().setAttribute('contenteditable', 'false');
    var tinyContainer = $('.mce-tinymce.mce-container.mce-panel');
    tinyContainer.css({opacity: 0.5});

    $.ajax({
        url: $('#tinymce_file_uploader').attr('action'),
        type: 'POST',
        data: data,
        contentType: false,
        processData: false,
        success: function (response) {
            if (typeof(tinymce) != 'undefined') {
                ed.selection.setContent(response);
                // Remove uploaded image, so it is possibly to reupload the same
                $('#tiny_inner_image').closest('form').get(0).reset();
                flashes.success('', 'Bild wurde hochgeladen');
            }
        },
        error: function (response) {
            if (response.status == 400) {
                flashes.danger('', response.responseText);
            } else {
                flashes.danger('', 'Bild konnte nicht hochgeladen werden');
            }
        },
        complete: function () {
            ed.getBody().setAttribute('contenteditable', 'true');
            tmpMsg.alert('close');
            tinyContainer.css({opacity: 1});
        }
    });
});
<commit_msg>Reset image, even after error<commit_after>$('#tiny_inner_image').bind('change', function (event) {
    formElement = document.getElementById("tinymce_file_uploader");
    data = new FormData(formElement);
    ed = tinymce.get('news_type_text');

    var tmpMsg = flashes.info('', 'Bild wird hochgeladen');
    ed.getBody().setAttribute('contenteditable', 'false');
    var tinyContainer = $('.mce-tinymce.mce-container.mce-panel');
    tinyContainer.css({opacity: 0.5});

    $.ajax({
        url: $('#tinymce_file_uploader').attr('action'),
        type: 'POST',
        data: data,
        contentType: false,
        processData: false,
        success: function (response) {
            if (typeof(tinymce) != 'undefined') {
                ed.selection.setContent(response);
                flashes.success('', 'Bild wurde hochgeladen');
            }
        },
        error: function (response) {
            if (response.status == 400) {
                flashes.danger('', response.responseText);
            } else {
                flashes.danger('', 'Bild konnte nicht hochgeladen werden');
            }
        },
        complete: function () {
            $('#tiny_inner_image').closest('form').get(0).reset();
            ed.getBody().setAttribute('contenteditable', 'true');
            tmpMsg.alert('close');
            tinyContainer.css({opacity: 1});
        }
    });
});
<|endoftext|>


<commit_before>/**
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
  System.config({
    paths: {
      // paths serve as alias
      'npm:': 'node_modules/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      app: 'app',
      // angular bundles
      '@angular/core': 'npm:@angular/core/fesm2015/core.mjs',
      '@angular/common': 'npm:@angular/common/fesm2015/common.mjs',
      '@angular/common/http': 'npm:@angular/common/fesm2015/http.mjs',
      '@angular/compiler': 'npm:@angular/compiler/fesm2015/compiler.mjs',
      '@angular/platform-browser': 'npm:@angular/platform-browser/fesm2015/platform-browser.mjs',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/fesm2015/platform-browser-dynamic.mjs',
      '@angular/router': 'npm:@angular/router/fesm2015/router.mjs',
      '@angular/forms': 'npm:@angular/forms/fesm2015/forms.mjs',
      // #docregion upgrade-static-package
      '@angular/upgrade/static': 'npm:@angular/upgrade/fesm2015/static.mjs',
      // #enddocregion upgrade-static-package

      // other libraries
      'rxjs': 'npm:rxjs/dist/cjs',
      'rxjs/operators': 'npm:rxjs/dist/cjs/operators',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',

      'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js',
      'systemjs-babel-build': 'npm:systemjs-plugin-babel/systemjs-babel-browser.js'
    },
    transpiler: 'plugin-babel',
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      'app': { main: './main.js', defaultExtension: 'js' },
      'rxjs': {
        defaultExtension: 'js',
        format: 'cjs',
        main: 'index.js'
      },
      'rxjs/operators': {
        defaultExtension: 'js',
        format: 'cjs',
        main: 'index.js'
      },
      'meta': {
        '*.mjs': {
          babelOptions: {
            es2015: false
          }
        }
      }
    }
  });
})(this);
<commit_msg>fix(docs-infra): include `tslib` into SystemJS config in `upgrade-module` example app (#45706)<commit_after>/**
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
  System.config({
    paths: {
      // paths serve as alias
      'npm:': 'node_modules/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      app: 'app',
      // angular bundles
      '@angular/core': 'npm:@angular/core/fesm2015/core.mjs',
      '@angular/common': 'npm:@angular/common/fesm2015/common.mjs',
      '@angular/common/http': 'npm:@angular/common/fesm2015/http.mjs',
      '@angular/compiler': 'npm:@angular/compiler/fesm2015/compiler.mjs',
      '@angular/platform-browser': 'npm:@angular/platform-browser/fesm2015/platform-browser.mjs',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/fesm2015/platform-browser-dynamic.mjs',
      '@angular/router': 'npm:@angular/router/fesm2015/router.mjs',
      '@angular/forms': 'npm:@angular/forms/fesm2015/forms.mjs',
      // #docregion upgrade-static-package
      '@angular/upgrade/static': 'npm:@angular/upgrade/fesm2015/static.mjs',
      // #enddocregion upgrade-static-package

      // other libraries
      'rxjs': 'npm:rxjs/dist/cjs',
      'rxjs/operators': 'npm:rxjs/dist/cjs/operators',
      'tslib': 'npm:tslib/tslib.js',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',

      'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js',
      'systemjs-babel-build': 'npm:systemjs-plugin-babel/systemjs-babel-browser.js'
    },
    transpiler: 'plugin-babel',
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      'app': { main: './main.js', defaultExtension: 'js' },
      'rxjs': {
        defaultExtension: 'js',
        format: 'cjs',
        main: 'index.js'
      },
      'rxjs/operators': {
        defaultExtension: 'js',
        format: 'cjs',
        main: 'index.js'
      },
      'meta': {
        '*.mjs': {
          babelOptions: {
            es2015: false
          }
        }
      }
    }
  });
})(this);
<|endoftext|>


<commit_before>/* eslint-disable no-underscore-dangle */
// WP REST API has many underscore dangle's, no point to fight against it
import defaultsDeep from 'lodash/defaultsDeep';
import nthArg from 'lodash/nthArg';
import mapKeys from 'lodash/mapKeys';
import forEach from 'lodash/forEach';
import find from 'lodash/find';
import noop from 'lodash/noop';
import Immutable from 'immutable';
import isArray from 'lodash/isArray';
import isUndefined from 'lodash/isUndefined';
import { selectRequest } from './selectors';
import WPAPIAdapter from './adapters/wpapi';
import {
  lastCacheUpdate as lastCacheUpdateSymbol,
  partial as partialSymbol,
} from './symbols';

import {
  REDUX_WP_API_CALL,
  REDUX_WP_API_REQUEST,
  REDUX_WP_API_SUCCESS,
  REDUX_WP_API_FAILURE,
  REDUX_WP_API_CACHE_HIT,
} from './constants/actions';

import {
  pending,
  resolved,
  rejected,
} from './constants/requestStatus';

export const initialReducerState = Immutable.fromJS({
  requestsByName: {},
  requestsByQuery: {},

  resources: [],
  resourcesIndexes: {},
});

export default class ReduxWPAPI {
  static displayName = '[REDUX-WP-API]';

  static defaultSettings = {
    transformResource: nthArg(0),

    /**
     * Get aggregator for URL
     *
     * Infers the aggregator identifier of a given URL to which all resulting resources are going to
     * be associated with. An aggregator is a set containing resources indexed by its ids and by the
     * its custom indexers.
     *
     * @param  {String}      url                 URL from which the aggregator will be infered
     * @param  {Object|null} additionalData      Available data about the expected resource
     * @param  {String}      suggestedAggregator Suggestion given by the adapter
     * @return {String|null} aggregatorID        String to which the resource will be associated
     *                                           with or null, if resources musn't be indexed
     */
    getAggregator: nthArg(2),
    timeout: 30000,
    ttl: 60000,
  }

  constructor({ adapter, ...settings }) {
    this.settings = defaultsDeep({}, settings, ReduxWPAPI.defaultSettings);

    if (!adapter) {
      this.adapter = new WPAPIAdapter(this.settings);
    } else {
      this.adapter = adapter;
    }

    this.settings.customCacheIndexes = {
       // reinforces demandatory indexes besides id
      ...(this.adapter.customCacheIndexes || {}),
      ...this.settings.customCacheIndexes,
    };
  }

  middleware = store => next => action => {
    if (!action || action.type !== REDUX_WP_API_CALL) return next(action);

    const request = this.adapter.buildRequest(action.payload);
    const meta = {
      name: action.payload.name,
      url: this.adapter.getUrl(request),
      operation: this.adapter.getOperation(request),
      requestAt: Date.now(),
    };

    const payload = {};
    if (meta.operation === 'get') {
      let cache;
      let lastCacheUpdate;
      let data;
      let ttl;
      if (this.adapter.getTTL) {
        ttl = this.adapter.getTTL(request);
      }

      if (ttl !== 0 && ttl !== false && !ttl) {
        ttl = this.settings.ttl;
      }

      const state = store.getState().wp;
      const indexes = this.adapter.getIndexes(request);
      const aggregator = this.settings.getAggregator(
        meta.url,
        indexes,
        this.adapter.getAggregator(meta.url)
      );
      const resourceLocalID = this.getResourceLocalID(state, aggregator, indexes);

      payload.cacheID = this.adapter.generateCacheID(request);
      payload.page = parseInt(this.adapter.getRequestedPage(request) || 1, 10);

      if (resourceLocalID !== false) {
        cache = state.getIn(['resources', resourceLocalID]);
        data = [resourceLocalID];
      }

      if (cache) {
        lastCacheUpdate = cache[lastCacheUpdateSymbol];
        if (cache[partialSymbol]) {
          ttl = 0;
        }
      } else {
        cache = state.getIn(['requestsByQuery', payload.cacheID, payload.page]);
        data = state.get('data');

        if (cache && cache.get('error')) {
          cache = false;
        }
      }

      if (cache) {
        lastCacheUpdate = lastCacheUpdate || cache.get('responseAt') || cache.get('requestAt');
        next({
          meta,
          type: REDUX_WP_API_CACHE_HIT,
          payload: {
            cacheID: payload.cacheID,
            page: payload.page,
            lastCacheUpdate,
            data,
          },
        });

        if (Date.now() - lastCacheUpdate < ttl) {
          return Promise.resolve(
            selectRequest({
              cacheID: payload.cacheID,
              page: payload.page,
            })(store.getState())
          );
        }
      }
    }

    next({
      type: REDUX_WP_API_REQUEST,
      payload,
      meta,
    });

    return (
      this.adapter.sendRequest(request)
      .then(
        response =>
          next({
            type: REDUX_WP_API_SUCCESS,
            payload: { ...payload, response },
            meta: { ...meta, responseAt: Date.now() },
          }),
        error =>
          next({
            type: REDUX_WP_API_FAILURE,
            payload,
            error,
            meta: { ...meta, responseAt: Date.now() },
          })
      )
      .then(() => {
        if (meta.operation === 'get') {
          return selectRequest({
            cacheID: payload.cacheID,
            page: payload.page,
          })(store.getState());
        }

        return selectRequest(meta.name)(store.getState());
      })
    );
  }

  reducer = (state = initialReducerState, action) => {
    switch (action.type) {
      case REDUX_WP_API_CACHE_HIT: {
        const { data, page, cacheID } = action.payload;
        let newState = state.mergeIn(
          ['requestsByName', action.meta.name],
          { page, cacheID }
        );

        if (!newState.getIn(['requestsByQuery', cacheID, page])) {
          newState = (
            newState
            .mergeIn(['requestsByQuery', cacheID, page], {
              status: resolved,
              operation: action.meta.operation,
              error: false,
              requestAt: action.payload.lastCacheUpdate,
              responseAt: action.payload.lastCacheUpdate,
            })
            .setIn(['requestsByQuery', cacheID, 1, 'data'], data)
          );
        }

        return newState;
      }

      case REDUX_WP_API_REQUEST: {
        const { name, operation, requestAt } = action.meta;

        const requestState = {
          status: pending,
          requestAt,
          operation,
        };

        if (operation === 'get') {
          const { page, cacheID } = action.payload;
          let pagination = state.getIn(['requestsByQuery', cacheID, 'pagination']);
          pagination = pagination || {};

          return (
            state
            .mergeIn(['requestsByName', name], { page, cacheID })
            .setIn(['requestsByQuery', cacheID, 'pagination'], pagination)
            .mergeIn(['requestsByQuery', cacheID, page], requestState)
          );
        }

        return (
          state
          .mergeIn(['requestsByName', name], requestState)
          .setIn(['requestsByName', name, 'data'], false)
        );
      }
      case REDUX_WP_API_SUCCESS: {
        let newState = state;
        const { payload, meta: { name, url, requestAt, responseAt } } = action;
        const { cacheID, page, response } = payload;
        let body = response;

        const requestState = {
          responseAt,
          status: resolved,
          error: false,
        };

        if (!isArray(body)) {
          body = [body];
        }

        const data = [];
        const additionalData = {
          [lastCacheUpdateSymbol]: requestState.responseAt,
          [partialSymbol]: false,
        };

        body.forEach(resource => {
          newState = this.indexResource(newState, url, resource, additionalData, id => {
            data.push(id);
          });
        });

        if (action.meta.operation === 'get') {
          const pagination = this.adapter.getPagination(response);
          newState = newState.mergeIn(['requestsByQuery', cacheID, page], requestState);
          newState = newState.setIn(['requestsByQuery', cacheID, page, 'data'], data);
          newState = newState.setIn(['requestsByQuery', cacheID, 'pagination'], pagination);
        } else if (newState.getIn(['requestsByName', name, 'requestAt']) === requestAt) {
          newState = (
            newState
            .setIn(['requestsByName', action.meta.name, 'data'], data)
            .mergeIn(['requestsByName', name], requestState)
          );
        }

        return newState;
      }
      case REDUX_WP_API_FAILURE: {
        const { error } = action;
        const { page, cacheID } = action.payload;
        const requestState = {
          status: rejected,
          error: {
            message: error.message,
            status: error.status,
          },
        };

        if (action.meta.operation === 'get') {
          return state.mergeIn(['requestsByQuery', cacheID, page], requestState);
        }

        return state.mergeIn(['requestsByName', action.meta.name], requestState);
      }
      default: return state;
    }
  }

  getResourceLocalID(state, aggregator, resource) {
    const indexers = this.settings.customCacheIndexes[aggregator];
    const indexBy = find(
      isArray(indexers) ? ['id'].concat(indexers) : ['id', indexers],
      indexer =>
        !isUndefined(resource[indexer]) &&
        !isUndefined(
          state.getIn(['resourcesIndexes', aggregator, indexer, resource[indexer].toString()])
        )
    );

    if (isUndefined(indexBy)) {
      return false;
    }

    return state.getIn(['resourcesIndexes', aggregator, indexBy, resource[indexBy].toString()]);
  }

  indexResource(state, url, resource, meta, onIndex = noop) {
    let newState = state;
    let _embedded;
    const aggregator = this.settings.getAggregator(
      url,
      resource,
      this.adapter.getAggregator(url, resource)
    );
    const curies = (resource._links || {}).curies;
    const _links = this.resolveAliases(resource._links, curies) || {};
    delete _links.curies;

    let resourceLocalID = this.getResourceLocalID(state, aggregator, resource);
    const emptyState = {};
    let oldState = emptyState;
    if (resourceLocalID !== false) {
      oldState = newState.getIn(['resources', resourceLocalID]);
    }

    if (resource._embedded) {
      _embedded = { ...(oldState || { })._embedded };
      const alisedEmbedded = this.resolveAliases(resource._embedded, curies);
      const embeddedMeta = { ...meta, [partialSymbol]: true };
      forEach(alisedEmbedded, (embeddable, relName) => {
        forEach(_links[relName], (link, index) => {
          let toEmbed = alisedEmbedded[relName][index];
          toEmbed = (isArray(toEmbed) ? toEmbed : [toEmbed]).reduce((collection, toBeEmbedded) => {
            if (toBeEmbedded.code === 'rest_no_route') return collection;

            newState = this.indexResource(
              newState,
              link.href,
              toBeEmbedded,
              embeddedMeta,
              id => collection.push(id)
            );

            return collection;
          }, []);

          if (!isArray(alisedEmbedded[relName][index])) {
            if (!toEmbed.length) {
              return;
            }

            toEmbed = toEmbed[0];
          }

          _embedded[this.adapter.embedLinkAs({ name: relName, ...link })] = toEmbed;
        });
      });
    }

    if (resourceLocalID === false) {
      resourceLocalID = newState.get('resources').size;
    }

    let isPartial = meta[partialSymbol] || false;
    if (oldState !== emptyState && oldState[partialSymbol] && !meta[partialSymbol]) {
      isPartial = false;
    }

    let resourceTransformed = {
      ...oldState,
      ...resource,
      ...meta,
      _links,
      _embedded,
      [partialSymbol]: isPartial,
    };

    if (this.adapter.transformResource) {
      resourceTransformed = this.adapter.transformResource(resourceTransformed);
    }

    if (this.settings.transformResource) {
      resourceTransformed = this.settings.transformResource(resourceTransformed);
    }

    newState = newState.setIn(['resources', resourceLocalID], resourceTransformed);
    const indexers = this.settings.customCacheIndexes[aggregator];

    forEach(isArray(indexers) ? ['id'].concat(indexers) : ['id', indexers], indexer => {
      if (!isUndefined(resource[indexer])) {
        newState = newState.setIn(
          ['resourcesIndexes', aggregator, indexer, resource[indexer].toString()],
          resourceLocalID
        );
      }
    });

    onIndex(resourceLocalID);
    return newState;
  }

  resolveAliases(map, curies) {
    return (curies || []).reduce((remapped, cury) => {
      const alias = new RegExp(`^${cury.name}:`);
      return mapKeys(remapped, (value, key) =>
        cury.href.replace(/\{rel\}/g, key.replace(alias, ''))
      );
    }, map) || map;
  }
}
<commit_msg>Code Review<commit_after>/* eslint-disable no-underscore-dangle */
// WP REST API has many underscore dangle's, no point to fight against it
import defaultsDeep from 'lodash/defaultsDeep';
import nthArg from 'lodash/nthArg';
import mapKeys from 'lodash/mapKeys';
import forEach from 'lodash/forEach';
import find from 'lodash/find';
import noop from 'lodash/noop';
import Immutable from 'immutable';
import isArray from 'lodash/isArray';
import isUndefined from 'lodash/isUndefined';
import { selectRequest } from './selectors';
import WPAPIAdapter from './adapters/wpapi';
import {
  lastCacheUpdate as lastCacheUpdateSymbol,
  partial as partialSymbol,
} from './symbols';

import {
  REDUX_WP_API_CALL,
  REDUX_WP_API_REQUEST,
  REDUX_WP_API_SUCCESS,
  REDUX_WP_API_FAILURE,
  REDUX_WP_API_CACHE_HIT,
} from './constants/actions';

import {
  pending,
  resolved,
  rejected,
} from './constants/requestStatus';

export const initialReducerState = Immutable.fromJS({
  requestsByName: {},
  requestsByQuery: {},

  resources: [],
  resourcesIndexes: {},
});

export default class ReduxWPAPI {
  static displayName = '[REDUX-WP-API]';

  static defaultSettings = {
    transformResource: nthArg(0),

    /**
     * Get aggregator for URL
     *
     * Infers the aggregator identifier of a given URL to which all resulting resources are going to
     * be associated with. An aggregator is a set containing resources indexed by its ids and by the
     * its custom indexers.
     *
     * @param  {String}      url                 URL from which the aggregator will be infered
     * @param  {Object|null} additionalData      Available data about the expected resource
     * @param  {String}      suggestedAggregator Suggestion given by the adapter
     * @return {String|null} aggregatorID        String to which the resource will be associated
     *                                           with or null, if resources musn't be indexed
     */
    getAggregator: nthArg(2),
    timeout: 30000,
    ttl: 60000,
  }

  constructor({ adapter, ...settings }) {
    this.settings = defaultsDeep({}, settings, ReduxWPAPI.defaultSettings);

    if (!adapter) {
      this.adapter = new WPAPIAdapter(this.settings);
    } else {
      this.adapter = adapter;
    }

    this.settings.customCacheIndexes = {
       // reinforces demandatory indexes besides id
      ...(this.adapter.customCacheIndexes || {}),
      ...this.settings.customCacheIndexes,
    };
  }

  middleware = store => next => action => {
    if (!action || action.type !== REDUX_WP_API_CALL) return next(action);

    const request = this.adapter.buildRequest(action.payload);
    const meta = {
      name: action.payload.name,
      url: this.adapter.getUrl(request),
      operation: this.adapter.getOperation(request),
      requestAt: Date.now(),
    };

    const payload = {};
    if (meta.operation === 'get') {
      let cache;
      let lastCacheUpdate;
      let data;
      let ttl;
      if (this.adapter.getTTL) {
        ttl = this.adapter.getTTL(request);
      }

      if (ttl === null || ttl === undefined) {
        ttl = this.settings.ttl;
      }

      const state = store.getState().wp;
      const indexes = this.adapter.getIndexes(request);
      const aggregator = this.settings.getAggregator(
        meta.url,
        indexes,
        this.adapter.getAggregator(meta.url)
      );
      const resourceLocalID = this.getResourceLocalID(state, aggregator, indexes);

      payload.cacheID = this.adapter.generateCacheID(request);
      payload.page = parseInt(this.adapter.getRequestedPage(request) || 1, 10);

      if (resourceLocalID !== false) {
        cache = state.getIn(['resources', resourceLocalID]);
        data = [resourceLocalID];
      }

      if (cache) {
        lastCacheUpdate = cache[lastCacheUpdateSymbol];
        if (cache[partialSymbol]) {
          ttl = 0;
        }
      } else {
        cache = state.getIn(['requestsByQuery', payload.cacheID, payload.page]);
        data = state.get('data');

        if (cache && cache.get('error')) {
          cache = false;
        }
      }

      if (cache) {
        lastCacheUpdate = lastCacheUpdate || cache.get('responseAt') || cache.get('requestAt');
        next({
          meta,
          type: REDUX_WP_API_CACHE_HIT,
          payload: {
            cacheID: payload.cacheID,
            page: payload.page,
            lastCacheUpdate,
            data,
          },
        });

        if (Date.now() - lastCacheUpdate < ttl) {
          return Promise.resolve(
            selectRequest({
              cacheID: payload.cacheID,
              page: payload.page,
            })(store.getState())
          );
        }
      }
    }

    next({
      type: REDUX_WP_API_REQUEST,
      payload,
      meta,
    });

    return (
      this.adapter.sendRequest(request)
      .then(
        response =>
          next({
            type: REDUX_WP_API_SUCCESS,
            payload: { ...payload, response },
            meta: { ...meta, responseAt: Date.now() },
          }),
        error =>
          next({
            type: REDUX_WP_API_FAILURE,
            payload,
            error,
            meta: { ...meta, responseAt: Date.now() },
          })
      )
      .then(() => {
        if (meta.operation === 'get') {
          return selectRequest({
            cacheID: payload.cacheID,
            page: payload.page,
          })(store.getState());
        }

        return selectRequest(meta.name)(store.getState());
      })
    );
  }

  reducer = (state = initialReducerState, action) => {
    switch (action.type) {
      case REDUX_WP_API_CACHE_HIT: {
        const { data, page, cacheID } = action.payload;
        let newState = state.mergeIn(
          ['requestsByName', action.meta.name],
          { page, cacheID }
        );

        if (!newState.getIn(['requestsByQuery', cacheID, page])) {
          newState = (
            newState
            .mergeIn(['requestsByQuery', cacheID, page], {
              status: resolved,
              operation: action.meta.operation,
              error: false,
              requestAt: action.payload.lastCacheUpdate,
              responseAt: action.payload.lastCacheUpdate,
            })
            .setIn(['requestsByQuery', cacheID, 1, 'data'], data)
          );
        }

        return newState;
      }

      case REDUX_WP_API_REQUEST: {
        const { name, operation, requestAt } = action.meta;

        const requestState = {
          status: pending,
          requestAt,
          operation,
        };

        if (operation === 'get') {
          const { page, cacheID } = action.payload;
          let pagination = state.getIn(['requestsByQuery', cacheID, 'pagination']);
          pagination = pagination || {};

          return (
            state
            .mergeIn(['requestsByName', name], { page, cacheID })
            .setIn(['requestsByQuery', cacheID, 'pagination'], pagination)
            .mergeIn(['requestsByQuery', cacheID, page], requestState)
          );
        }

        return (
          state
          .mergeIn(['requestsByName', name], requestState)
          .setIn(['requestsByName', name, 'data'], false)
        );
      }
      case REDUX_WP_API_SUCCESS: {
        let newState = state;
        const { payload, meta: { name, url, requestAt, responseAt } } = action;
        const { cacheID, page, response } = payload;
        let body = response;

        const requestState = {
          responseAt,
          status: resolved,
          error: false,
        };

        if (!isArray(body)) {
          body = [body];
        }

        const data = [];
        const additionalData = {
          [lastCacheUpdateSymbol]: requestState.responseAt,
          [partialSymbol]: false,
        };

        body.forEach(resource => {
          newState = this.indexResource(newState, url, resource, additionalData, id => {
            data.push(id);
          });
        });

        if (action.meta.operation === 'get') {
          const pagination = this.adapter.getPagination(response);
          newState = newState.mergeIn(['requestsByQuery', cacheID, page], requestState);
          newState = newState.setIn(['requestsByQuery', cacheID, page, 'data'], data);
          newState = newState.setIn(['requestsByQuery', cacheID, 'pagination'], pagination);
        } else if (newState.getIn(['requestsByName', name, 'requestAt']) === requestAt) {
          newState = (
            newState
            .setIn(['requestsByName', action.meta.name, 'data'], data)
            .mergeIn(['requestsByName', name], requestState)
          );
        }

        return newState;
      }
      case REDUX_WP_API_FAILURE: {
        const { error } = action;
        const { page, cacheID } = action.payload;
        const requestState = {
          status: rejected,
          error: {
            message: error.message,
            status: error.status,
          },
        };

        if (action.meta.operation === 'get') {
          return state.mergeIn(['requestsByQuery', cacheID, page], requestState);
        }

        return state.mergeIn(['requestsByName', action.meta.name], requestState);
      }
      default: return state;
    }
  }

  getResourceLocalID(state, aggregator, resource) {
    const indexers = this.settings.customCacheIndexes[aggregator];
    const indexBy = find(
      isArray(indexers) ? ['id'].concat(indexers) : ['id', indexers],
      indexer =>
        !isUndefined(resource[indexer]) &&
        !isUndefined(
          state.getIn(['resourcesIndexes', aggregator, indexer, resource[indexer].toString()])
        )
    );

    if (isUndefined(indexBy)) {
      return false;
    }

    return state.getIn(['resourcesIndexes', aggregator, indexBy, resource[indexBy].toString()]);
  }

  indexResource(state, url, resource, meta, onIndex = noop) {
    let newState = state;
    let _embedded;
    const aggregator = this.settings.getAggregator(
      url,
      resource,
      this.adapter.getAggregator(url, resource)
    );
    const curies = (resource._links || {}).curies;
    const _links = this.resolveAliases(resource._links, curies) || {};
    delete _links.curies;

    let resourceLocalID = this.getResourceLocalID(state, aggregator, resource);
    const emptyState = {};
    let oldState = emptyState;
    if (resourceLocalID !== false) {
      oldState = newState.getIn(['resources', resourceLocalID]);
    }

    if (resource._embedded) {
      _embedded = { ...(oldState || { })._embedded };
      const alisedEmbedded = this.resolveAliases(resource._embedded, curies);
      const embeddedMeta = { ...meta, [partialSymbol]: true };
      forEach(alisedEmbedded, (embeddable, relName) => {
        forEach(_links[relName], (link, index) => {
          let toEmbed = alisedEmbedded[relName][index];
          toEmbed = (isArray(toEmbed) ? toEmbed : [toEmbed]).reduce((collection, toBeEmbedded) => {
            if (toBeEmbedded.code === 'rest_no_route') return collection;

            newState = this.indexResource(
              newState,
              link.href,
              toBeEmbedded,
              embeddedMeta,
              id => collection.push(id)
            );

            return collection;
          }, []);

          if (!isArray(alisedEmbedded[relName][index])) {
            if (!toEmbed.length) {
              return;
            }

            toEmbed = toEmbed[0];
          }

          _embedded[this.adapter.embedLinkAs({ name: relName, ...link })] = toEmbed;
        });
      });
    }

    if (resourceLocalID === false) {
      resourceLocalID = newState.get('resources').size;
    }

    let isPartial = meta[partialSymbol] || false;
    if (oldState !== emptyState && oldState[partialSymbol] && !meta[partialSymbol]) {
      isPartial = false;
    }

    let resourceTransformed = {
      ...oldState,
      ...resource,
      ...meta,
      _links,
      _embedded,
      [partialSymbol]: isPartial,
    };

    if (this.adapter.transformResource) {
      resourceTransformed = this.adapter.transformResource(resourceTransformed);
    }

    if (this.settings.transformResource) {
      resourceTransformed = this.settings.transformResource(resourceTransformed);
    }

    newState = newState.setIn(['resources', resourceLocalID], resourceTransformed);
    const indexers = this.settings.customCacheIndexes[aggregator];

    forEach(isArray(indexers) ? ['id'].concat(indexers) : ['id', indexers], indexer => {
      if (!isUndefined(resource[indexer])) {
        newState = newState.setIn(
          ['resourcesIndexes', aggregator, indexer, resource[indexer].toString()],
          resourceLocalID
        );
      }
    });

    onIndex(resourceLocalID);
    return newState;
  }

  resolveAliases(map, curies) {
    return (curies || []).reduce((remapped, cury) => {
      const alias = new RegExp(`^${cury.name}:`);
      return mapKeys(remapped, (value, key) =>
        cury.href.replace(/\{rel\}/g, key.replace(alias, ''))
      );
    }, map) || map;
  }
}
<|endoftext|>


<commit_before>const EventEmitter = require('events').EventEmitter
const pMap = require('p-map')
const Log = require('ipfs-log')

const Logger = require('logplease')
const logger = Logger.create("orbit-db.replicator", { color: Logger.Colors.Cyan })
Logger.setLogLevel('ERROR')

const getNext = e => e.next
const flatMap = (res, val) => res.concat(val)
const notNull = entry => entry !== null && entry !== undefined
const uniqueValues = (res, val) => {
  res[val] = val
  return res
}

const batchSize = 1

class Replicator extends EventEmitter {
  constructor (store, concurrency) {
    super()
    this._store = store
    this._fetching = {}
    this._stats = {
      tasksRequested: 0,
      tasksStarted: 0,
      tasksProcessed: 0,
    }
    this._buffer = []

    this._concurrency = concurrency || 128
    this._queue = {}
    this._q = new Set()

    // Flush the queue as an emergency switch
    this._flushTimer = setInterval(() => {
      if (this.tasksRunning === 0 && Object.keys(this._queue).length > 0) {
        logger.error("Had to flush the queue!", Object.keys(this._queue).length, "items in the queue, ", this.tasksRequested, this.tasksFinished, " tasks requested/finished")
        setTimeout(() => this._processQueue(), 0)
      }
    }, 3000)
  }

  /**
   * Returns the number of tasks started during the life time
   * @return {[Integer]} [Number of tasks started]
   */
  get tasksRequested () {
    return this._stats.tasksRequested
  }

  /**
   * Returns the number of tasks started during the life time
   * @return {[Integer]} [Number of tasks running]
   */
  get tasksStarted () {
    return this._stats.tasksStarted
  }

  /**
   * Returns the number of tasks running currently
   * @return {[Integer]} [Number of tasks running]
   */
  get tasksRunning () {
    return this._stats.tasksStarted - this._stats.tasksProcessed
  }

  /**
   * Returns the number of tasks currently queued
   * @return {[Integer]} [Number of tasks queued]
   */
  get tasksQueued () {
    return Math.max(Object.keys(this._queue).length - this.tasksRunning, 0)
  }

  /**
   * Returns the number of tasks finished during the life time
   * @return {[Integer]} [Number of tasks finished]
   */
  get tasksFinished () {
    return this._stats.tasksProcessed
  }

  /**
   * Returns the hashes currently queued
   * @return {[Array<String>]} [Queued hashes]
   */
  getQueue () {
    return Object.values(this._queue)
  }

  /*
    Process new heads.
   */
  load (entries) {
    const notKnown = entry => !this._store._oplog.has(entry.hash || entry) && !this._queue[entry.hash || entry]

    try {
      entries
        .filter(notNull)
        .filter(notKnown)
        .forEach(this._addToQueue.bind(this))

      setTimeout(() => this._processQueue(), 0)
    } catch (e) {
      console.error(e)
    }
  }

  _addToQueue (entry) {
    const hash = entry.hash || entry

    if (this._store._oplog.has(hash) || this._fetching[hash] || this._queue[hash])
      return

    this._stats.tasksRequested += 1
    this._queue[hash] = entry
  }

  async _processQueue () {
    if (this.tasksRunning < this._concurrency) {
      const capacity = this._concurrency - this.tasksRunning
      const items = Object.values(this._queue).slice(0, capacity).filter(notNull)
      items.forEach(entry => delete this._queue[entry.hash || entry])

      const flattenAndGetUniques = (nexts) => nexts.reduce(flatMap, []).reduce(uniqueValues, {})
      const processValues = (nexts) => {
        const values = Object.values(nexts).filter(notNull)

        if ((items.length > 0 && this._buffer.length > 0)
          || (this.tasksRunning === 0 && this._buffer.length > 0)) {
            const logs = this._buffer.slice()
            this._buffer = []
            this.emit('load.end', logs)
        }

        if (values.length > 0)
          this.load(values)
      }

      return pMap(items, e => this._processOne(e))
        .then(flattenAndGetUniques)
        .then(processValues)
    }
  }

  async _processOne (entry) {
    const hash = entry.hash || entry

    if (this._store._oplog.has(hash) || this._fetching[hash])
      return

    this._fetching[hash] = hash
    this.emit('load.added', entry)
    this._stats.tasksStarted += 1

    const exclude = []
    const log = await Log.fromEntryHash(this._store._ipfs, hash, this._store._oplog.id, batchSize, exclude, this._store.key, this._store.access.write)
    this._buffer.push(log)

    const latest = log.values[0]
    delete this._queue[hash]

    // Mark this task as processed
    this._stats.tasksProcessed += 1

    // Notify subscribers that we made progress
    this.emit('load.progress', this._id, hash, latest, null, this._buffer.length)

    // Return all next pointers
    return log.values.map(getNext).reduce(flatMap, [])
  }
}

module.exports = Replicator
<commit_msg>Decrease log level for flushing. Improve logger naming.<commit_after>const EventEmitter = require('events').EventEmitter
const pMap = require('p-map')
const Log = require('ipfs-log')

const Logger = require('logplease')
const logger = Logger.create("replicator", { color: Logger.Colors.Cyan })
Logger.setLogLevel('ERROR')

const getNext = e => e.next
const flatMap = (res, val) => res.concat(val)
const notNull = entry => entry !== null && entry !== undefined
const uniqueValues = (res, val) => {
  res[val] = val
  return res
}

const batchSize = 1

class Replicator extends EventEmitter {
  constructor (store, concurrency) {
    super()
    this._store = store
    this._fetching = {}
    this._stats = {
      tasksRequested: 0,
      tasksStarted: 0,
      tasksProcessed: 0,
    }
    this._buffer = []

    this._concurrency = concurrency || 128
    this._queue = {}
    this._q = new Set()

    // Flush the queue as an emergency switch
    this._flushTimer = setInterval(() => {
      if (this.tasksRunning === 0 && Object.keys(this._queue).length > 0) {
        logger.warn("Had to flush the queue!", Object.keys(this._queue).length, "items in the queue, ", this.tasksRequested, this.tasksFinished, " tasks requested/finished")
        setTimeout(() => this._processQueue(), 0)
      }
    }, 3000)
  }

  /**
   * Returns the number of tasks started during the life time
   * @return {[Integer]} [Number of tasks started]
   */
  get tasksRequested () {
    return this._stats.tasksRequested
  }

  /**
   * Returns the number of tasks started during the life time
   * @return {[Integer]} [Number of tasks running]
   */
  get tasksStarted () {
    return this._stats.tasksStarted
  }

  /**
   * Returns the number of tasks running currently
   * @return {[Integer]} [Number of tasks running]
   */
  get tasksRunning () {
    return this._stats.tasksStarted - this._stats.tasksProcessed
  }

  /**
   * Returns the number of tasks currently queued
   * @return {[Integer]} [Number of tasks queued]
   */
  get tasksQueued () {
    return Math.max(Object.keys(this._queue).length - this.tasksRunning, 0)
  }

  /**
   * Returns the number of tasks finished during the life time
   * @return {[Integer]} [Number of tasks finished]
   */
  get tasksFinished () {
    return this._stats.tasksProcessed
  }

  /**
   * Returns the hashes currently queued
   * @return {[Array<String>]} [Queued hashes]
   */
  getQueue () {
    return Object.values(this._queue)
  }

  /*
    Process new heads.
   */
  load (entries) {
    const notKnown = entry => !this._store._oplog.has(entry.hash || entry) && !this._queue[entry.hash || entry]

    try {
      entries
        .filter(notNull)
        .filter(notKnown)
        .forEach(this._addToQueue.bind(this))

      setTimeout(() => this._processQueue(), 0)
    } catch (e) {
      console.error(e)
    }
  }

  _addToQueue (entry) {
    const hash = entry.hash || entry

    if (this._store._oplog.has(hash) || this._fetching[hash] || this._queue[hash])
      return

    this._stats.tasksRequested += 1
    this._queue[hash] = entry
  }

  async _processQueue () {
    if (this.tasksRunning < this._concurrency) {
      const capacity = this._concurrency - this.tasksRunning
      const items = Object.values(this._queue).slice(0, capacity).filter(notNull)
      items.forEach(entry => delete this._queue[entry.hash || entry])

      const flattenAndGetUniques = (nexts) => nexts.reduce(flatMap, []).reduce(uniqueValues, {})
      const processValues = (nexts) => {
        const values = Object.values(nexts).filter(notNull)

        if ((items.length > 0 && this._buffer.length > 0)
          || (this.tasksRunning === 0 && this._buffer.length > 0)) {
            const logs = this._buffer.slice()
            this._buffer = []
            this.emit('load.end', logs)
        }

        if (values.length > 0)
          this.load(values)
      }

      return pMap(items, e => this._processOne(e))
        .then(flattenAndGetUniques)
        .then(processValues)
    }
  }

  async _processOne (entry) {
    const hash = entry.hash || entry

    if (this._store._oplog.has(hash) || this._fetching[hash])
      return

    this._fetching[hash] = hash
    this.emit('load.added', entry)
    this._stats.tasksStarted += 1

    const exclude = []
    const log = await Log.fromEntryHash(this._store._ipfs, hash, this._store._oplog.id, batchSize, exclude, this._store.key, this._store.access.write)
    this._buffer.push(log)

    const latest = log.values[0]
    delete this._queue[hash]

    // Mark this task as processed
    this._stats.tasksProcessed += 1

    // Notify subscribers that we made progress
    this.emit('load.progress', this._id, hash, latest, null, this._buffer.length)

    // Return all next pointers
    return log.values.map(getNext).reduce(flatMap, [])
  }
}

module.exports = Replicator
<|endoftext|>