<commit_before>import Immutable from 'immutable';
import getKey from './get-key';

/**
 * Returns true if the
 */
export const matchesField = (hash, field, operator, value) => {
  switch(operator) {
  case '===':     return hash.has(field) && hash.get(field, undefined) === value;
  case '>=':      return hash.has(field) && hash.get(field, undefined) >=  value;
  case '>':       return hash.has(field) && hash.get(field, undefined) >   value;
  case '<=':      return hash.has(field) && hash.get(field, undefined) <=  value;
  case '<':       return hash.has(field) && hash.get(field, undefined) <   value;
  case 'in':      return hash.has(field) && Immutable.List(value).flatten().includes(hash.get(field, undefined));
  case 'not in':  return !hash.has(field) || !Immutable.List(value).flatten().includes(hash.get(field, undefined));
  default:        throw new Error(`Unknown operator=${operator} for field=${field} &, value=${value}`);
  }
};

export const filterNotHash = (hash, value, field) => {
  // console.log(`hash=${hash}, field=${field}, value=${value}`);
  if (!Immutable.Map.isMap(value)) {
    // console.log(`hash=${hash}, field=${field}, value=${value}, === : ${matchesField(hash, field, '===', value)}`);
    return matchesField(hash, field, '===', value);
  }
  // console.log(`hash=${hash}, field=${field}, value=${value} : ${value.filterNot( (value, operator) => matchesField(hash, field, operator, value) ).isEmpty()}`);
  return value.filterNot( (value, operator) => matchesField(hash, field, operator, value) ).isEmpty();
};


export const matchesAudience = (audience, audienceProps) => {
  if (!audienceProps || audienceProps.isEmpty()) {
    return true;
  }
  return audienceProps.filterNot(
    (value, field) => filterNotHash(audience, value, field)
  ).isEmpty();
};


export const matchesRoute = (route, routeProps) => {
  if (!routeProps || routeProps.isEmpty()) {
    return true;
  }
  return routeProps.filterNot((value, field) => {
    if (['query', 'params'].includes(field)) {
      const hash = route.get(field);
      // console.log(`hash=${hash}, field=${field}, value=${value}, output=${value.filterNot((value, field) => filterNotHash(hash, value, field)).isEmpty()}`);
      return value.filterNot((value, field) => filterNotHash(hash, value, field)).isEmpty();
    }
    return filterNotHash(route, value, field);
  }).isEmpty();
};



const availableExperiments = ({experiments, audience_path, audience, route, route_path}) => {
  // Filter by routes
  let output = experiments.filter(
    (experiment) => matchesRoute(route, experiment.getIn(route_path, null))
  );

  // Filter by audience
  output = output.filter(
    (experiment) => matchesAudience(audience, experiment.getIn(audience_path, null))
  );

  // Map the results together into a hash of `key` => `experiment`
  output = output.reduce(
    (hash, experiment) => hash.set(getKey(experiment), experiment),
    Immutable.Map({}),
  );
  return output;
};
export default availableExperiments;<commit_msg>bug: Added comments to availableExpriments and working on sticky experiences<commit_after>import Immutable from 'immutable';
import getKey from './get-key';

/**
 * Returns true if the hash has the given field and the value matches the operator
 *
 * ex: Matches the given hash
 *   hash     = { loggedIn: true, vipRank: 10 }
 *   field    = 'loggedIn'
 *   operator = '==='
 *   value    = true
 *
 * ex: Doesn't match the hash
 *   hash     = { loggedIn: true, vipRank: 10 }
 *   field    = 'vipRank'
 *   operator = '<='
 *   value    = 5
 */
export const matchesField = (hash, field, operator, value) => {
  switch(operator) {
  case '===':     return hash.has(field) && hash.get(field, undefined) === value;
  case '>=':      return hash.has(field) && hash.get(field, undefined) >=  value;
  case '>':       return hash.has(field) && hash.get(field, undefined) >   value;
  case '<=':      return hash.has(field) && hash.get(field, undefined) <=  value;
  case '<':       return hash.has(field) && hash.get(field, undefined) <   value;
  case 'in':      return hash.has(field) && Immutable.List(value).flatten().includes(hash.get(field, undefined));
  case 'not in':  return !hash.has(field) || !Immutable.List(value).flatten().includes(hash.get(field, undefined));
  default:        throw new Error(`Unknown operator=${operator} for field=${field} &, value=${value}`);
  }
};


/**
 * Filter out all values that don't match the hash's field
 */
export const filterNotHash = (hash, value, field) => {
  if (!Immutable.Map.isMap(value)) {
    return matchesField(hash, field, '===', value);
  }
  return value.filterNot( (value, operator) => matchesField(hash, field, operator, value) ).isEmpty();
};


/**
 * Does the audience match the given audienceProps?
 * There are two conditions that determin an available audience:
 * 1. `audienceProps` is null / empty hash ({})
 * 2. `audienceProps` is exactly matches or is a sub-set of `audience`
 */
export const matchesAudience = (audience, audienceProps) => {
  if (!audienceProps || audienceProps.isEmpty()) {
    return true;
  }
  return audienceProps.filterNot(
    (value, field) => filterNotHash(audience, value, field)
  ).isEmpty();
};


/**
 * Does the route match the given routeProps?
 * There are two conditions that determin an available route:
 * 1. `routeProps` is null / empty hash ({})
 * 2. `routeProps` is exactly matches or is a sub-set of `route`
 */
export const matchesRoute = (route, routeProps) => {
  if (!routeProps || routeProps.isEmpty()) {
    return true;
  }
  return routeProps.filterNot((value, field) => {
    if (['query', 'params'].includes(field)) {
      const hash = route.get(field);
      // console.log(`hash=${hash}, field=${field}, value=${value}, output=${value.filterNot((value, field) => filterNotHash(hash, value, field)).isEmpty()}`);
      return value.filterNot((value, field) => filterNotHash(hash, value, field)).isEmpty();
    }
    return filterNotHash(route, value, field);
  }).isEmpty();
};


/**
 * Find all experiments that match the given audience and route.
 * Experiments that are available to the current audience & route match the following criteria:
 * 1. 'route' matches the given experiment.getIn(route_path)
 * 2. 'audience' matches the given experiment.getIn(audience_path)
 */
const availableExperiments = ({experiments, audience_path, route_path, audience, route}) => {
  // Filter by routes
  let output = experiments.filter(
    (experiment) => matchesRoute(route, experiment.getIn(route_path, null))
  );

  // Filter by audience
  output = output.filter(
    (experiment) => matchesAudience(audience, experiment.getIn(audience_path, null))
  );

  // Map the results together into a hash of `key` => `experiment`
  output = output.reduce(
    (hash, experiment) => hash.set(getKey(experiment), experiment),
    Immutable.Map({}),
  );
  return output;
};
export default availableExperiments;
{'fraction_non_alphanumeric': 0.16738754325259517, 'fraction_numerical': 0.0015859284890426758, 'mean_word_length': 4.082051282051282, 'pattern_counts': {'":': 0, '<': 12, '<?xml version=': 0, '>': 40, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2118396', 'n_tokens_mistral': 2091, 'n_tokens_neox': 2070, 'n_words': 572}