<commit_before>var API_v1_2 = require('./API_v1_2');
var util = require('util');

// Public Functions
var API_v2_0 = function(v, storeT, storeL) {
  API_v1_2.apply(this, arguments);

  this.baseUrl = "https://api.mysportsfeeds.com/v2.0/pull";

  this.validFeeds = [
	  'seasonal_games',
    'daily_games',
    'daily_odds_gamelines',
    'weekly_games',
    'seasonal_dfs',
    'daily_dfs',
    'weekly_dfs',
    'seasonal_player_gamelogs',
    'daily_player_gamelogs',
    'weekly_player_gamelogs',
    'seasonal_team_gamelogs',
    'daily_team_gamelogs',
    'weekly_team_gamelogs',
    'game_boxscore',
    'game_playbyplay',
    'game_lineup',
    'current_season',
    'player_injuries',
    'latest_updates',
    'seasonal_team_stats',
    'seasonal_player_stats',
    'seasonal_venues',
    'players',
    'seasonal_standings',
    'odds_futures'
  ];
}

util.inherits(API_v2_0, API_v1_2);

API_v2_0.prototype.__determineUrl = function(league, season, feed, format, params) {
  if ( feed == "seasonal_games" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/games.' + format;

  } else if ( feed == "daily_games" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("date") ) {
      throw new Error("You must specify a 'date' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/date/' + params['date'] + '/games.' + format;

  } else if ( feed == "daily_odds_gamelines" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("date") ) {
      throw new Error("You must specify a 'date' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/date/' + params['date'] + '/odds_gamelines.' + format;

  } else if ( feed == "weekly_games" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("week") ) {
      throw new Error("You must specify a 'week' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/week/' + params['week'] + '/games.' + format;

  } else if ( feed == "seasonal_dfs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/dfs.' + format;

  } else if ( feed == "daily_dfs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("date") ) {
      throw new Error("You must specify a 'date' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/date/' + params['date'] + '/dfs.' + format;

  } else if ( feed == "weekly_dfs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("week") ) {
      throw new Error("You must specify a 'week' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/week/' + params['week'] + '/dfs.' + format;

  } else if ( feed == "seasonal_player_gamelogs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/player_gamelogs.' + format;

  } else if ( feed == "daily_player_gamelogs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("date") ) {
      throw new Error("You must specify a 'date' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/date/' + params['date'] + '/player_gamelogs.' + format;

  } else if ( feed == "weekly_player_gamelogs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("week") ) {
      throw new Error("You must specify a 'week' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/week/' + params['week'] + '/player_gamelogs.' + format;

  } else if ( feed == "seasonal_team_gamelogs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/team_gamelogs.' + format;

  } else if ( feed == "daily_team_gamelogs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("date") ) {
      throw new Error("You must specify a 'date' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/date/' + params['date'] + '/team_gamelogs.' + format;

  } else if ( feed == "weekly_team_gamelogs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("week") ) {
      throw new Error("You must specify a 'week' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/week/' + params['week'] + '/team_gamelogs.' + format;

  } else if ( feed == "game_boxscore" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("game") ) {
      throw new Error("You must specify a 'game' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/games/' + params['game'] + '/boxscore.' + format;

  } else if ( feed == "game_playbyplay" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("game") ) {
      throw new Error("You must specify a 'game' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/games/' + params['game'] + '/playbyplay.' + format;

  } else if ( feed == "game_lineup" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("game") ) {
      throw new Error("You must specify a 'game' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/games/' + params['game'] + '/lineup.' + format;

  } else if ( feed == "current_season" ) {

    return this.baseUrl + '/' + league + '/current_season.' + format;

  } else if ( feed == "player_injuries" ) {

    return this.baseUrl + '/' + league + '/injuries.' + format;

  } else if ( feed == "latest_updates" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/latest_updates.' + format;

  } else if ( feed == "seasonal_team_stats" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/team_stats_totals.' + format;

  } else if ( feed == "seasonal_player_stats" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/player_stats_totals.' + format;

  } else if ( feed == "seasonal_venues" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/venues.' + format;

  } else if ( feed == "players" ) {

    return this.baseUrl + '/' + league + '/players.' + format;

  } else if ( feed == "seasonal_standings" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/standings.' + format;

  }  else if ( feed == "odds_futures" ) {
    if ( !season ) {
      throw new Error("You must specify a season for this request.");
    }
    if ( !Object.keys(params).includes("date") ) {
      throw new Error("You must specify a 'date' param for this request.");
    }
    return this.baseUrl + '/' + league + '/' + season + '/date/' + params['date'] + '/odds_futures.' + format;
  } else {
	  throw new Error("Unrecognized feed '" + feed + "'.");
  }

}

exports = module.exports = API_v2_0;
<commit_msg>update base url to v2.1<commit_after>var API_v1_2 = require('./API_v1_2');
var util = require('util');

// Public Functions
var API_v2_0 = function(v, storeT, storeL) {
  API_v1_2.apply(this, arguments);

  this.baseUrl = "https://api.mysportsfeeds.com/v2.1/pull";

  this.validFeeds = [
	  'seasonal_games',
    'daily_games',
    'daily_odds_gamelines',
    'weekly_games',
    'seasonal_dfs',
    'daily_dfs',
    'weekly_dfs',
    'seasonal_player_gamelogs',
    'daily_player_gamelogs',
    'weekly_player_gamelogs',
    'seasonal_team_gamelogs',
    'daily_team_gamelogs',
    'weekly_team_gamelogs',
    'game_boxscore',
    'game_playbyplay',
    'game_lineup',
    'current_season',
    'player_injuries',
    'latest_updates',
    'seasonal_team_stats',
    'seasonal_player_stats',
    'seasonal_venues',
    'players',
    'seasonal_standings',
    'odds_futures'
  ];
}

util.inherits(API_v2_0, API_v1_2);

API_v2_0.prototype.__determineUrl = function(league, season, feed, format, params) {
  if ( feed == "seasonal_games" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/games.' + format;

  } else if ( feed == "daily_games" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("date") ) {
      throw new Error("You must specify a 'date' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/date/' + params['date'] + '/games.' + format;

  } else if ( feed == "daily_odds_gamelines" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("date") ) {
      throw new Error("You must specify a 'date' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/date/' + params['date'] + '/odds_gamelines.' + format;

  } else if ( feed == "weekly_games" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("week") ) {
      throw new Error("You must specify a 'week' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/week/' + params['week'] + '/games.' + format;

  } else if ( feed == "seasonal_dfs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/dfs.' + format;

  } else if ( feed == "daily_dfs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("date") ) {
      throw new Error("You must specify a 'date' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/date/' + params['date'] + '/dfs.' + format;

  } else if ( feed == "weekly_dfs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("week") ) {
      throw new Error("You must specify a 'week' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/week/' + params['week'] + '/dfs.' + format;

  } else if ( feed == "seasonal_player_gamelogs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/player_gamelogs.' + format;

  } else if ( feed == "daily_player_gamelogs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("date") ) {
      throw new Error("You must specify a 'date' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/date/' + params['date'] + '/player_gamelogs.' + format;

  } else if ( feed == "weekly_player_gamelogs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("week") ) {
      throw new Error("You must specify a 'week' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/week/' + params['week'] + '/player_gamelogs.' + format;

  } else if ( feed == "seasonal_team_gamelogs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/team_gamelogs.' + format;

  } else if ( feed == "daily_team_gamelogs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("date") ) {
      throw new Error("You must specify a 'date' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/date/' + params['date'] + '/team_gamelogs.' + format;

  } else if ( feed == "weekly_team_gamelogs" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("week") ) {
      throw new Error("You must specify a 'week' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/week/' + params['week'] + '/team_gamelogs.' + format;

  } else if ( feed == "game_boxscore" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("game") ) {
      throw new Error("You must specify a 'game' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/games/' + params['game'] + '/boxscore.' + format;

  } else if ( feed == "game_playbyplay" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("game") ) {
      throw new Error("You must specify a 'game' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/games/' + params['game'] + '/playbyplay.' + format;

  } else if ( feed == "game_lineup" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}
    if ( !Object.keys(params).includes("game") ) {
      throw new Error("You must specify a 'game' param for this request.");
    }

    return this.baseUrl + '/' + league + '/' + season + '/games/' + params['game'] + '/lineup.' + format;

  } else if ( feed == "current_season" ) {

    return this.baseUrl + '/' + league + '/current_season.' + format;

  } else if ( feed == "player_injuries" ) {

    return this.baseUrl + '/' + league + '/injuries.' + format;

  } else if ( feed == "latest_updates" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/latest_updates.' + format;

  } else if ( feed == "seasonal_team_stats" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/team_stats_totals.' + format;

  } else if ( feed == "seasonal_player_stats" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/player_stats_totals.' + format;

  } else if ( feed == "seasonal_venues" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/venues.' + format;

  } else if ( feed == "players" ) {

    return this.baseUrl + '/' + league + '/players.' + format;

  } else if ( feed == "seasonal_standings" ) {
    if ( !season ) {
	  throw new Error("You must specify a season for this request.");
	}

    return this.baseUrl + '/' + league + '/' + season + '/standings.' + format;

  }  else if ( feed == "odds_futures" ) {
    if ( !season ) {
      throw new Error("You must specify a season for this request.");
    }
    if ( !Object.keys(params).includes("date") ) {
      throw new Error("You must specify a 'date' param for this request.");
    }
    return this.baseUrl + '/' + league + '/' + season + '/date/' + params['date'] + '/odds_futures.' + format;
  } else {
	  throw new Error("Unrecognized feed '" + feed + "'.");
  }

}

exports = module.exports = API_v2_0;
<|endoftext|>


<commit_before>const facadeFieldFactories = require("./entryFields.js");
const { createFieldDescriptor, getEntryValueType, setEntryValueType } = require("./tools.js");
const Entry = require("../core/Entry.js");

/**
 * Add extra fields to a fields array that are not mentioned in a preset
 * Facades are creaded by presets which don't mention all property values (custom user
 * added items). This method adds the unmentioned items to the facade fields so that
 * they can be edited as well.
 * @param {Entry} entry An Entry instance
 * @param {Array.<EntryFacadeField>} fields An array of fields
 * @returns {Array.<EntryFacadeField>} A new array with all combined fields
 * @private
 */
function addExtraFieldsNonDestructive(entry, fields) {
    const exists = (propName, fieldType) =>
        fields.find(item => item.propertyType === fieldType && item.property === propName);
    const { properties = {}, attributes = {} } = entry._source;
    return [
        ...fields,
        ...Object.keys(properties)
            .filter(name => !exists(name, "property"))
            .map(name =>
                createFieldDescriptor(
                    entry, // Entry instance
                    "", // Title
                    "property", // Type
                    name, // Property name
                    { removeable: true }
                )
            ),
        ...Object.keys(attributes)
            .filter(name => !exists(name, "attribute"))
            .map(name =>
                createFieldDescriptor(
                    entry, // Entry instance
                    "", // Title
                    "attribute", // Type
                    name // Property name
                )
            )
    ];
}

/**
 * @typedef {Object} EntryHistoryItem
 * @property {String} property The property/attribute name
 * @property {String} propertyType Either "property" or "attribute"
 * @property {String|null} originalValue The original value or null if it did not exist
 *  before this change
 * @property {String|null} newValue The new value or null if it was deleted
 */

/**
 * Entry facade for data input
 * @typedef {Object} EntryFacade
 * @property {String} id - The entry ID
 * @property {String} type - The type of the facade
 * @property {Array.<EntryFacadeField>} fields - An array of fields
 * @property {String} parentID - The parent group ID
 * @property {Array.<EntryHistoryItem>} _history - Array of changes for all properties of
 *  the entry
 */

/**
 * Apply a facade field descriptor to an entry
 * Takes data from the descriptor and writes it to the entry.
 * @param {Entry} entry The entry to apply to
 * @param {EntryFacadeField} descriptor The descriptor object
 * @private
 */
function applyFieldDescriptor(entry, descriptor) {
    setEntryValue(entry, descriptor.propertyType, descriptor.property, descriptor.value, descriptor.valueType);
}

/**
 * Process a modified entry facade
 * @param {Entry} entry The entry to apply processed data on
 * @param {EntryFacade} facade The facade object
 * @memberof module:Buttercup
 */
function consumeEntryFacade(entry, facade) {
    const facadeType = getEntryFacadeType(entry);
    if (facade && facade.type) {
        const properties = entry.getProperty();
        const attributes = entry.getAttribute();
        if (facade.type !== facadeType) {
            throw new Error(`Failed consuming entry data: Expected type "${facadeType}" but received "${facade.type}"`);
        }
        // update data
        (facade.fields || []).forEach(field => applyFieldDescriptor(entry, field));
        // remove missing properties
        Object.keys(properties).forEach(propKey => {
            const correspondingField = facade.fields.find(
                ({ propertyType, property }) => propertyType === "property" && property === propKey
            );
            if (typeof correspondingField === "undefined") {
                entry.deleteProperty(propKey);
            }
        });
        // remove missing attributes
        Object.keys(attributes).forEach(attrKey => {
            const correspondingField = facade.fields.find(
                ({ propertyType, property }) => propertyType === "attribute" && property === attrKey
            );
            if (typeof correspondingField === "undefined") {
                entry.deleteAttribute(attrKey);
            }
        });
        return;
    }
    throw new Error("Failed consuming entry data: Invalid item passed as a facade");
}

/**
 * @typedef {Object} CreateEntryFacadeOptions
 * @property {String=} type - Optionally override the created facade type
 */

/**
 * Create a data/input facade for an Entry instance
 * @param {Entry=} entry The Entry instance
 * @param {CreateEntryFacadeOptions=} ops Options for the entry facade creation
 * @returns {EntryFacade} A newly created facade
 * @memberof module:Buttercup
 */
function createEntryFacade(entry, { type } = {}) {
    if (entry && entry.type !== "Entry") {
        throw new Error("Failed creating entry facade: Provided item is not an Entry");
    }
    const facadeType = type || getEntryFacadeType(entry);
    const createFields = facadeFieldFactories[facadeType];
    if (!createFields) {
        throw new Error(`Failed creating entry facade: No factory found for type "${facadeType}"`);
    }
    const fields = entry ? addExtraFieldsNonDestructive(entry, createFields(entry)) : createFields(entry);
    if (!fields.find(field => field.propertyType === "attribute" && field.property === FacadeTypeAttribute)) {
        const entryTypeField = createFieldDescriptor(
            entry, // Entry instance
            "", // Title
            "attribute", // Type
            FacadeTypeAttribute // Property name
        );
        entryTypeField.value = facadeType;
        fields.push(entryTypeField);
    }
    return {
        id: entry ? entry.id : null,
        type: facadeType,
        fields,
        parentID: entry ? entry.getGroup().id : null,
        _history: entry ? entry.getChanges() : []
    };
}

/**
 * Get the facade type for an entry
 * @param {Entry} entry The entry instance
 * @returns {String} The facade type
 * @private
 */
function getEntryFacadeType(entry) {
    if (!entry) {
        return "login";
    }
    return entry.getType();
}

/**
 * Set a value on an entry
 * @param {Entry} entry The entry instance
 * @param {String} property Type of property ("property"/"meta"/"attribute")
 * @param {String} name The property name
 * @param {String} value The value to set
 * @param {String=} valueType Value type to set
 * @throws {Error} Throws if the property type is not recognised
 * @private
 */
function setEntryValue(entry, property, name, value, valueType) {
    switch (property) {
        case "property":
            if (entry.getProperty(name) !== value) {
                // Only update if changed
                entry.setProperty(name, value);
            }
            break;
        case "attribute":
            if (entry.getAttribute(name) !== value) {
                // Only update if changed
                entry.setAttribute(name, value);
            }
            break;
        default:
            throw new Error(`Cannot set value: Unknown property type: ${property}`);
    }
    if (valueType && getEntryValueType(entry) !== valueType) {
        setEntryValueType(entry, name, valueType);
    }
}

module.exports = {
    consumeEntryFacade,
    createEntryFacade
};
<commit_msg>Fix tests<commit_after>const facadeFieldFactories = require("./entryFields.js");
const { createFieldDescriptor, getEntryValueType, setEntryValueType } = require("./tools.js");
const Entry = require("../core/Entry.js");

const { FacadeType: FacadeTypeAttribute } = Entry.Attributes;

/**
 * Add extra fields to a fields array that are not mentioned in a preset
 * Facades are creaded by presets which don't mention all property values (custom user
 * added items). This method adds the unmentioned items to the facade fields so that
 * they can be edited as well.
 * @param {Entry} entry An Entry instance
 * @param {Array.<EntryFacadeField>} fields An array of fields
 * @returns {Array.<EntryFacadeField>} A new array with all combined fields
 * @private
 */
function addExtraFieldsNonDestructive(entry, fields) {
    const exists = (propName, fieldType) =>
        fields.find(item => item.propertyType === fieldType && item.property === propName);
    const { properties = {}, attributes = {} } = entry._source;
    return [
        ...fields,
        ...Object.keys(properties)
            .filter(name => !exists(name, "property"))
            .map(name =>
                createFieldDescriptor(
                    entry, // Entry instance
                    "", // Title
                    "property", // Type
                    name, // Property name
                    { removeable: true }
                )
            ),
        ...Object.keys(attributes)
            .filter(name => !exists(name, "attribute"))
            .map(name =>
                createFieldDescriptor(
                    entry, // Entry instance
                    "", // Title
                    "attribute", // Type
                    name // Property name
                )
            )
    ];
}

/**
 * @typedef {Object} EntryHistoryItem
 * @property {String} property The property/attribute name
 * @property {String} propertyType Either "property" or "attribute"
 * @property {String|null} originalValue The original value or null if it did not exist
 *  before this change
 * @property {String|null} newValue The new value or null if it was deleted
 */

/**
 * Entry facade for data input
 * @typedef {Object} EntryFacade
 * @property {String} id - The entry ID
 * @property {String} type - The type of the facade
 * @property {Array.<EntryFacadeField>} fields - An array of fields
 * @property {String} parentID - The parent group ID
 * @property {Array.<EntryHistoryItem>} _history - Array of changes for all properties of
 *  the entry
 */

/**
 * Apply a facade field descriptor to an entry
 * Takes data from the descriptor and writes it to the entry.
 * @param {Entry} entry The entry to apply to
 * @param {EntryFacadeField} descriptor The descriptor object
 * @private
 */
function applyFieldDescriptor(entry, descriptor) {
    setEntryValue(entry, descriptor.propertyType, descriptor.property, descriptor.value, descriptor.valueType);
}

/**
 * Process a modified entry facade
 * @param {Entry} entry The entry to apply processed data on
 * @param {EntryFacade} facade The facade object
 * @memberof module:Buttercup
 */
function consumeEntryFacade(entry, facade) {
    const facadeType = getEntryFacadeType(entry);
    if (facade && facade.type) {
        const properties = entry.getProperty();
        const attributes = entry.getAttribute();
        if (facade.type !== facadeType) {
            throw new Error(`Failed consuming entry data: Expected type "${facadeType}" but received "${facade.type}"`);
        }
        // update data
        (facade.fields || []).forEach(field => applyFieldDescriptor(entry, field));
        // remove missing properties
        Object.keys(properties).forEach(propKey => {
            const correspondingField = facade.fields.find(
                ({ propertyType, property }) => propertyType === "property" && property === propKey
            );
            if (typeof correspondingField === "undefined") {
                entry.deleteProperty(propKey);
            }
        });
        // remove missing attributes
        Object.keys(attributes).forEach(attrKey => {
            const correspondingField = facade.fields.find(
                ({ propertyType, property }) => propertyType === "attribute" && property === attrKey
            );
            if (typeof correspondingField === "undefined") {
                entry.deleteAttribute(attrKey);
            }
        });
        return;
    }
    throw new Error("Failed consuming entry data: Invalid item passed as a facade");
}

/**
 * @typedef {Object} CreateEntryFacadeOptions
 * @property {String=} type - Optionally override the created facade type
 */

/**
 * Create a data/input facade for an Entry instance
 * @param {Entry=} entry The Entry instance
 * @param {CreateEntryFacadeOptions=} ops Options for the entry facade creation
 * @returns {EntryFacade} A newly created facade
 * @memberof module:Buttercup
 */
function createEntryFacade(entry, { type } = {}) {
    if (entry && entry.type !== "Entry") {
        throw new Error("Failed creating entry facade: Provided item is not an Entry");
    }
    const facadeType = type || getEntryFacadeType(entry);
    const createFields = facadeFieldFactories[facadeType];
    if (!createFields) {
        throw new Error(`Failed creating entry facade: No factory found for type "${facadeType}"`);
    }
    const fields = entry ? addExtraFieldsNonDestructive(entry, createFields(entry)) : createFields(entry);
    if (!fields.find(field => field.propertyType === "attribute" && field.property === FacadeTypeAttribute)) {
        const entryTypeField = createFieldDescriptor(
            entry, // Entry instance
            "", // Title
            "attribute", // Type
            FacadeTypeAttribute // Property name
        );
        entryTypeField.value = facadeType;
        fields.push(entryTypeField);
    }
    return {
        id: entry ? entry.id : null,
        type: facadeType,
        fields,
        parentID: entry ? entry.getGroup().id : null,
        _history: entry ? entry.getChanges() : []
    };
}

/**
 * Get the facade type for an entry
 * @param {Entry} entry The entry instance
 * @returns {String} The facade type
 * @private
 */
function getEntryFacadeType(entry) {
    if (!entry) {
        return "login";
    }
    return entry.getType();
}

/**
 * Set a value on an entry
 * @param {Entry} entry The entry instance
 * @param {String} property Type of property ("property"/"meta"/"attribute")
 * @param {String} name The property name
 * @param {String} value The value to set
 * @param {String=} valueType Value type to set
 * @throws {Error} Throws if the property type is not recognised
 * @private
 */
function setEntryValue(entry, property, name, value, valueType) {
    switch (property) {
        case "property":
            if (entry.getProperty(name) !== value) {
                // Only update if changed
                entry.setProperty(name, value);
            }
            break;
        case "attribute":
            if (entry.getAttribute(name) !== value) {
                // Only update if changed
                entry.setAttribute(name, value);
            }
            break;
        default:
            throw new Error(`Cannot set value: Unknown property type: ${property}`);
    }
    if (valueType && getEntryValueType(entry) !== valueType) {
        setEntryValueType(entry, name, valueType);
    }
}

module.exports = {
    consumeEntryFacade,
    createEntryFacade
};
<|endoftext|>


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

define.class("$server/composition",function(require, $ui$, checkbox, screen, label, view, icon, cadgrid) {

		this.render = function() {
			return [
				screen(
					cadgrid({
							name:"grid",
							bgcolor:"#4e4e4e",
							gridsize:8,
							majorevery:5,
							majorline:vec4(0.34117648005485535,0.34117648005485535,0.34117648005485535,1),
							minorline:vec4(0.2823529541492462,0.2823529541492462,0.2823529541492462,1),
							alignself:'stretch',
							flexdirection:'row',
							alignitems:"center",
							justifycontent:'space-around'
						},
						view({ flexdirection:"column", flex:0 },
							label({text:"Basic Usage", marginbottom:20}),
							checkbox({
								bgcolor:"transparent",
								textcolor:"transparent",
								textactivecolor:"white",
								click:function(){
									this.find("status").text = "Value is: " + this.value
								}
							}),
							label({name:"status", text:"Value is: false", fontsize:12, margintop:5})
						),
						view({ flexdirection:"column", flex:0 },
							checkbox({
								icon:"square-o",
								text:"Checkbox One Unselected (FALSE)",
								borderwidth:0,
								bgcolor:"transparent",
								textcolor:"white",
								textactivecolor:"white",
								click:function(){
									this.icon = this.value ? "check-square-o" : "square-o"
									this.text = "Checkbox One " + (this.value ? "Selected (TRUE)" : "Unselected (FALSE)")
								}
							}),
							checkbox({
								icon:"square-o",
								text:"Checkbox Two Unselected (FALSE)",
								borderwidth:0,
								bgcolor:"transparent",
								textcolor:"white",
								textactivecolor:"white",
								click:function(){
									this.icon = this.value ? "check-square-o" : "square-o"
									this.text = "Checkbox Two " + (this.value ? "Selected (TRUE)" : "Unselected (FALSE)")
								}
							}),
							checkbox({
								value:true,
								icon:"check-square-o",
								text:"Checkbox Three Unseected (TRUE)",
								borderwidth:0,
								bgcolor:"transparent",
								textcolor:"white",
								textactivecolor:"white",
								click:function(){
									this.icon = this.value ? "check-square-o" : "square-o"
									this.text = "Checkbox Three " + (this.value ? "Selected (TRUE)" : "Unselected (FALSE)")
								}
							}),
							checkbox({
								value:true,
								icon:"check-square-o",
								text:"Checkbox Four Unseected (TRUE)",
								borderwidth:0,
								bgcolor:"transparent",
								textcolor:"white",
								textactivecolor:"white",
								click:function(){
									this.icon = this.value ? "check-square-o" : "square-o"
									this.text = "Checkbox Four " + (this.value ? "Selected (TRUE)" : "Unselected (FALSE)")
								}
							})
						),
						view({ flexdirection:"column", flex:0 },
							label({text:"Icon w/ text", marginbottom:20}),
							checkbox({
								icon:"star-o",
								text:"My value is: false",
								bgcolor:"transparent",
								borderwidth:0,
								textcolor:"white",
								textactivecolor:"pink",
								fontsize:30,
								pickalpha:-1,
								click:function(){
									this.icon = this.value ? "star" : "star-o";
									this.text = "My value is: " + this.value
								}
							})
						),
						view({ flexdirection:"column", flex:0 },
							label({text:"Background Image", marginbottom:20}),
							checkbox({
								icon:undefined,
								width:40,
								bgimage:"$resources/textures/purplecloud.png",
								bgimagemode:"stretch",
								click:function(){
									this.bgimage = this.value ? "$resources/textures/bluecloud.png" : "$resources/textures/purplecloud.png";
									this.find("status").text = "Value is: " + this.value
								}
							}),
							label({name:"status", text:"Value is: false", fontsize:12, margintop:5})
						)
					)
				)
			]
		}
	}
)
<commit_msg>fix typos<commit_after>/* DreemGL is a collaboration between Teeming Society & Samsung Electronics, sponsored by Samsung and others.
 Copyright 2015-2016 Teeming Society. Licensed under the Apache License, Version 2.0 (the "License"); You may not use this file except in compliance with the License.
 You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing,
 software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and limitations under the License.*/

define.class("$server/composition",function(require, $ui$, checkbox, screen, label, view, icon, cadgrid) {

		this.render = function() {
			return [
				screen(
					cadgrid({
							name:"grid",
							bgcolor:"#4e4e4e",
							gridsize:8,
							majorevery:5,
							majorline:vec4(0.34117648005485535,0.34117648005485535,0.34117648005485535,1),
							minorline:vec4(0.2823529541492462,0.2823529541492462,0.2823529541492462,1),
							alignself:'stretch',
							flexdirection:'row',
							alignitems:"center",
							justifycontent:'space-around'
						},
						view({ flexdirection:"column", flex:0 },
							label({text:"Basic Usage", marginbottom:20}),
							checkbox({
								bgcolor:"transparent",
								textcolor:"transparent",
								textactivecolor:"white",
								click:function(){
									this.find("status").text = "Value is: " + this.value
								}
							}),
							label({name:"status", text:"Value is: false", fontsize:12, margintop:5})
						),
						view({ flexdirection:"column", flex:0 },
							checkbox({
								icon:"square-o",
								text:"Checkbox One Unselected (FALSE)",
								borderwidth:0,
								bgcolor:"transparent",
								textcolor:"white",
								textactivecolor:"white",
								click:function(){
									this.icon = this.value ? "check-square-o" : "square-o"
									this.text = "Checkbox One " + (this.value ? "Selected (TRUE)" : "Unselected (FALSE)")
								}
							}),
							checkbox({
								icon:"square-o",
								text:"Checkbox Two Unselected (FALSE)",
								borderwidth:0,
								bgcolor:"transparent",
								textcolor:"white",
								textactivecolor:"white",
								click:function(){
									this.icon = this.value ? "check-square-o" : "square-o"
									this.text = "Checkbox Two " + (this.value ? "Selected (TRUE)" : "Unselected (FALSE)")
								}
							}),
							checkbox({
								value:true,
								icon:"check-square-o",
								text:"Checkbox Three Selected (TRUE)",
								borderwidth:0,
								bgcolor:"transparent",
								textcolor:"white",
								textactivecolor:"white",
								click:function(){
									this.icon = this.value ? "check-square-o" : "square-o"
									this.text = "Checkbox Three " + (this.value ? "Selected (TRUE)" : "Unselected (FALSE)")
								}
							}),
							checkbox({
								value:true,
								icon:"check-square-o",
								text:"Checkbox Four Selected (TRUE)",
								borderwidth:0,
								bgcolor:"transparent",
								textcolor:"white",
								textactivecolor:"white",
								click:function(){
									this.icon = this.value ? "check-square-o" : "square-o"
									this.text = "Checkbox Four " + (this.value ? "Selected (TRUE)" : "Unselected (FALSE)")
								}
							})
						),
						view({ flexdirection:"column", flex:0 },
							label({text:"Icon w/ text", marginbottom:20}),
							checkbox({
								icon:"star-o",
								text:"My value is: false",
								bgcolor:"transparent",
								borderwidth:0,
								textcolor:"white",
								textactivecolor:"pink",
								fontsize:30,
								pickalpha:-1,
								click:function(){
									this.icon = this.value ? "star" : "star-o";
									this.text = "My value is: " + this.value
								}
							})
						),
						view({ flexdirection:"column", flex:0 },
							label({text:"Background Image", marginbottom:20}),
							checkbox({
								icon:undefined,
								width:40,
								bgimage:"$resources/textures/purplecloud.png",
								bgimagemode:"stretch",
								click:function(){
									this.bgimage = this.value ? "$resources/textures/bluecloud.png" : "$resources/textures/purplecloud.png";
									this.find("status").text = "Value is: " + this.value
								}
							}),
							label({name:"status", text:"Value is: false", fontsize:12, margintop:5})
						)
					)
				)
			]
		}
	}
)
<|endoftext|>


<commit_before>#!/usr/bin/env node

var sc = require('skale-engine').context();

sc.range(6).map(a => a*a).reduce((a,b) => a+b, 0).then(function (res) {
	console.log(res);
	console.assert(res == 55);
	sc.end();
});
<commit_msg>fix broken arrow function example<commit_after>#!/usr/bin/env node

var sc = require('skale-engine').context();

sc.range(6).map(a => a*a).reduce((a,b) => a+b, 0)
  .on('data', function (res) {
	console.log(res);
	console.assert(res == 55);
	sc.end();
});
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';

import GridTable from './GridTable';
import GridBody from './GridBody';
import GridRow from './GridRow';
import GridCell from './GridCell';
import GridColumnHeader from './GridColumnHeader';
import GridHeader from './GridHeader';
import GridHeaderCell from './GridHeaderCell';
import BaseCellRenderer from './BaseCellRenderer';
import ObjectCellRenderer from './ObjectCellRenderer';
import ArrayCellRenderer from './ArrayCellRenderer';
import Filter from './Filter';
import ToolbarDefault from './ToolbarDefault';
import Pager from './Pager';

import { DESCENDING, DEFAULT_ITEMS_PER_PAGE } from './constants';

import { filterConfigFromProp, sortConfigFromProp } from './utils';
import { columnsConfig as columnsConfigPropType, sortConfig as sortConfigPropType, filterConfig as filterConfigPropType, globalConfig as globalConfigPropType } from './proptypes';

const filterObjects = (objects, filterConfig, columnConfig, defaultFilterFunction, defaultDisplayValueGetter) => {

    //Filter objects based on supplied filter strings
    let filters = {};
    const columnNamesWithFilter = filterConfig.map((filterObject) => {
        filters[filterObject.columnName] = filterObject.expression;
        return filterObject.columnName;
    });

    return objects.filter((currentObjectToBeFiltered) => {
        for (let i in columnNamesWithFilter) {
            let currentColumn = columnNamesWithFilter[i];

            //Objects shall be filtered by the visible value by default.
            //Therefore get the correct displayValueGetter for every column.
            let displayValueGetter = defaultDisplayValueGetter;
            if (columnConfig !== undefined
                && columnConfig[currentColumn] !== undefined
                && columnConfig[currentColumn].displayValueGetter !== undefined){
                displayValueGetter = columnConfig[currentColumn].displayValueGetter;
            }

            let filterFunction;
            if (columnConfig !== undefined
                && columnConfig[currentColumn] !== undefined
                && columnConfig[currentColumn].filterFunction !== undefined) {
                filterFunction = columnConfig[currentColumn].filterFunction;
            } else {
                filterFunction = defaultFilterFunction;
            }
            if(!(filterFunction({
                value: currentObjectToBeFiltered[currentColumn],
                expression: filters[currentColumn],
                displayValue: displayValueGetter({value: currentObjectToBeFiltered[currentColumn]})
            }))){
                return false;
            }
        }
        return true;
    });
};

const sortObjects = (objects, sort, columnConfig, defaultSortValueGetter)=>{
    const { columnName, order } = sort;
    let sortValueGetter;
    if (columnConfig !== undefined
            && columnConfig[columnName] !== undefined
            && columnConfig[columnName].sortValueGetter !== undefined) {
        sortValueGetter = columnConfig[columnName].sortValueGetter;
    } else {
        sortValueGetter = defaultSortValueGetter;
    }

    // temporary array holds objects with position and sort-value
    let mapped = objects.map(function(el, i) {
        const value = sortValueGetter({value: el[columnName], object: el});
        return { index: i, value: value };
    });

    //reverse order on "descending" sort option
    const multplier = order === DESCENDING ? -1 : 1;

    // sorting the mapped array containing the reduced values
    mapped.sort((a, b) => {
        if (a.value == null){
            return multplier * -1;
        }
        if (b.value == null){
            return multplier * 1;
        }
        return multplier * (+(a.value > b.value) || +(a.value === b.value) - 1);
    });

    // container for the resulting order
    return mapped.map(function(el){
        return objects[el.index];
    });

};

const extractColumnKeys = (objects, columnConfig, showColumnsWithoutConfig)=>{
    //Generate array with selected column-names in configured order
    if (objects.length > 0){
        let availableColumnKeys = Object.keys(columnConfig).filter((columnName)=> columnConfig[columnName].show !== false);
        if (showColumnsWithoutConfig){
            Object.keys(objects[0]).forEach((columnName)=>{
                if ((columnConfig[columnName] === undefined || columnConfig[columnName].show !== false) && availableColumnKeys.indexOf(columnName) === -1){
                    availableColumnKeys.push(columnName);
                }
            });
        }
        return availableColumnKeys.sort((a,b) => {
            const configForA = columnConfig[a];
            const configForB = columnConfig[b];
            const valueForA = configForA && configForA.order != null ? configForA.order : 1000;
            const valueForB = configForB && configForB.order != null ? configForB.order : 1000;
            return valueForA-valueForB;
        });
    } else {
        return [];
    }
};

//Find id-column, or enhance objects with ids
const getOrCreateIdColumn = (objects, columns)=> {
    //check if explicit id-column is set in column-config
    const idColumn = Object.keys(columns).find((key)=>{
        if(columns[key].id){
            return true;
        }
    });

    if (idColumn){
        return idColumn;
    } else if (objects.length > 0 && objects[0].id !== undefined) {
        //check if objects have a id-property
        return 'id';
    } else {
        //TODO: use some type of hashing
        //generate id-property
        let index = 0;
        objects.map((object) => {
            object.id = index++;
        });
        return 'id';
    }
};

const Ardagryd = (props)=>{
    //Merge custom and default config
    const config = Object.assign({}, defaultConfig, props.config);

    //Get components from config
    const Grid = config.grid;
    const GridHeader = config.header;

    const Toolbar = config.toolbar;
    const GridBody = config.body;
    const ColumnHeader = config.columnHeader;

    //Get custom column-configuration
    const columnConfig = props.columns;

    config.eventHandler = props.dispatch;

    const idColumn = getOrCreateIdColumn(props.objects,columnConfig);

    //filter function
    const filterFunction = config.filterFunction;
    const filterConfig = filterConfigFromProp(props.filter);

    const displayValueGetter = config.displayValueGetter;

    let objects = filterObjects(props.objects, filterConfig, columnConfig, filterFunction, displayValueGetter);

    //Columns to show
    const columnKeys = extractColumnKeys(props.objects, columnConfig, config.showColumnsWithoutConfig);

    //Sort

    //default order

    const sort = sortConfigFromProp(props.sort);

    //check for sort configuration

    if (sort){
        objects = sortObjects(objects, sort, columnConfig, config.sortValueGetter);
    }

    let pagedObjects;
    const paging = config.paging;
    const skip = props.skip;
    const usePaging = paging !== undefined && paging !== false && objects.length > paging;
    let pager = null;
    if (usePaging){
        if (paging < 1){
            throw new Error(`Invalid value for config.paging: ${paging}`);
        }
        pagedObjects = objects.slice(skip, skip+paging);

        const rest = objects.length % paging;
        let numberOfPages = objects.length / paging;
        if(rest !== 0){
            numberOfPages = Math.floor(numberOfPages + 1);
        }
        const activePageNumber = (skip / paging) + 1;
        const changeHandler = (pageNumber) => {
            const skipNumber = (pageNumber - 1)  * paging;
            config.eventHandler({
                type: 'change-page',
                skip: skipNumber
            });
        };

        pager = <Pager currentPage={activePageNumber} numberOfPages={numberOfPages} onChange={changeHandler} />;
    } else {
        pagedObjects = objects;
    }

    return (

            { pager }
            <Grid>
                <GridHeader>
                    <ColumnHeader columns={columnConfig} config={config} columnKeys={columnKeys} sort={sort} />
                    { config.showToolbar &&
                        <Toolbar config={config} columnKeys={columnKeys} columns={columnConfig} filter={filterConfig} />
                    }
                </GridHeader>
                <GridBody idColumn={idColumn} objects={pagedObjects} columns={columnConfig} config={config} columnKeys={columnKeys}/>
            </Grid>

    );
};

const defaultConfig = {
    grid: GridTable,
    body: GridBody,
    row: GridRow,
    cell: GridCell,
    columnHeader: GridColumnHeader,
    header: GridHeader,
    columnHeaderCell: GridHeaderCell,
    cellRendererBase: BaseCellRenderer,
    cellRendererObject: ObjectCellRenderer,
    cellRendererArray: ArrayCellRenderer,
    filter: Filter,
    toolbar: ToolbarDefault,
    showToolbar: true,
    showColumnsWithoutConfig: true, //show all columns which are not explicitly hidden
    paging: DEFAULT_ITEMS_PER_PAGE,
    displayValueGetter: ({value}) => value,
    sortValueGetter: ({value}) => {
        if (value){
            const valueType = typeof value;
            switch (valueType){
                case 'number':
                case 'boolean':
                    return value;
                case 'string':
                    return value.toLowerCase();
                default:
                    return JSON.stringify(value).toLowerCase();
            }
        }
        return value;
    },
    filterFunction: ({displayValue, expression}) => {
        if (!(typeof expression === 'string') || expression === '' || expression === undefined || expression === null){return true;}
        if (displayValue === undefined || displayValue === null) {return false;}
        if ((typeof displayValue === 'string' ) && displayValue.toLowerCase().indexOf(expression.toLowerCase()) === -1){
            return false;
        } else if ((typeof displayValue === 'object') || (typeof displayValue === 'number') ){
            return JSON.stringify(displayValue).toLowerCase().indexOf(expression.toLowerCase()) !== -1;
        }
        else return true;
    }
};

Ardagryd.defaultProps = {
    config: {},
    columns: {},
    dispatch: () => {}
};

if (process.env.NODE_ENV !== 'production'){
    Ardagryd.propTypes = {
        objects: PropTypes.arrayOf(PropTypes.object),
        config: globalConfigPropType,
        columns: columnsConfigPropType,
        dispatch: PropTypes.func.isRequired,
        sort: sortConfigPropType,
        filter: filterConfigPropType,
        skip: PropTypes.number
    };
}

export default Ardagryd;
<commit_msg>pass current object to filterFunction<commit_after>import React, { PropTypes } from 'react';

import GridTable from './GridTable';
import GridBody from './GridBody';
import GridRow from './GridRow';
import GridCell from './GridCell';
import GridColumnHeader from './GridColumnHeader';
import GridHeader from './GridHeader';
import GridHeaderCell from './GridHeaderCell';
import BaseCellRenderer from './BaseCellRenderer';
import ObjectCellRenderer from './ObjectCellRenderer';
import ArrayCellRenderer from './ArrayCellRenderer';
import Filter from './Filter';
import ToolbarDefault from './ToolbarDefault';
import Pager from './Pager';

import { DESCENDING, DEFAULT_ITEMS_PER_PAGE } from './constants';

import { filterConfigFromProp, sortConfigFromProp } from './utils';
import { columnsConfig as columnsConfigPropType, sortConfig as sortConfigPropType, filterConfig as filterConfigPropType, globalConfig as globalConfigPropType } from './proptypes';

const filterObjects = (objects, filterConfig, columnConfig, defaultFilterFunction, defaultDisplayValueGetter) => {

    //Filter objects based on supplied filter strings
    let filters = {};
    const columnNamesWithFilter = filterConfig.map((filterObject) => {
        filters[filterObject.columnName] = filterObject.expression;
        return filterObject.columnName;
    });

    return objects.filter((currentObjectToBeFiltered) => {
        for (let i in columnNamesWithFilter) {
            let currentColumn = columnNamesWithFilter[i];

            //Objects shall be filtered by the visible value by default.
            //Therefore get the correct displayValueGetter for every column.
            let displayValueGetter = defaultDisplayValueGetter;
            if (columnConfig !== undefined
                && columnConfig[currentColumn] !== undefined
                && columnConfig[currentColumn].displayValueGetter !== undefined){
                displayValueGetter = columnConfig[currentColumn].displayValueGetter;
            }

            let filterFunction;
            if (columnConfig !== undefined
                && columnConfig[currentColumn] !== undefined
                && columnConfig[currentColumn].filterFunction !== undefined) {
                filterFunction = columnConfig[currentColumn].filterFunction;
            } else {
                filterFunction = defaultFilterFunction;
            }
            if(!(filterFunction({
                object: currentObjectToBeFiltered,
                value: currentObjectToBeFiltered[currentColumn],
                expression: filters[currentColumn],
                displayValue: displayValueGetter({value: currentObjectToBeFiltered[currentColumn]})
            }))){
                return false;
            }
        }
        return true;
    });
};

const sortObjects = (objects, sort, columnConfig, defaultSortValueGetter)=>{
    const { columnName, order } = sort;
    let sortValueGetter;
    if (columnConfig !== undefined
            && columnConfig[columnName] !== undefined
            && columnConfig[columnName].sortValueGetter !== undefined) {
        sortValueGetter = columnConfig[columnName].sortValueGetter;
    } else {
        sortValueGetter = defaultSortValueGetter;
    }

    // temporary array holds objects with position and sort-value
    let mapped = objects.map(function(el, i) {
        const value = sortValueGetter({value: el[columnName], object: el});
        return { index: i, value: value };
    });

    //reverse order on "descending" sort option
    const multplier = order === DESCENDING ? -1 : 1;

    // sorting the mapped array containing the reduced values
    mapped.sort((a, b) => {
        if (a.value == null){
            return multplier * -1;
        }
        if (b.value == null){
            return multplier * 1;
        }
        return multplier * (+(a.value > b.value) || +(a.value === b.value) - 1);
    });

    // container for the resulting order
    return mapped.map(function(el){
        return objects[el.index];
    });

};

const extractColumnKeys = (objects, columnConfig, showColumnsWithoutConfig)=>{
    //Generate array with selected column-names in configured order
    if (objects.length > 0){
        let availableColumnKeys = Object.keys(columnConfig).filter((columnName)=> columnConfig[columnName].show !== false);
        if (showColumnsWithoutConfig){
            Object.keys(objects[0]).forEach((columnName)=>{
                if ((columnConfig[columnName] === undefined || columnConfig[columnName].show !== false) && availableColumnKeys.indexOf(columnName) === -1){
                    availableColumnKeys.push(columnName);
                }
            });
        }
        return availableColumnKeys.sort((a,b) => {
            const configForA = columnConfig[a];
            const configForB = columnConfig[b];
            const valueForA = configForA && configForA.order != null ? configForA.order : 1000;
            const valueForB = configForB && configForB.order != null ? configForB.order : 1000;
            return valueForA-valueForB;
        });
    } else {
        return [];
    }
};

//Find id-column, or enhance objects with ids
const getOrCreateIdColumn = (objects, columns)=> {
    //check if explicit id-column is set in column-config
    const idColumn = Object.keys(columns).find((key)=>{
        if(columns[key].id){
            return true;
        }
    });

    if (idColumn){
        return idColumn;
    } else if (objects.length > 0 && objects[0].id !== undefined) {
        //check if objects have a id-property
        return 'id';
    } else {
        //TODO: use some type of hashing
        //generate id-property
        let index = 0;
        objects.map((object) => {
            object.id = index++;
        });
        return 'id';
    }
};

const Ardagryd = (props)=>{
    //Merge custom and default config
    const config = Object.assign({}, defaultConfig, props.config);

    //Get components from config
    const Grid = config.grid;
    const GridHeader = config.header;

    const Toolbar = config.toolbar;
    const GridBody = config.body;
    const ColumnHeader = config.columnHeader;

    //Get custom column-configuration
    const columnConfig = props.columns;

    config.eventHandler = props.dispatch;

    const idColumn = getOrCreateIdColumn(props.objects,columnConfig);

    //filter function
    const filterFunction = config.filterFunction;
    const filterConfig = filterConfigFromProp(props.filter);

    const displayValueGetter = config.displayValueGetter;

    let objects = filterObjects(props.objects, filterConfig, columnConfig, filterFunction, displayValueGetter);

    //Columns to show
    const columnKeys = extractColumnKeys(props.objects, columnConfig, config.showColumnsWithoutConfig);

    //Sort

    //default order

    const sort = sortConfigFromProp(props.sort);

    //check for sort configuration

    if (sort){
        objects = sortObjects(objects, sort, columnConfig, config.sortValueGetter);
    }

    let pagedObjects;
    const paging = config.paging;
    const skip = props.skip;
    const usePaging = paging !== undefined && paging !== false && objects.length > paging;
    let pager = null;
    if (usePaging){
        if (paging < 1){
            throw new Error(`Invalid value for config.paging: ${paging}`);
        }
        pagedObjects = objects.slice(skip, skip+paging);

        const rest = objects.length % paging;
        let numberOfPages = objects.length / paging;
        if(rest !== 0){
            numberOfPages = Math.floor(numberOfPages + 1);
        }
        const activePageNumber = (skip / paging) + 1;
        const changeHandler = (pageNumber) => {
            const skipNumber = (pageNumber - 1)  * paging;
            config.eventHandler({
                type: 'change-page',
                skip: skipNumber
            });
        };

        pager = <Pager currentPage={activePageNumber} numberOfPages={numberOfPages} onChange={changeHandler} />;
    } else {
        pagedObjects = objects;
    }

    return (

            { pager }
            <Grid>
                <GridHeader>
                    <ColumnHeader columns={columnConfig} config={config} columnKeys={columnKeys} sort={sort} />
                    { config.showToolbar &&
                        <Toolbar config={config} columnKeys={columnKeys} columns={columnConfig} filter={filterConfig} />
                    }
                </GridHeader>
                <GridBody idColumn={idColumn} objects={pagedObjects} columns={columnConfig} config={config} columnKeys={columnKeys}/>
            </Grid>

    );
};

const defaultConfig = {
    grid: GridTable,
    body: GridBody,
    row: GridRow,
    cell: GridCell,
    columnHeader: GridColumnHeader,
    header: GridHeader,
    columnHeaderCell: GridHeaderCell,
    cellRendererBase: BaseCellRenderer,
    cellRendererObject: ObjectCellRenderer,
    cellRendererArray: ArrayCellRenderer,
    filter: Filter,
    toolbar: ToolbarDefault,
    showToolbar: true,
    showColumnsWithoutConfig: true, //show all columns which are not explicitly hidden
    paging: DEFAULT_ITEMS_PER_PAGE,
    displayValueGetter: ({value}) => value,
    sortValueGetter: ({value}) => {
        if (value){
            const valueType = typeof value;
            switch (valueType){
                case 'number':
                case 'boolean':
                    return value;
                case 'string':
                    return value.toLowerCase();
                default:
                    return JSON.stringify(value).toLowerCase();
            }
        }
        return value;
    },
    filterFunction: ({displayValue, expression}) => {
        if (!(typeof expression === 'string') || expression === '' || expression === undefined || expression === null){return true;}
        if (displayValue === undefined || displayValue === null) {return false;}
        if ((typeof displayValue === 'string' ) && displayValue.toLowerCase().indexOf(expression.toLowerCase()) === -1){
            return false;
        } else if ((typeof displayValue === 'object') || (typeof displayValue === 'number') ){
            return JSON.stringify(displayValue).toLowerCase().indexOf(expression.toLowerCase()) !== -1;
        }
        else return true;
    }
};

Ardagryd.defaultProps = {
    config: {},
    columns: {},
    dispatch: () => {}
};

if (process.env.NODE_ENV !== 'production'){
    Ardagryd.propTypes = {
        objects: PropTypes.arrayOf(PropTypes.object),
        config: globalConfigPropType,
        columns: columnsConfigPropType,
        dispatch: PropTypes.func.isRequired,
        sort: sortConfigPropType,
        filter: filterConfigPropType,
        skip: PropTypes.number
    };
}

export default Ardagryd;
<|endoftext|>


<commit_before>'use strict';

var tileReduce = require('../../src');
var path = require('path');

tileReduce({
  bbox: [-131.693128, 30.664709, -100.681513, 55.35384],
  zoom: 12,
  map: path.join(__dirname, 'diff.js'),
  sources: [
    {name: 'osm',   mbtiles: path.join(__dirname, '../../../mbtiles/us-west.mbtiles')},
    {name: 'tiger', mbtiles: path.join(__dirname, '../../../mbtiles/tiger2015.mbtiles')}
  ]
})
.on('start', function() {
  // null feature is a hack to be able to write each feature starting with a comma
  process.stdout.write('{"type": "FeatureCollection", "features": [{"type": "Feature", "geometry": null, "properties": null}');
})
.on('end', function() {
  process.stdout.write(']}');
});
<commit_msg>use tile list in diff processor<commit_after>'use strict';

var tileReduce = require('../../src');
var path = require('path');

tileReduce({
  tiles: path.join(__dirname, '../../../mbtiles/us-west.txt'),
  zoom: 12,
  map: path.join(__dirname, 'diff.js'),
  sources: [
    {name: 'osm',   mbtiles: path.join(__dirname, '../../../mbtiles/us-west.mbtiles')},
    {name: 'tiger', mbtiles: path.join(__dirname, '../../../mbtiles/tiger2015.mbtiles')}
  ]
})
.on('start', function() {
  // null feature is a hack to be able to write each feature starting with a comma
  process.stdout.write('{"type": "FeatureCollection", "features": [{"type": "Feature", "geometry": null, "properties": null}');
})
.on('end', function() {
  process.stdout.write(']}');
});
<|endoftext|>


<commit_before>'use strict';
const libui = require('..');

let setIntervalHandle = null;
let setIntervalLast = Date.now();

const win = new libui.UiWindow('Event loop tests', 800, 600, false);
win.margined = 1;

const box = new libui.UiVerticalBox();
box.padded = true;
win.setChild(box);

const setIntervalMs = new libui.UiSlider(0, 1000);
setIntervalMs.onChanged(setIntervalChanged);
setIntervalMs.value = 0;

const form = new libui.UiForm();
form.padded = true;
form.append('setInterval', setIntervalMs, 0);
form.append('actions', makeToolbar(), 0);
box.append(form, true);

const log = new libui.UiMultilineEntry();
box.append(log, true);

win.onClosing(() => {
	if (setIntervalHandle !== null) {
		clearInterval(setIntervalHandle);
		setIntervalHandle = null;
	}
	win.close();
	libui.stopLoop();
});

win.show();
libui.startLoop();

function logAppend(line) {
	const lines = log.text.split('\n');
	if (lines.length > 20) {
		log.text = lines.slice(1).join('\n');
	}
	log.append(line + '\n');
}

function setIntervalChanged() {
	const ms = setIntervalMs.value;
	if (setIntervalHandle !== null) {
		clearInterval(setIntervalHandle);
		setIntervalHandle = null;
	}

	if (ms > 0) {
		setIntervalHandle = setInterval(() => {
			const now = Date.now();
			const elapsed = now - setIntervalLast;
			logAppend(`setInterval:${now} - elapsed ${elapsed} ms`);
			setIntervalLast = now;
		}, ms);
	}
}

function makeToolbar() {
	const toolbar = new libui.UiHorizontalBox();
	toolbar.padded = true;

	const btnSetImmediate = new libui.UiButton('setImmediate');
	btnSetImmediate.onClicked(() => {
		const now = Date.now();
		setImmediate(() => {
			const elapsed = Date.now() - now;
			logAppend(`setImmediate:${now} - elapsed ${elapsed} ms`);
		});
	});

	toolbar.append(btnSetImmediate, false);

	const btnPromise = new libui.UiButton('Promise');
	btnPromise.onClicked(() => {
		const now = Date.now();

		Promise.resolve(true).then(() => {
			const elapsed = Date.now() - now;
			logAppend(`Promise: ${now} - elapsed ${elapsed} ms`);
		});
	});

	toolbar.append(btnPromise, false);

	const btnNextTick = new libui.UiButton('NextTick');
	btnNextTick.onClicked(() => {
		const now = Date.now();
		process.nextTick(() => {
			const elapsed = Date.now() - now;
			logAppend(`nextTick:${now} - elapsed ${elapsed} ms`);
		});
	});

	toolbar.append(btnNextTick, false);

	const btnReadFile = new libui.UiButton('ReadFile');
	btnReadFile.onClicked(() => {
		const {createReadStream} = require('fs');
		const stream = createReadStream('/tmp/big');
		let i = 0;
		stream.on('data', data => {
			logAppend(`ReadFile: chunk ${i++} - ` + data.length);
		});
	});

	toolbar.append(btnReadFile, false);

	return toolbar;
}
<commit_msg>Add http server<commit_after>'use strict';
const libui = require('..');

let setIntervalHandle = null;
let lastTimeout = 0;
let setIntervalLast = Date.now();

const win = new libui.UiWindow('Event loop tests', 800, 600, false);
win.margined = 1;

const box = new libui.UiVerticalBox();
box.padded = true;
win.setChild(box);

const setIntervalMs = new libui.UiSlider(0, 1000);
setIntervalMs.onChanged(setIntervalChanged);
setIntervalMs.value = 0;

const form = new libui.UiForm();
form.padded = true;
form.append('setInterval', setIntervalMs, 0);
form.append('actions', makeToolbar(), 0);
box.append(form, true);

const log = new libui.UiMultilineEntry();
box.append(log, true);

win.onClosing(() => {
	if (setIntervalHandle !== null) {
		clearInterval(setIntervalHandle);
		setIntervalHandle = null;
	}
	win.close();
	libui.stopLoop();
});

win.show();
libui.startLoop();

function logAppend(line) {
	const lines = log.text.split('\n');
	/*if (lines.length > 20) {
		log.text = lines.slice(1).join('\n');
	}*/
	log.append(line + '\n');
}

function setIntervalChanged() {
	const ms = setIntervalMs.value;
	if (Math.abs(ms - lastTimeout) < 100) {
		return;
	}
	lastTimeout = ms;
	if (setIntervalHandle !== null) {
		clearInterval(setIntervalHandle);
		setIntervalHandle = null;
	}

	if (ms > 0) {
		setIntervalHandle = setInterval(() => {
			const now = Date.now();
			const elapsed = now - setIntervalLast;
			logAppend(`setInterval:${now} - elapsed ${elapsed} ms`);
			setIntervalLast = now;
		}, ms);
	}
}

function makeToolbar() {
	const toolbar = new libui.UiHorizontalBox();
	toolbar.padded = true;

	const btnSetImmediate = new libui.UiButton('setImmediate');
	btnSetImmediate.onClicked(() => {
		const now = Date.now();
		setImmediate(() => {
			const elapsed = Date.now() - now;
			logAppend(`setImmediate:${now} - elapsed ${elapsed} ms`);
		});
	});

	toolbar.append(btnSetImmediate, false);

	const btnPromise = new libui.UiButton('Promise');
	btnPromise.onClicked(() => {
		const now = Date.now();

		Promise.resolve(true).then(() => {
			const elapsed = Date.now() - now;
			logAppend(`Promise: ${now} - elapsed ${elapsed} ms`);
		});
	});

	toolbar.append(btnPromise, false);

	const btnNextTick = new libui.UiButton('NextTick');
	btnNextTick.onClicked(() => {
		const now = Date.now();
		process.nextTick(() => {
			const elapsed = Date.now() - now;
			logAppend(`nextTick:${now} - elapsed ${elapsed} ms`);
		});
	});

	toolbar.append(btnNextTick, false);

	const btnReadFile = new libui.UiButton('ReadFile');
	btnReadFile.onClicked(() => {
		const {createReadStream} = require('fs');
		const stream = createReadStream('/tmp/big');
		let i = 0;
		stream.on('data', data => {
			logAppend(`ReadFile: chunk ${i++} - ` + data.length);
		});
	});

	const btnHttp = new libui.UiButton('Http');
	btnHttp.onClicked(() => {
		const http = require('http');
		let i = 0;

		const server = http.createServer((req, res) => {
			res.writeHead(200, {'Content-Type': 'text/plain'});
			logAppend(`Http: request ${i}`);
			res.end(String(i++));
		});

		server.listen(3000, '127.0.0.1', () => {
			logAppend('listening...');
		});
	});

	toolbar.append(btnHttp, false);

	return toolbar;
}
<|endoftext|>


<commit_before>const Ajv = require("ajv");
const Request = require("./utils/Request");
const parameters = require("../config/parameters/campaign");

const ajv = new Ajv({allErrors: true});
const validate = ajv.compile(parameters);

class Campaign {
    /**
     *
     * @param {Config} cfg
     */
    constructor (cfg) {
        this.request = new Request(cfg);
    }

    /**
     * Create a new campaign using the given payload
     * @see {@link https://batch.com/doc/api/campaigns/parameters.html} to get the possible parameters
     *
     * @param {Object} payload
     * @returns {Promise.<String, Error>} get the campaign token if fulfilled
     */
    create (payload) {
        // don't send the payload if it's malformed
        const isValid = validate(payload);

        if (!isValid) {
            return Promise.reject(new Error(`payload invalid. Reason: "${ajv.errorsText(validate.errors)}"`));
        }

        return this.request.post("/campaigns/create", payload)
        .then(function (response) {
            const tokenKey = "campaign_token";

            if (!response.data.hasOwnProperty(tokenKey)) {
                return Promise.reject(new Error(`the response is malformed. Reason: missing "${tokenKey}" key`));
            }

            return response.data[tokenKey];
        });
    }

    /**
     * the possible parameters to create a payload is on the following link:
     * @see {@link https://batch.com/doc/api/campaigns/parameters.html}
     *
     * @param {String} token
     * @param {Object} payload
     * @returns {Promise.<undefined, Error>}
     */
    update (token, payload) {
        if (!token || typeof token !== "string") {
            return Promise.reject(new Error("the token must be a non-empty string"));
        }

        // do not validate ATM since the doc is too vague
        // about what is a valid payload when updating

        return this.request.post(`/campaigns/update/${token}`, payload)
        .then(() => void 0);
    }

    /**
     * Since `delete` is a reserved word, "remove" has been chosen as a synonym
     *
     * @param {String} token
     * @returns {Promise.<undefined, Error>}
     */
    remove (token) {
        if (!token || typeof token !== "string") {
            return Promise.reject(new Error("the token must be a non-empty string"));
        }

        return this.request.post(`/campaigns/delete/${token}`)
        .then(() => void 0);
    }

    /**
     * campaign infos are composed of *at least* the following properties
     *
     * @typedef {Object} CampaignInfos
     * @property {String} campaign_token
     * @property {Boolean} from_api
     * @property {Boolean} dev_only
     * @property {Date} created_date
     * @property {String} name
     * @property {String} live
     * @property {Date} push_time
     *
     * to get the complete list:
     * @see {@link https://batch.com/doc/api/campaigns/parameters.html}
     */

    /**
     * Warning: You'll have to use your "live" key in order to fetch the details!
     *
     * You can refer to the official docs to get the details:
     * @see {@link https://batch.com/doc/api/campaigns/get-campaign.html}
     *
     * @param {String} token
     * @returns {Promise.<CampaignInfos, Error>}
     */
    get (token) {
        if (!token || typeof token !== "string") {
            return Promise.reject(new Error("the token must be a non-empty string"));
        }

        return this.request.get(`/campaigns/${token}`)
        .then(response => response.data);
    }

    /**
     * campaign stats are composed of the following properties
     * @typedef {Object} Stats
     * @property {Date} date
     * @property {Number} sent
     * @property {Number} direct_open
     * @property {Number} influenced_open
     * @property {Number} open_rate
     * @property {Number} reengaged
     * @property {Number} errors
     */

    /**
     * @param {String} token
     * @returns {Promise.<Stats, Error>} get the campaign stats if fulfilled.
     *
     * You can refer to the official docs to get the details:
     * @see {@link https://batch.com/doc/dashboard/push/analytics.html}
     */
    stats (token) {
        if (!token || typeof token !== "string") {
            return Promise.reject(new Error("the token must be a non-empty string"));
        }

        return this.request.get(`/campaigns/stats/${token}`)
        .then(function (response) {
            const detailKey = "detail";

            if (!response.data.hasOwnProperty(detailKey)) {
                return Promise.reject(new Error(`the response is malformed. Reason: missing "${detailKey}" key`));
            }

            const details = response.data.detail;

            if (!Array.isArray(details)) {
                return Promise.reject(new Error(`the response is malformed. Reason: the "${detailKey}" key must be ar array`));
            }

            // default result
            const result = {
                date: new Date(0),
                sent: 0,
                direct_open: 0,
                influenced_open: 0,
                open_rate: 0,
                reengaged: 0,
                errors: 0
            };

            // the stats are given only when the push campaign was effectively sent
            if (details.length > 0) {
                // fetch only the first element atm
                const detail = details[0];

                result.date = new Date(detail.date);
                result.sent = detail.sent;
                result.direct_open = detail.direct_open;
                result.influenced_open = detail.influenced_open;
                result.reengaged = detail.reengaged;
                result.errors = detail.errors;

                // set the open rate dynamically since it's not given by default
                if (detail.sent > 0) {
                    result.open_rate = (detail.direct_open + detail.influenced_open) / detail.sent;
                }
            }

            return result;
        });
    }
}

/**
 * @returns {Campaign}
 */
module.exports = Campaign;
<commit_msg>feat(Campaigns): add `list` method to list available notifications<commit_after>const querystring = require("querystring");
const Ajv = require("ajv");
const Request = require("./utils/Request");
const parameters = require("../config/parameters/campaign");

const ajv = new Ajv({allErrors: true});
const validate = ajv.compile(parameters);

class Campaign {
    /**
     *
     * @param {Config} cfg
     */
    constructor (cfg) {
        this.request = new Request(cfg);
    }

    /**
     * Create a new campaign using the given payload
     * @see {@link https://batch.com/doc/api/campaigns/parameters.html} to get the possible parameters
     *
     * @param {Object} payload
     * @returns {Promise.<String, Error>} get the campaign token if fulfilled
     */
    create (payload) {
        // don't send the payload if it's malformed
        const isValid = validate(payload);

        if (!isValid) {
            return Promise.reject(new Error(`payload invalid. Reason: "${ajv.errorsText(validate.errors)}"`));
        }

        return this.request.post("/campaigns/create", payload)
        .then(function (response) {
            const tokenKey = "campaign_token";

            if (!response.data.hasOwnProperty(tokenKey)) {
                return Promise.reject(new Error(`the response is malformed. Reason: missing "${tokenKey}" key`));
            }

            return response.data[tokenKey];
        });
    }

    /**
     * the possible parameters to create a payload is on the following link:
     * @see {@link https://batch.com/doc/api/campaigns/parameters.html}
     *
     * @param {String} token
     * @param {Object} payload
     * @returns {Promise.<undefined, Error>}
     */
    update (token, payload) {
        if (!token || typeof token !== "string") {
            return Promise.reject(new Error("the token must be a non-empty string"));
        }

        // do not validate ATM since the doc is too vague
        // about what is a valid payload when updating

        return this.request.post(`/campaigns/update/${token}`, payload)
        .then(() => void 0);
    }

    /**
     * Since `delete` is a reserved word, "remove" has been chosen as a synonym
     *
     * @param {String} token
     * @returns {Promise.<undefined, Error>}
     */
    remove (token) {
        if (!token || typeof token !== "string") {
            return Promise.reject(new Error("the token must be a non-empty string"));
        }

        return this.request.post(`/campaigns/delete/${token}`)
        .then(() => void 0);
    }

    /**
     * campaign infos are composed of *at least* the following properties
     *
     * @typedef {Object} CampaignInfos
     * @property {String} campaign_token
     * @property {Boolean} from_api
     * @property {Boolean} dev_only
     * @property {Date} created_date
     * @property {String} name
     * @property {String} live
     * @property {Date} push_time
     *
     * to get the complete list:
     * @see {@link https://batch.com/doc/api/campaigns/parameters.html}
     */

    /**
     * Warning: You'll have to use your "live" key in order to fetch the details!
     *
     * You can refer to the official docs to get the details:
     * @see {@link https://batch.com/doc/api/campaigns/get-campaign.html}
     *
     * @param {String} token
     * @returns {Promise.<CampaignInfos, Error>}
     */
    get (token) {
        if (!token || typeof token !== "string") {
            return Promise.reject(new Error("the token must be a non-empty string"));
        }

        return this.request.get(`/campaigns/${token}`)
        .then(response => response.data);
    }

    /**
     * Warning: You'll have to use your "live" key in order to fetch the listing!
     *
     * You can refer to the official docs to get the details:
     * @see {@link https://batch.com/doc/api/campaigns/get-campaign.html}
     *
     * @param {Number} [from=0]
     * @param {Number} [limit=10] the maximum allowed value is 100
     * @param {Boolean} [live]
     * @param {Boolean} [fromAPI]
     * @returns {Promise.<Array.<CampaignInfos>, Error>}
     */
    list (from = 0, limit = 10, live, fromAPI) {
        const queryParams = {
            from,
            limit
        };

        // only add the following parameters if there're set
        if (typeof live === "boolean") queryParams.live = live;
        if (typeof fromAPI === "boolean") queryParams.from_api = fromAPI;

        const queryString = querystring.stringify(queryParams);

        return this.request.get(`/campaigns/list?${queryString}`)
        .then(response => response.data);
    }

    /**
     * campaign stats are composed of the following properties
     * @typedef {Object} Stats
     * @property {Date} date
     * @property {Number} sent
     * @property {Number} direct_open
     * @property {Number} influenced_open
     * @property {Number} open_rate
     * @property {Number} reengaged
     * @property {Number} errors
     */

    /**
     * @param {String} token
     * @returns {Promise.<Stats, Error>} get the campaign stats if fulfilled.
     *
     * You can refer to the official docs to get the details:
     * @see {@link https://batch.com/doc/dashboard/push/analytics.html}
     */
    stats (token) {
        if (!token || typeof token !== "string") {
            return Promise.reject(new Error("the token must be a non-empty string"));
        }

        return this.request.get(`/campaigns/stats/${token}`)
        .then(function (response) {
            const detailKey = "detail";

            if (!response.data.hasOwnProperty(detailKey)) {
                return Promise.reject(new Error(`the response is malformed. Reason: missing "${detailKey}" key`));
            }

            const details = response.data.detail;

            if (!Array.isArray(details)) {
                return Promise.reject(new Error(`the response is malformed. Reason: the "${detailKey}" key must be ar array`));
            }

            // default result
            const result = {
                date: new Date(0),
                sent: 0,
                direct_open: 0,
                influenced_open: 0,
                open_rate: 0,
                reengaged: 0,
                errors: 0
            };

            // the stats are given only when the push campaign was effectively sent
            if (details.length > 0) {
                // fetch only the first element atm
                const detail = details[0];

                result.date = new Date(detail.date);
                result.sent = detail.sent;
                result.direct_open = detail.direct_open;
                result.influenced_open = detail.influenced_open;
                result.reengaged = detail.reengaged;
                result.errors = detail.errors;

                // set the open rate dynamically since it's not given by default
                if (detail.sent > 0) {
                    result.open_rate = (detail.direct_open + detail.influenced_open) / detail.sent;
                }
            }

            return result;
        });
    }
}

/**
 * @returns {Campaign}
 */
module.exports = Campaign;
<|endoftext|>