<commit_before>import JSON5 from 'json5'
import limitConcurrency from 'limit-concurrency-decorator'
import synchronized from 'decorator-synchronized'
import { BaseError } from 'make-error'
import {
  endsWith,
  findKey,
  forEach,
  get,
  identity,
  map,
  mapValues,
  mean,
  sum,
  uniq,
  zipWith,
} from 'lodash'

import { parseDateTime } from './xapi'

export class FaultyGranularity extends BaseError {}

// -------------------------------------------------------------------

// according to https://xapi-project.github.io/xen-api/metrics.html
// The values are stored at intervals of:
//  - 5 seconds for the past 10 minutes
//  - one minute for the past 2 hours
//  - one hour for the past week
//  - one day for the past year
const RRD_STEP_SECONDS = 5
const RRD_STEP_MINUTES = 60
const RRD_STEP_HOURS = 3600
const RRD_STEP_DAYS = 86400

const RRD_STEP_FROM_STRING = {
  seconds: RRD_STEP_SECONDS,
  minutes: RRD_STEP_MINUTES,
  hours: RRD_STEP_HOURS,
  days: RRD_STEP_DAYS,
}

// points = intervalInSeconds / step
const RRD_POINTS_PER_STEP = {
  [RRD_STEP_SECONDS]: 120,
  [RRD_STEP_MINUTES]: 120,
  [RRD_STEP_HOURS]: 168,
  [RRD_STEP_DAYS]: 366,
}

// -------------------------------------------------------------------
// Utils
// -------------------------------------------------------------------

// Return current local timestamp in seconds
function getCurrentTimestamp() {
  return Date.now() / 1000
}

function convertNanToNull(value) {
  return isNaN(value) ? null : value
}

async function getServerTimestamp(xapi, hostRef) {
  const serverLocalTime = await xapi.call('host.get_servertime', hostRef)
  return Math.floor(parseDateTime(serverLocalTime).getTime() / 1e3)
}

// -------------------------------------------------------------------
// Stats
// -------------------------------------------------------------------

const computeValues = (dataRow, legendIndex, transformValue = identity) =>
  map(dataRow, ({ values }) =>
    transformValue(convertNanToNull(values[legendIndex]))
  )

const combineStats = (stats, path, combineValues) =>
  zipWith(...map(stats, path), (...values) => combineValues(values))

// It browse the object in depth and initialise it's properties
// The targerPath can be a string or an array containing the depth
// targetPath: [a, b, c] => a.b.c
const getValuesFromDepth = (obj, targetPath) => {
  if (typeof targetPath === 'string') {
    return (obj[targetPath] = [])
  }

  forEach(targetPath, (path, key) => {
    if (obj[path] === undefined) {
      obj = obj[path] = targetPath.length - 1 === key ? [] : {}
      return
    }
    obj = obj[path]
  })
  return obj
}

const testMetric = (test, type) =>
  typeof test === 'string'
    ? test === type
    : typeof test === 'function'
    ? test(type)
    : test.exec(type)

const findMetric = (metrics, metricType) => {
  let testResult
  let metric

  forEach(metrics, (current, key) => {
    if (current.test === undefined) {
      const newValues = findMetric(current, metricType)

      metric = newValues.metric
      if (metric !== undefined) {
        testResult = newValues.testResult
        return false
      }
    } else if ((testResult = testMetric(current.test, metricType))) {
      metric = current
      return false
    }
  })

  return { metric, testResult }
}

// -------------------------------------------------------------------

// The metrics:
//  test: can be a function, regexp or string, default to: currentKey
//  getPath: default to: () => currentKey
//  transformValue: default to: identity
const STATS = {
  host: {
    load: {
      test: 'loadavg',
    },
    memoryFree: {
      test: 'memory_free_kib',
      transformValue: value => value * 1024,
    },
    memory: {
      test: 'memory_total_kib',
      transformValue: value => value * 1024,
    },
    cpus: {
      test: /^cpu(\d+)$/,
      getPath: matches => ['cpus', matches[1]],
      transformValue: value => value * 1e2,
    },
    pifs: {
      rx: {
        test: /^pif_eth(\d+)_rx$/,
        getPath: matches => ['pifs', 'rx', matches[1]],
      },
      tx: {
        test: /^pif_eth(\d+)_tx$/,
        getPath: matches => ['pifs', 'tx', matches[1]],
      },
    },
    iops: {
      r: {
        test: /^iops_read_(\w+)$/,
        getPath: matches => ['iops', 'r', matches[1]],
      },
      w: {
        test: /^iops_write_(\w+)$/,
        getPath: matches => ['iops', 'w', matches[1]],
      },
    },
    ioThroughput: {
      r: {
        test: /^io_throughput_read_(\w+)$/,
        getPath: matches => ['ioThroughput', 'r', matches[1]],
        transformValue: value => value * 2 ** 20,
      },
      w: {
        test: /^io_throughput_write_(\w+)$/,
        getPath: matches => ['ioThroughput', 'w', matches[1]],
        transformValue: value => value * 2 ** 20,
      },
    },
    latency: {
      r: {
        test: /^read_latency_(\w+)$/,
        getPath: matches => ['latency', 'r', matches[1]],
        transformValue: value => value / 1e3,
      },
      w: {
        test: /^write_latency_(\w+)$/,
        getPath: matches => ['latency', 'w', matches[1]],
        transformValue: value => value / 1e3,
      },
    },
    iowait: {
      test: /^iowait_(\w+)$/,
      getPath: matches => ['iowait', matches[1]],
    },
  },
  vm: {
    memoryFree: {
      test: 'memory_internal_free',
      transformValue: value => value * 1024,
    },
    memory: {
      test: metricType => endsWith(metricType, 'memory'),
    },
    cpus: {
      test: /^cpu(\d+)$/,
      getPath: matches => ['cpus', matches[1]],
      transformValue: value => value * 1e2,
    },
    vifs: {
      rx: {
        test: /^vif_(\d+)_rx$/,
        getPath: matches => ['vifs', 'rx', matches[1]],
      },
      tx: {
        test: /^vif_(\d+)_tx$/,
        getPath: matches => ['vifs', 'tx', matches[1]],
      },
    },
    xvds: {
      r: {
        test: /^vbd_xvd(.)_read$/,
        getPath: matches => ['xvds', 'r', matches[1]],
      },
      w: {
        test: /^vbd_xvd(.)_write$/,
        getPath: matches => ['xvds', 'w', matches[1]],
      },
    },
    iops: {
      r: {
        test: /^vbd_xvd(.)_iops_read$/,
        getPath: matches => ['iops', 'r', matches[1]],
      },
      w: {
        test: /^vbd_xvd(.)_iops_write$/,
        getPath: matches => ['iops', 'w', matches[1]],
      },
    },
  },
}

// -------------------------------------------------------------------

// RRD
// json: {
//   meta: {
//     start: Number,
//     step: Number,
//     end: Number,
//     rows: Number,
//     columns: Number,
//     legend: String[rows]
//   },
//   data: Item[columns] // Item = { t: Number, values: Number[rows] }
// }

// Local cache
// _statsByObject : {
//   [uuid]: {
//     [step]: {
//       endTimestamp: Number, // the timestamp of the last statistic point
//       interval: Number, // step
//       stats: {
//         [metric1]: Number[],
//         [metric2]: {
//           [subMetric]: Number[],
//         }
//       }
//     }
//   }
// }
export default class XapiStats {
  constructor() {
    this._statsByObject = {}
  }

  // Execute one http request on a XenServer for get stats
  // Return stats (Json format) or throws got exception
  @limitConcurrency(3)
  _getJson(xapi, host, timestamp, step) {
    return xapi
      .getResource('/rrd_updates', {
        host,
        query: {
          cf: 'AVERAGE',
          host: 'true',
          interval: step,
          json: 'true',
          start: timestamp,
        },
      })
      .then(response => response.readAll().then(JSON5.parse))
  }

  async _getNextTimestamp(xapi, host, step) {
    const currentTimeStamp = await getServerTimestamp(xapi, host.$ref)
    const maxDuration = step * RRD_POINTS_PER_STEP[step]
    const lastTimestamp = get(this._statsByObject, [
      host.uuid,
      step,
      'endTimestamp',
    ])

    if (
      lastTimestamp === undefined ||
      currentTimeStamp - lastTimestamp + step > maxDuration
    ) {
      return currentTimeStamp - maxDuration + step
    }
    return lastTimestamp
  }

  _getStats(hostUuid, step, vmUuid) {
    const hostStats = this._statsByObject[hostUuid][step]

    // Return host stats
    if (vmUuid === undefined) {
      return {
        interval: step,
        ...hostStats,
      }
    }

    // Return vm stats
    return {
      interval: step,
      endTimestamp: hostStats.endTimestamp,
      ...this._statsByObject[vmUuid][step],
    }
  }

  @synchronized.withKey((_, { host }) => host.uuid)
  async _getAndUpdateStats(xapi, { host, vmUuid, granularity }) {
    const step =
      granularity === undefined
        ? RRD_STEP_SECONDS
        : RRD_STEP_FROM_STRING[granularity]

    if (step === undefined) {
      throw new FaultyGranularity(
        `Unknown granularity: '${granularity}'. Use 'seconds', 'minutes', 'hours', or 'days'.`
      )
    }

    // Limit the number of http requests
    const hostUuid = host.uuid

    if (
      !(
        vmUuid !== undefined &&
        get(this._statsByObject, [vmUuid, step]) === undefined
      ) &&
      get(this._statsByObject, [hostUuid, step, 'localTimestamp']) + step >
        getCurrentTimestamp()
    ) {
      return this._getStats(hostUuid, step, vmUuid)
    }

    const timestamp = await this._getNextTimestamp(xapi, host, step)
    const json = await this._getJson(xapi, host, timestamp, step)
    if (json.meta.step !== step) {
      throw new FaultyGranularity(
        `Unable to get the true granularity: ${json.meta.step}`
      )
    }

    // It exists data
    if (json.data.length !== 0) {
      // Warning: Sometimes, the json.xport.meta.start value does not match with the
      // timestamp of the oldest data value
      // So, we use the timestamp of the oldest data value !
      const startTimestamp = json.data[json.meta.rows - 1].t
      const endTimestamp = get(this._statsByObject, [
        hostUuid,
        step,
        'endTimestamp',
      ])

      const statsOffset = endTimestamp - startTimestamp + step
      if (endTimestamp !== undefined && statsOffset > 0) {
        const parseOffset = statsOffset / step
        // Remove useless data
        // Note: Older values are at end of json.data.row
        json.data.splice(json.data.length - parseOffset)
      }

      // It exists useful data
      if (json.data.length > 0) {
        // reorder data
        json.data.reverse()
        forEach(json.meta.legend, (legend, index) => {
          const [, type, uuid, metricType] = /^AVERAGE:([^:]+):(.+):(.+)$/.exec(
            legend
          )

          const metrics = STATS[type]
          if (metrics === undefined) {
            return
          }

          const { metric, testResult } = findMetric(metrics, metricType)

          if (metric === undefined) {
            return
          }

          const path =
            metric.getPath !== undefined
              ? metric.getPath(testResult)
              : [findKey(metrics, metric)]

          const metricValues = getValuesFromDepth(this._statsByObject, [
            uuid,
            step,
            'stats',
            ...path,
          ])

          metricValues.push(
            ...computeValues(json.data, index, metric.transformValue)
          )

          // remove older Values
          metricValues.splice(
            0,
            metricValues.length - RRD_POINTS_PER_STEP[step]
          )
        })
      }
    }

    // Update timestamp
    const hostStats = this._statsByObject[hostUuid][step]
    hostStats.endTimestamp = json.meta.end
    hostStats.localTimestamp = getCurrentTimestamp()
    return this._getStats(hostUuid, step, vmUuid)
  }

  getHostStats(xapi, hostId, granularity) {
    return this._getAndUpdateStats(xapi, {
      host: xapi.getObject(hostId),
      granularity,
    })
  }

  async getVmStats(xapi, vmId, granularity) {
    const vm = xapi.getObject(vmId)
    const host = vm.$resident_on
    if (!host) {
      throw new Error(`VM ${vmId} is halted or host could not be found.`)
    }

    return this._getAndUpdateStats(xapi, {
      host,
      vmUuid: vm.uuid,
      granularity,
    })
  }

  async getSrStats(xapi, srId, granularity) {
    const sr = xapi.getObject(srId)

    const hostsStats = {}
    await Promise.all(
      map(uniq(map(sr.$PBDs, 'host')), hostId =>
        this.getHostStats(xapi, hostId, granularity).then(stats => {
          hostsStats[xapi.getObject(hostId).name_label] = stats
        })
      )
    )

    const srShortUUID = sr.uuid.slice(0, 8)
    return {
      interval: hostsStats[Object.keys(hostsStats)[0]].interval,
      endTimestamp: Math.max(...map(hostsStats, 'endTimestamp')),
      localTimestamp: Math.min(...map(hostsStats, 'localTimestamp')),
      stats: {
        iops: {
          r: combineStats(hostsStats, `stats.iops.r[${srShortUUID}]`, sum),
          w: combineStats(hostsStats, `stats.iops.w[${srShortUUID}]`, sum),
        },
        ioThroughput: {
          r: combineStats(
            hostsStats,
            `stats.ioThroughput.r[${srShortUUID}]`,
            sum
          ),
          w: combineStats(
            hostsStats,
            `stats.ioThroughput.w[${srShortUUID}]`,
            sum
          ),
        },
        latency: {
          r: combineStats(hostsStats, `stats.latency.r[${srShortUUID}]`, mean),
          w: combineStats(hostsStats, `stats.latency.w[${srShortUUID}]`, mean),
        },
        iowait: mapValues(hostsStats, `stats.iowait[${srShortUUID}]`),
      },
    }
  }
}
<commit_msg>chore(xo-server/XapiStats): improve the cache implementation (#3859)<commit_after>import JSON5 from 'json5'
import limitConcurrency from 'limit-concurrency-decorator'
import synchronized from 'decorator-synchronized'
import { BaseError } from 'make-error'
import {
  defaults,
  endsWith,
  findKey,
  forEach,
  identity,
  map,
  mapValues,
  mean,
  sum,
  uniq,
  zipWith,
} from 'lodash'

import { parseDateTime } from './xapi'

export class FaultyGranularity extends BaseError {}

// -------------------------------------------------------------------

// according to https://xapi-project.github.io/xen-api/metrics.html
// The values are stored at intervals of:
//  - 5 seconds for the past 10 minutes
//  - one minute for the past 2 hours
//  - one hour for the past week
//  - one day for the past year
const RRD_STEP_SECONDS = 5
const RRD_STEP_MINUTES = 60
const RRD_STEP_HOURS = 3600
const RRD_STEP_DAYS = 86400

const RRD_STEP_FROM_STRING = {
  seconds: RRD_STEP_SECONDS,
  minutes: RRD_STEP_MINUTES,
  hours: RRD_STEP_HOURS,
  days: RRD_STEP_DAYS,
}

// points = intervalInSeconds / step
const RRD_POINTS_PER_STEP = {
  [RRD_STEP_SECONDS]: 120,
  [RRD_STEP_MINUTES]: 120,
  [RRD_STEP_HOURS]: 168,
  [RRD_STEP_DAYS]: 366,
}

// -------------------------------------------------------------------
// Utils
// -------------------------------------------------------------------

function convertNanToNull(value) {
  return isNaN(value) ? null : value
}

async function getServerTimestamp(xapi, hostRef) {
  const serverLocalTime = await xapi.call('host.get_servertime', hostRef)
  return Math.floor(parseDateTime(serverLocalTime).getTime() / 1e3)
}

// -------------------------------------------------------------------
// Stats
// -------------------------------------------------------------------

const computeValues = (dataRow, legendIndex, transformValue = identity) =>
  map(dataRow, ({ values }) =>
    transformValue(convertNanToNull(values[legendIndex]))
  )

const combineStats = (stats, path, combineValues) =>
  zipWith(...map(stats, path), (...values) => combineValues(values))

const createGetProperty = (obj, property, defaultValue) =>
  defaults(obj, { [property]: defaultValue })[property]

const testMetric = (test, type) =>
  typeof test === 'string'
    ? test === type
    : typeof test === 'function'
    ? test(type)
    : test.exec(type)

const findMetric = (metrics, metricType) => {
  let testResult
  let metric

  forEach(metrics, (current, key) => {
    if (current.test === undefined) {
      const newValues = findMetric(current, metricType)

      metric = newValues.metric
      if (metric !== undefined) {
        testResult = newValues.testResult
        return false
      }
    } else if ((testResult = testMetric(current.test, metricType))) {
      metric = current
      return false
    }
  })

  return { metric, testResult }
}

// -------------------------------------------------------------------

// The metrics:
//  test: can be a function, regexp or string, default to: currentKey
//  getPath: default to: () => currentKey
//  transformValue: default to: identity
const STATS = {
  host: {
    load: {
      test: 'loadavg',
    },
    memoryFree: {
      test: 'memory_free_kib',
      transformValue: value => value * 1024,
    },
    memory: {
      test: 'memory_total_kib',
      transformValue: value => value * 1024,
    },
    cpus: {
      test: /^cpu(\d+)$/,
      getPath: matches => ['cpus', matches[1]],
      transformValue: value => value * 1e2,
    },
    pifs: {
      rx: {
        test: /^pif_eth(\d+)_rx$/,
        getPath: matches => ['pifs', 'rx', matches[1]],
      },
      tx: {
        test: /^pif_eth(\d+)_tx$/,
        getPath: matches => ['pifs', 'tx', matches[1]],
      },
    },
    iops: {
      r: {
        test: /^iops_read_(\w+)$/,
        getPath: matches => ['iops', 'r', matches[1]],
      },
      w: {
        test: /^iops_write_(\w+)$/,
        getPath: matches => ['iops', 'w', matches[1]],
      },
    },
    ioThroughput: {
      r: {
        test: /^io_throughput_read_(\w+)$/,
        getPath: matches => ['ioThroughput', 'r', matches[1]],
        transformValue: value => value * 2 ** 20,
      },
      w: {
        test: /^io_throughput_write_(\w+)$/,
        getPath: matches => ['ioThroughput', 'w', matches[1]],
        transformValue: value => value * 2 ** 20,
      },
    },
    latency: {
      r: {
        test: /^read_latency_(\w+)$/,
        getPath: matches => ['latency', 'r', matches[1]],
        transformValue: value => value / 1e3,
      },
      w: {
        test: /^write_latency_(\w+)$/,
        getPath: matches => ['latency', 'w', matches[1]],
        transformValue: value => value / 1e3,
      },
    },
    iowait: {
      test: /^iowait_(\w+)$/,
      getPath: matches => ['iowait', matches[1]],
    },
  },
  vm: {
    memoryFree: {
      test: 'memory_internal_free',
      transformValue: value => value * 1024,
    },
    memory: {
      test: metricType => endsWith(metricType, 'memory'),
    },
    cpus: {
      test: /^cpu(\d+)$/,
      getPath: matches => ['cpus', matches[1]],
      transformValue: value => value * 1e2,
    },
    vifs: {
      rx: {
        test: /^vif_(\d+)_rx$/,
        getPath: matches => ['vifs', 'rx', matches[1]],
      },
      tx: {
        test: /^vif_(\d+)_tx$/,
        getPath: matches => ['vifs', 'tx', matches[1]],
      },
    },
    xvds: {
      r: {
        test: /^vbd_xvd(.)_read$/,
        getPath: matches => ['xvds', 'r', matches[1]],
      },
      w: {
        test: /^vbd_xvd(.)_write$/,
        getPath: matches => ['xvds', 'w', matches[1]],
      },
    },
    iops: {
      r: {
        test: /^vbd_xvd(.)_iops_read$/,
        getPath: matches => ['iops', 'r', matches[1]],
      },
      w: {
        test: /^vbd_xvd(.)_iops_write$/,
        getPath: matches => ['iops', 'w', matches[1]],
      },
    },
  },
}

// -------------------------------------------------------------------

// RRD
// json: {
//   meta: {
//     start: Number,
//     step: Number,
//     end: Number,
//     rows: Number,
//     columns: Number,
//     legend: String[rows]
//   },
//   data: Item[columns] // Item = { t: Number, values: Number[rows] }
// }

// Local cache
// _statsByObject : {
//   [uuid]: {
//     [step]: {
//       endTimestamp: Number, // the timestamp of the last statistic point
//       interval: Number, // step
//       stats: {
//         [metric1]: Number[],
//         [metric2]: {
//           [subMetric]: Number[],
//         }
//       }
//     }
//   }
// }
export default class XapiStats {
  constructor() {
    this._statsByObject = {}
  }

  // Execute one http request on a XenServer for get stats
  // Return stats (Json format) or throws got exception
  @limitConcurrency(3)
  _getJson(xapi, host, timestamp, step) {
    return xapi
      .getResource('/rrd_updates', {
        host,
        query: {
          cf: 'AVERAGE',
          host: 'true',
          interval: step,
          json: 'true',
          start: timestamp,
        },
      })
      .then(response => response.readAll().then(JSON5.parse))
  }

  // To avoid multiple requests, we keep a cash for the stats and
  // only return it if we not exceed a step
  _getCachedStats(uuid, step, currentTimeStamp) {
    const statsByObject = this._statsByObject

    const stats = statsByObject[uuid]?.[step]
    if (stats === undefined) {
      return
    }

    if (stats.endTimestamp + step < currentTimeStamp) {
      delete statsByObject[uuid][step]
      return
    }

    return stats
  }

  @synchronized.withKey((_, { host }) => host.uuid)
  async _getAndUpdateStats(xapi, { host, uuid, granularity }) {
    const step =
      granularity === undefined
        ? RRD_STEP_SECONDS
        : RRD_STEP_FROM_STRING[granularity]

    if (step === undefined) {
      throw new FaultyGranularity(
        `Unknown granularity: '${granularity}'. Use 'seconds', 'minutes', 'hours', or 'days'.`
      )
    }

    const currentTimeStamp = await getServerTimestamp(xapi, host.$ref)

    const stats = this._getCachedStats(uuid, step, currentTimeStamp)
    if (stats !== undefined) {
      return stats
    }

    const maxDuration = step * RRD_POINTS_PER_STEP[step]

    // To avoid crossing over the boundary, we ask for one less step
    const optimumTimestamp = currentTimeStamp - maxDuration + step
    const json = await this._getJson(xapi, host, optimumTimestamp, step)

    const actualStep = json.meta.step
    if (json.data.length > 0) {
      // fetched data is organized from the newest to the oldest
      // but this implementation requires it in the other direction
      json.data.reverse()
      json.meta.legend.forEach((legend, index) => {
        const [, type, uuid, metricType] = /^AVERAGE:([^:]+):(.+):(.+)$/.exec(
          legend
        )

        const metrics = STATS[type]
        if (metrics === undefined) {
          return
        }

        const { metric, testResult } = findMetric(metrics, metricType)
        if (metric === undefined) {
          return
        }

        const xoObjectStats = createGetProperty(this._statsByObject, uuid, {})
        let stepStats = xoObjectStats[actualStep]
        if (
          stepStats === undefined ||
          stepStats.endTimestamp !== json.meta.end
        ) {
          stepStats = xoObjectStats[actualStep] = {
            endTimestamp: json.meta.end,
            interval: actualStep,
          }
        }

        const path =
          metric.getPath !== undefined
            ? metric.getPath(testResult)
            : [findKey(metrics, metric)]

        const lastKey = path.length - 1
        let metricStats = createGetProperty(stepStats, 'stats', {})
        path.forEach((property, key) => {
          if (key === lastKey) {
            metricStats[property] = computeValues(
              json.data,
              index,
              metric.transformValue
            )
            return
          }

          metricStats = createGetProperty(metricStats, property, {})
        })
      })
    }

    if (actualStep !== step) {
      throw new FaultyGranularity(
        `Unable to get the true granularity: ${actualStep}`
      )
    }

    return (
      this._statsByObject[uuid]?.[step] ?? {
        endTimestamp: currentTimeStamp,
        interval: step,
        stats: {},
      }
    )
  }

  getHostStats(xapi, hostId, granularity) {
    const host = xapi.getObject(hostId)
    return this._getAndUpdateStats(xapi, {
      host,
      uuid: host.uuid,
      granularity,
    })
  }

  async getVmStats(xapi, vmId, granularity) {
    const vm = xapi.getObject(vmId)
    const host = vm.$resident_on
    if (!host) {
      throw new Error(`VM ${vmId} is halted or host could not be found.`)
    }

    return this._getAndUpdateStats(xapi, {
      host,
      uuid: vm.uuid,
      granularity,
    })
  }

  async getSrStats(xapi, srId, granularity) {
    const sr = xapi.getObject(srId)

    const hostsStats = {}
    await Promise.all(
      map(uniq(map(sr.$PBDs, 'host')), hostId =>
        this.getHostStats(xapi, hostId, granularity).then(stats => {
          hostsStats[xapi.getObject(hostId).name_label] = stats
        })
      )
    )

    const srShortUUID = sr.uuid.slice(0, 8)
    return {
      interval: hostsStats[Object.keys(hostsStats)[0]].interval,
      endTimestamp: Math.max(...map(hostsStats, 'endTimestamp')),
      localTimestamp: Math.min(...map(hostsStats, 'localTimestamp')),
      stats: {
        iops: {
          r: combineStats(hostsStats, `stats.iops.r[${srShortUUID}]`, sum),
          w: combineStats(hostsStats, `stats.iops.w[${srShortUUID}]`, sum),
        },
        ioThroughput: {
          r: combineStats(
            hostsStats,
            `stats.ioThroughput.r[${srShortUUID}]`,
            sum
          ),
          w: combineStats(
            hostsStats,
            `stats.ioThroughput.w[${srShortUUID}]`,
            sum
          ),
        },
        latency: {
          r: combineStats(hostsStats, `stats.latency.r[${srShortUUID}]`, mean),
          w: combineStats(hostsStats, `stats.latency.w[${srShortUUID}]`, mean),
        },
        iowait: mapValues(hostsStats, `stats.iowait[${srShortUUID}]`),
      },
    }
  }
}
<|endoftext|>


<commit_before>'use strict';

var inquirer = require('inquirer');

function showCheckoutMenu() {
  var questions = [{
      type: 'input',
      name:'radionum',
      message: 'Radio Number:',
      validate: function (input) {
        return input !== ''; // can't be blank
        // todo: check if this radio is already checked out
      }
    }, {
      type: 'input',
      name:'name',
      message: 'Name:',
      validate: function (input) {
        return input !== ''; // can't be blank
      }
    }, {
      type: 'input',
      name:'dept',
      message: 'Department:',
      validate: function (input) {
        return input !== ''; // can't be blank
      }
    }, {
      type: 'input',
      name:'phone',
      message: 'Phone Number:',
      validate: function (input) {
        var pass = input.match(/\d+/); // literally just see if it has at least one number in it
        if (pass) {
          return true;
        } else {
          return 'Please enter a valid phone number';
        }
      }
    }, {
      type: 'input',
      name:'badge',
      message: 'Badge Number:',
      validate: function (input) {
        return input !== ''; // can't be blank
      }
    }, {
      type: 'input',
      name:'notes',
      message: 'Notes (optional):'
    }];

  console.log('Checkout Radio\n');
  inquirer.prompt(questions, function (answers) {
    console.log('\nAnswers:')
    console.log(answers);
  });
}

function showMainMenu() {
  var question = {
    type: 'list',
    name:'main',
    message: 'Main Menu',
    choices: ['Checkout', 'Checkin', new inquirer.Separator(), 'Find Radio', 'List Radios', new inquirer.Separator(), 'Show Stats']
  };

  inquirer.prompt([question], function (answers) {
    switch (answers.main) {
      case 'Checkout':
        showCheckoutMenu();
        break;
      case 'Checkin':
        showCheckin();
        break;
      case 'Find Radio':
        showFindMenu();
        break;
      case 'List Radios':
        showList();
        break;
      case 'Show Stats':
        showStats();
        break;
    }
  });
}

module.exports = function () {
  showMainMenu();
};<commit_msg>super wonky behavior<commit_after>'use strict';

var inquirer = require('inquirer'),
  chalk = require('chalk'),
  currMenu = 'main'; // yeah, it's a state. sue me.

function showCheckoutMenu() {
  currMenu = 'checkout';

  var questions = [{
      type: 'input',
      name:'radionum',
      message: 'Radio Number:',
      validate: function (input) {
        return input !== ''; // can't be blank
        // todo: check if this radio is already checked out
      }
    }, {
      type: 'input',
      name:'name',
      message: 'Name:',
      validate: function (input) {
        return input !== ''; // can't be blank
      }
    }, {
      type: 'input',
      name:'dept',
      message: 'Department:',
      validate: function (input) {
        return input !== ''; // can't be blank
      }
    }, {
      type: 'input',
      name:'phone',
      message: 'Phone Number:',
      validate: function (input) {
        var pass = input.match(/\d+/); // literally just see if it has at least one number in it
        if (pass) {
          return true;
        } else {
          return 'Please enter a valid phone number';
        }
      }
    }, {
      type: 'input',
      name:'badge',
      message: 'Badge Number:',
      validate: function (input) {
        return input !== ''; // can't be blank
      }
    }, {
      type: 'input',
      name:'notes',
      message: 'Notes (optional):'
    }];

  console.log('Checkout Radio\n');
  inquirer.prompt(questions, function (answers) {
    console.log('\nAnswers:')
    console.log(answers);
  });
}

function showMainMenu() {
  if (currMenu !== 'main') {
    console.log(chalk.grey('\n\n##################\n'));
  }
  currMenu = 'main';

  var question = {
    type: 'list',
    name:'main',
    message: 'Main Menu',
    choices: ['Checkout', 'Checkin', new inquirer.Separator(), 'Find Radio', 'List Radios', new inquirer.Separator(), 'Show Stats']
  };

  inquirer.prompt([question], function (answers) {
    switch (answers.main) {
      case 'Checkout':
        showCheckoutMenu();
        break;
      case 'Checkin':
        showCheckin();
        break;
      case 'Find Radio':
        showFindMenu();
        break;
      case 'List Radios':
        showList();
        break;
      case 'Show Stats':
        showStats();
        break;
    }
  });
}

module.exports = function () {
  var stdin = process.openStdin();
  require('tty').setRawMode(true);

  stdin.on('keypress', function (chunk, key) {
    if (key && key.name === 'escape') {
      if (currMenu === 'checkout') {
        showMainMenu();
      }
    }
  });

  showMainMenu();
};<|endoftext|>


<commit_before>/*global canny */
/*jslint browser: true*/

/**
 * E.g.: canny-mod="flowControl" canny-var="{'view' : 'viewName'}"
 *
 * you can activate a initial view with a anchor in the URL e.g.: yourdomain.html#viewToShow
 * Or pass a comma separated module list for activate more module #viewToShow,otherView
 *
 * TODO made it possible to summarize views with one identifier.
 * Instead of call: gdom.flowControl.show('view1', 'view2', 'view3') call gdom.flowControl.show('view').
 */
(function () {
    "use strict";

    var flowControlInstance = function () {

            var modViews = {}, // saves module views
                getViewAnchor = function () {
                    var hash = location.hash || null, hashSub;
                    if (hash) {
                        hashSub = hash.substr(1);
                        return hashSub.split(',');
                    }
                    return hash;
                },
                showInitialView = getViewAnchor(),
                fc = {
                    // get the parent module from the given viewName
                    getParentNode : function (viewName) {
                        var queue = Object.keys(modViews), l, i;
                        l = queue.length;
                        for (i = 0; i < l; i++) {
                            if (modViews[queue[i]].hasChildrenWithName(viewName)) {
                                return modViews[queue[i]];
                            }
                        }
                        return null;
                    },
                    // passes a view list and complete the list with all parent node names
                    addParents : function (views) {
                        var extViews = views, i, l, pNode,
                            pushExtViews = function (name) {
                                if (extViews.indexOf(name) === -1) {
                                    extViews.push(name);
                                }
                            },
                            addParentView = function (viewName) {
                                var pViewName = fc.getParentNode(viewName);
                                if (pViewName) {
                                    // TOOD while has parent add it to the extViews
                                    pushExtViews(pViewName.getViewName());
                                    addParentView(pViewName.getViewName());
                                }
                            };
                        l = views.length;
                        for (i = 0; i < l; i++) {
                            pNode = fc.getParentNode(views[i]);
                            if (pNode) {
                                pushExtViews(pNode.getViewName());
                                // so far we have parents do it recursive
                                addParentView(pNode.getViewName());
                            }
                        }
                        return extViews;
                    },
                    fadeOut : function (node, cb) {
                        var opacity = node.style.opacity || 1,
                            fade = function (op) {
                                if (op > 0) {
                                    node.style.opacity = op;

                                    setTimeout(function () {
                                        fade(op - 0.1);
                                    }, 30);
                                } else {
                                    node.style.display = 'none';
                                    cb();
                                }
                            };
                        fade(opacity);
                        console.log('fadeOut', node);
                    },
                    fadeIn : function (node, cb) {
                        var opacity = node.opacity || 0,
                            fade = function (op) {
                                if (op <= 1) {
                                    node.style.opacity = op;
                                    setTimeout(function () {
                                        fade(op + 0.1);
                                    }, 30);
                                } else {
                                    cb();
                                }
                            };
                        if (node.style.display === 'none') {
                            node.style.opacity = opacity;
                            node.style.display = '';
                            fade(opacity);
                        } else {
                            node.style.opacity = 1;
                            console.log('fadeIn', node);
                        }
                    }
                },
                ext = {
                    /**
                     *
                     * @param node
                     * @param innerNode
                     * @returns {{remove: remove}}
                     */
                    progress : function (node, innerNode) {
                        var newNode = document.createElement('div'), centerNode = document.createElement('div'), txtNode;
                        node.style.position = 'relative';
                        newNode.style.opacity = '0.6';
                        newNode.style.backgroundColor = '#666';
                        newNode.style.position = 'absolute';
                        newNode.style.top = 0;
                        newNode.style.left = 0;
                        newNode.style.width = node.offsetWidth + 'px';
                        newNode.style.height = node.offsetHeight + 'px';
                        newNode.style.borderRadius = window.getComputedStyle(node, null).borderRadius;

                        centerNode.style.position = 'absolute';
                        centerNode.style.top = (node.offsetHeight / 2) - 30 + 'px';
                        centerNode.style.width = node.offsetWidth + 'px';
                        centerNode.style.textAlign = 'center';

                        if (innerNode) {
                            centerNode.appendChild(innerNode);
                        }
                        node.appendChild(newNode);
                        node.appendChild(centerNode);
                        return {
                            remove : function (delay, cb) {
                                setTimeout(function () {
                                    node.removeChild(newNode);
                                    node.removeChild(centerNode);
                                    cb && cb();
                                }, delay || 0);
                            },
                            fadeOut : function (delay, cb) {
                                setTimeout(function () {
                                    fc.fadeOut(newNode, function () {
                                        node.removeChild(newNode);
                                        node.removeChild(centerNode);
                                        cb && cb();
                                    });
                                }, delay || 0);
                            }
                        };
                    }
                };

            return {
                mod : modViews, // part of api
                ready : function () {
                    var modNames, i, l;
                    if (showInitialView) {
                        modNames = Object.keys(this.mod);
                        l = modNames.length;
                        // check if showInitialView contains a registered module
                        for (i = 0; i < l; i++) {
                            if (showInitialView.indexOf(modNames[i]) !== -1) {
                                this.showImmediately.apply(null, showInitialView);
                                break;
                            }
                        }
                    }
                },
                add : function (node, attr) {    // part of api

                    modViews[attr.view] = (function (node, parentView) {
                        var flowControlChildNodes = {},
                            // TODO do it with a querySelectorAll
                            findChildren = function (cNode) {
                                if (cNode.hasChildNodes()) {
                                    [].slice.call(cNode.children).forEach(findChildren);
                                }
                                var modAttr = cNode.getAttribute('gd-module'), attrValue, view;
                                if (/flowControl/.test(modAttr)) {
                                    console.log(cNode);
                                    // TODO read attributes should be part gdom functionality
                                    attrValue = cNode.getAttribute('gd-attr').split("\'").join('\"');
                                    if (/:/.test(attrValue)) {
                                        // could be a JSON
                                        view = JSON.parse(attrValue).view;
                                    } else {
                                        view = attrValue;
                                    }
                                    flowControlChildNodes[view] = cNode;
                                }
                            };
                        // saves all children in a object
                        [].slice.call(node.children).forEach(findChildren);

                        return {
                            hasChildrenWithName : function (viewName) {
                                return flowControlChildNodes.hasOwnProperty(viewName);
                            },
                            getViewName : function () {
                                return attr.view;
                            },
                            show : function () {
                                parentView && parentView.show();
                                node.style.display = '';
                            },
                            hide : function () {
                                node.style.display = 'none';
                            },
                            fadeOut : function (cb) {
                                fc.fadeOut(node, cb || function () {});
                            },
                            getNode : function () {
                                return node;
                            },
                            fadeIn : function (cb) {
                                parentView && parentView.show();  // do show fadeIn has flickering
                                fc.fadeIn(node,  cb || function () {});
                            }
                        };

                    }(node, fc.getParentNode(attr.view)));
                },
                // TODO rename it to fadeIn
                show : function (name) {    // module specific
                    var showMods = [].slice.call(arguments),
                        queue = Object.keys(modViews),
                        queueCount = queue.length,
                        fadeIn = function () {
                            showMods.forEach(function (module) {
                                if (modViews.hasOwnProperty(module)) {
                                    modViews[module].fadeIn(function () {
                                        // TODO remove
                                        console.log('FADE IN DONE');
                                    });
                                }
                            });
                        };
                    showMods = fc.addParents(showMods);
                    // hide all (except incoming)

                    queue.forEach(function (obj) {
                        if (showMods.indexOf(obj) === -1) {
                            modViews[obj].fadeOut(function () {
                                queueCount--;
                                if (queueCount <= 0) {
                                    // FADE IN
                                    fadeIn();
                                }
                            });
                        } else {
                            queueCount--;
                            if (queueCount <= 0) {
                                fadeIn();
                            }
                        }
                    });
                },
                // rename it to show
                showImmediately : function (name) {    // module specific
                    var showMods = [].slice.call(arguments),
                        queue = Object.keys(modViews),
                        queueCount = queue.length,
                        show = function () {
                            showMods.forEach(function (module) {
                                if (modViews.hasOwnProperty(module)) {
                                    modViews[module].show();
                                }
                            });
                        };
                    showMods = fc.addParents(showMods);
                    // hide all (except incoming)
                    queue.forEach(function (obj) {
                        queueCount--;
                        if (showMods.indexOf(obj) === -1) {
                            modViews[obj].hide();
                        }
                        if (queueCount <= 0) {
                            show();
                        }
                    });
                },
                overlay : function (name) {
                    var node;
                    // it's own module?
                    if (modViews.hasOwnProperty(name)) {
                        node = modViews[name].getNode();
                    } else {
                        node = document.getElementById(name);
                    }

                    return {
                        by : function (name, text) {
                            return ext[name](node, text);
                        }
                    };
                }
            };
        },
        flowControl  = (function () {
            var instances = {};
            return function (name) {
                var instance,
                    def = name || 'flowControl';
                if (instances.hasOwnProperty(def)) {
                    instance = instances[def];
                } else {
                    instances[def] = flowControlInstance();
                    instance = instances[def];
                }
                return instance;
            };
        }());
    // export as module or bind to global
    if (typeof module !== 'undefined') { module.exports = flowControl; } else {canny.add('flowControl', flowControl('flowControl')); }

}());
<commit_msg>Add callback for show method<commit_after>/*global canny */
/*jslint browser: true*/

/**
 * E.g.: canny-mod="flowControl" canny-var="{'view' : 'viewName'}"
 *
 * you can activate a initial view with a anchor in the URL e.g.: yourdomain.html#viewToShow
 * Or pass a comma separated module list for activate more module #viewToShow,otherView
 *
 * TODO made it possible to summarize views with one identifier.
 * Instead of call: gdom.flowControl.show('view1', 'view2', 'view3') call gdom.flowControl.show('view').
 *
 * TODO add a hide method that just hide the specific element.
 */
(function () {
    "use strict";

    var flowControlInstance = function () {

            var modViews = {}, // saves module views
                getViewAnchor = function () {
                    var hash = location.hash || null, hashSub;
                    if (hash) {
                        hashSub = hash.substr(1);
                        return hashSub.split(',');
                    }
                    return hash;
                },
                showInitialView = getViewAnchor(),
                fc = {
                    // get the parent module from the given viewName
                    getParentNode : function (viewName) {
                        var queue = Object.keys(modViews), l, i;
                        l = queue.length;
                        for (i = 0; i < l; i++) {
                            if (modViews[queue[i]].hasChildrenWithName(viewName)) {
                                return modViews[queue[i]];
                            }
                        }
                        return null;
                    },
                    // passes a view list and complete the list with all parent node names
                    addParents : function (views) {
                        var extViews = views, i, l, pNode,
                            pushExtViews = function (name) {
                                if (extViews.indexOf(name) === -1) {
                                    extViews.push(name);
                                }
                            },
                            addParentView = function (viewName) {
                                var pViewName = fc.getParentNode(viewName);
                                if (pViewName) {
                                    // TOOD while has parent add it to the extViews
                                    pushExtViews(pViewName.getViewName());
                                    addParentView(pViewName.getViewName());
                                }
                            };
                        l = views.length;
                        for (i = 0; i < l; i++) {
                            pNode = fc.getParentNode(views[i]);
                            if (pNode) {
                                pushExtViews(pNode.getViewName());
                                // so far we have parents do it recursive
                                addParentView(pNode.getViewName());
                            }
                        }
                        return extViews;
                    },
                    fadeOut : function (node, cb) {
                        var opacity = node.style.opacity || 1,
                            fade = function (op) {
                                if (op > 0) {
                                    node.style.opacity = op;

                                    setTimeout(function () {
                                        fade(op - 0.1);
                                    }, 30);
                                } else {
                                    node.style.display = 'none';
                                    cb();
                                }
                            };
                        fade(opacity);
                        console.log('fadeOut', node);
                    },
                    fadeIn : function (node, cb) {
                        var opacity = node.opacity || 0,
                            fade = function (op) {
                                if (op <= 1) {
                                    node.style.opacity = op;
                                    setTimeout(function () {
                                        fade(op + 0.1);
                                    }, 30);
                                } else {
                                    cb();
                                }
                            };
                        if (node.style.display === 'none') {
                            node.style.opacity = opacity;
                            node.style.display = '';
                            fade(opacity);
                        } else {
                            node.style.opacity = 1;
                            console.log('fadeIn', node);
                        }
                    }
                },
                ext = {
                    /**
                     *
                     * @param node
                     * @param innerNode
                     * @returns {{remove: remove}}
                     */
                    progress : function (node, innerNode) {
                        var newNode = document.createElement('div'), centerNode = document.createElement('div'), txtNode;
                        node.style.position = 'relative';
                        newNode.style.opacity = '0.6';
                        newNode.style.backgroundColor = '#666';
                        newNode.style.position = 'absolute';
                        newNode.style.top = 0;
                        newNode.style.left = 0;
                        newNode.style.width = node.offsetWidth + 'px';
                        newNode.style.height = node.offsetHeight + 'px';
                        newNode.style.borderRadius = window.getComputedStyle(node, null).borderRadius;

                        centerNode.style.position = 'absolute';
                        centerNode.style.top = (node.offsetHeight / 2) - 30 + 'px';
                        centerNode.style.width = node.offsetWidth + 'px';
                        centerNode.style.textAlign = 'center';

                        if (innerNode) {
                            centerNode.appendChild(innerNode);
                        }
                        node.appendChild(newNode);
                        node.appendChild(centerNode);
                        return {
                            remove : function (delay, cb) {
                                setTimeout(function () {
                                    node.removeChild(newNode);
                                    node.removeChild(centerNode);
                                    cb && cb();
                                }, delay || 0);
                            },
                            fadeOut : function (delay, cb) {
                                setTimeout(function () {
                                    fc.fadeOut(newNode, function () {
                                        node.removeChild(newNode);
                                        node.removeChild(centerNode);
                                        cb && cb();
                                    });
                                }, delay || 0);
                            }
                        };
                    }
                };

            return {
                mod : modViews, // part of api
                ready : function () {
                    var modNames, i, l;
                    if (showInitialView) {
                        modNames = Object.keys(this.mod);
                        l = modNames.length;
                        // check if showInitialView contains a registered module
                        for (i = 0; i < l; i++) {
                            if (showInitialView.indexOf(modNames[i]) !== -1) {
                                this.showImmediately.apply(null, showInitialView);
                                break;
                            }
                        }
                    }
                },
                add : function (node, attr) {    // part of api

                    modViews[attr.view] = (function (node, parentView) {
                        var flowControlChildNodes = {},
                            // TODO do it with a querySelectorAll
                            findChildren = function (cNode) {
                                if (cNode.hasChildNodes()) {
                                    [].slice.call(cNode.children).forEach(findChildren);
                                }
                                var modAttr = cNode.getAttribute('gd-module'), attrValue, view;
                                if (/flowControl/.test(modAttr)) {
                                    console.log(cNode);
                                    // TODO read attributes should be part gdom functionality
                                    attrValue = cNode.getAttribute('gd-attr').split("\'").join('\"');
                                    if (/:/.test(attrValue)) {
                                        // could be a JSON
                                        view = JSON.parse(attrValue).view;
                                    } else {
                                        view = attrValue;
                                    }
                                    flowControlChildNodes[view] = cNode;
                                }
                            };
                        // saves all children in a object
                        [].slice.call(node.children).forEach(findChildren);

                        return {
                            hasChildrenWithName : function (viewName) {
                                return flowControlChildNodes.hasOwnProperty(viewName);
                            },
                            getViewName : function () {
                                return attr.view;
                            },
                            show : function () {
                                parentView && parentView.show();
                                node.style.display = '';
                            },
                            hide : function () {
                                node.style.display = 'none';
                            },
                            fadeOut : function (cb) {
                                fc.fadeOut(node, cb || function () {});
                            },
                            getNode : function () {
                                return node;
                            },
                            fadeIn : function (cb) {
                                parentView && parentView.show();  // do show fadeIn has flickering
                                fc.fadeIn(node,  cb || function () {});
                            }
                        };

                    }(node, fc.getParentNode(attr.view)));
                },
                // TODO rename it to fadeIn
                show : function (name) {    // module specific
                    var showMods = [].slice.call(arguments),
                        queue = Object.keys(modViews),
                        queueCount = queue.length,
                        fadeIn = function () {
                            showMods.forEach(function (module) {
                                if (modViews.hasOwnProperty(module)) {
                                    modViews[module].fadeIn(function () {
                                        // TODO remove
                                        console.log('FADE IN DONE');
                                    });
                                }
                            });
                            // if last param is function than handle it as callback
                            if (typeof showMods[showMods.length - 1] === 'function') {
                                showMods[showMods.length - 1]();
                            }
                        };
                    showMods = fc.addParents(showMods);
                    // hide all (except incoming)

                    queue.forEach(function (obj) {
                        if (showMods.indexOf(obj) === -1) {
                            modViews[obj].fadeOut(function () {
                                queueCount--;
                                if (queueCount <= 0) {
                                    // FADE IN
                                    fadeIn();
                                }
                            });
                        } else {
                            queueCount--;
                            if (queueCount <= 0) {
                                fadeIn();
                            }
                        }
                    });
                },
                // rename it to show
                showImmediately : function (name) {    // module specific
                    var showMods = [].slice.call(arguments),
                        queue = Object.keys(modViews),
                        queueCount = queue.length,
                        show = function () {
                            showMods.forEach(function (module) {
                                if (modViews.hasOwnProperty(module)) {
                                    modViews[module].show();
                                }
                            });
                        };
                    showMods = fc.addParents(showMods);
                    // hide all (except incoming)
                    queue.forEach(function (obj) {
                        queueCount--;
                        if (showMods.indexOf(obj) === -1) {
                            modViews[obj].hide();
                        }
                        if (queueCount <= 0) {
                            show();
                        }
                    });
                },
                overlay : function (name) {
                    var node;
                    // it's own module?
                    if (modViews.hasOwnProperty(name)) {
                        node = modViews[name].getNode();
                    } else {
                        node = document.getElementById(name);
                    }

                    return {
                        by : function (name, text) {
                            return ext[name](node, text);
                        }
                    };
                }
            };
        },
        flowControl  = (function () {
            var instances = {};
            return function (name) {
                var instance,
                    def = name || 'flowControl';
                if (instances.hasOwnProperty(def)) {
                    instance = instances[def];
                } else {
                    instances[def] = flowControlInstance();
                    instance = instances[def];
                }
                return instance;
            };
        }());
    // export as module or bind to global
    if (typeof module !== 'undefined') { module.exports = flowControl; } else {canny.add('flowControl', flowControl('flowControl')); }

}());
<|endoftext|>


<commit_before>var $ = require('jquery');
var Backbone = require('backbone');
var _ = require('underscore');
var DataObservatoryNormalize = require('../../../../../data/data-observatory/normalize-collection');
var DataObservatoryTimespan = require('../../../../../data/data-observatory/timespan-collection');
var DataObservatoryBoundaries = require('../../../../../data/data-observatory/boundaries-collection');
var MeasurementsCollection = require('../../../../../data/data-observatory/measurements-collection');
var DataObservatoryColumnName = require('../../../../../data/data-observatory/column-name');
var checkAndBuildOpts = require('../../../../../helpers/required-opts');

var REQUIRED_OPTS = [
  'regions',
  'form',
  'configModel',
  'nodeDefModel'
];

var DENOMINATORS_BLACKLIST = ['', 'area'];

var wrap = function (value) {
  var result = "'" + value + "'";
  return result;
};

module.exports = Backbone.Model.extend({
  defaults: {
    normalization: 'denormalized',
    normalize: '',
    column_name: ''
  },

  initialize: function (attrs, options) {
    checkAndBuildOpts(options, REQUIRED_OPTS, this);

    var fetchOptions = {
      configModel: this._configModel,
      nodeDefModel: this._nodeDefModel
    };

    this.measurements = new MeasurementsCollection([], fetchOptions);
    this.normalize = new DataObservatoryNormalize(null, fetchOptions);
    this.timespan = new DataObservatoryTimespan(null, fetchOptions);
    this.boundaries = new DataObservatoryBoundaries(null, fetchOptions);
    DataObservatoryColumnName.initialize(fetchOptions);

    // this form has async component that fetch data to the server
    // in order to ignore those changes when fetch to avoid aditional renders of the analysis-control-view
    // we update the model attributes silently
    this._silentChangeInitially = !_.isEmpty(attrs);

    this._fetchNormalize = this._fetchNormalize.bind(this);
    this._syncNormalize = this._syncNormalize.bind(this);
    this._fetchTimespan = this._fetchTimespan.bind(this);
    this._syncTimespan = this._syncTimespan.bind(this);
    this._fetchBoundaries = this._fetchBoundaries.bind(this);
    this._syncBoundaries = this._syncBoundaries.bind(this);
    this._fetchNewColumn = this._fetchNewColumn.bind(this);
    this._columnNameSuccess = this._columnNameSuccess.bind(this);
    this._setSchema = this._setSchema.bind(this);

    if (this.get('normalization') === 'area' && !this.get('normalize')) {
      this.set({ normalize: 'area' }, { silent: this._silentChangeInitially });
    }

    this._initBinds();
    this._setSchema();
  },

  _fetchRegionsError: function () {
    this._setSchema();
  },

  _columnNameSuccess: function (data) {
    var rows;
    var metadata;

    rows = data.rows;
    if (!rows || rows.length === 0) return;

    metadata = rows[0].obs_getmeta;
    if (!metadata || metadata.length === 0) return;

    var suggestedName = metadata[0].suggested_name;

    this.set({ column_name: suggestedName }, { silent: this._silentChangeInitially });
    this._setSchema();

    if (this._silentChangeInitially) {
      this._silentChangeInitially = false;
    } else {
      this._form.commit(); // To update the hidden input value
      this._form.trigger('change'); // To force validation
    }
  },

  _setNormalization: function () {
    var denom = this.get('normalize');
    denom = denom === '' ? null : denom;
    var normalization = 'denormalized';

    if (denom) {
      normalization = (denom === 'area') ? 'area' : 'denominated';
    }

    this.set('normalization', normalization);
  },

  _getColumnNameOptions: function () {
    var normalize = this.get('normalize') || null;
    var normalization = this.get('normalization');

    var defaults = {
      numer_id: null,
      denom_id: null,
      numer_timespan: null
    };

    var columnOptions = _.defaults({
      numer_id: this.get('segment_name'),
      denom_id: normalize,
      numer_timespan: this.get('timespan') || null,
      normalization: normalization
    }, defaults);

    return _.extend(
      {
        success: this._columnNameSuccess
      },
      _.pick(columnOptions, _.identity)
    );
  },

  _initBinds: function () {
    // If measurement exists, we wait to get all measurement to fetch the rest of collections
    // because we need the type and aggregation of the selected measurement for normalization
    var measurement = this.get('segment_name');
    if (measurement) {
      this.listenToOnce(this.measurements, 'reset', this._checkIfMeasurementExist);
    }

    this.listenTo(this._form.model, 'change:area', this._checkIfMeasurementExist);
    this.on('change:segment_name', this._measurementChanged, this);
    this.on('change:normalize', this._normalizeChanged, this);
    this.on('change:timespan', this._timestampChanged, this);
  },

  _checkIfMeasurementExist: function () {
    var measurement = this.get('segment_name');

    // If measurement is set, we fetch all collections
    if (measurement) {
      this._measurementChanged();
    }
  },

  _measurementChanged: function (mdl, measure) {
    this._fetchNormalize()
      .then(this._syncNormalize)
      .then(this._fetchTimespan)
      .then(this._syncTimespan)
      .then(this._fetchBoundaries)
      .then(this._syncBoundaries)
      .then(this._fetchNewColumn);
  },

  _fetchNormalize: function () {
    var fetchOptions = this._getUserSelection();
    return this.normalize.fetch(fetchOptions);
  },

  _syncNormalize: function () {
    var deferred = $.Deferred();
    var normalize = this.get('normalize');
    var selected = this.normalize.setSelected(normalize);
    if (!selected) {
      this.set({ normalize: '' }, { silent: true });
    }

    this._setNormalization();
    deferred.resolve();
    return deferred.promise();
  },

  _fetchTimespan: function () {
    var fetchOptions = this._getUserSelection();
    return this.timespan.fetch(fetchOptions);
  },

  _syncTimespan: function () {
    var deferred = $.Deferred();
    var timespan = this.get('timespan');
    var selected = this.timespan.setSelected(timespan);
    if (!selected) {
      this.set({ timespan: null }, { silent: true });
    }

    deferred.resolve();
    return deferred.promise();
  },

  _fetchBoundaries: function () {
    var fetchOptions = this._getUserSelection();
    return this.boundaries.fetch(fetchOptions);
  },

  _syncBoundaries: function () {
    var deferred = $.Deferred();
    var boundaries = this.get('boundaries');
    var selected = this.boundaries.setSelected(boundaries);
    if (!selected) {
      this.set({ boundaries: null }, { silent: true });
    }
    deferred.resolve();
    return deferred.promise();
  },

  _fetchNewColumn: function () {
    var columnOptions = this._getColumnNameOptions();
    DataObservatoryColumnName.fetch(columnOptions);
  },

  _normalizeChanged: function (mdl, normalize) {
    this._setNormalization();
    this._fetchTimespan()
      .then(this._syncTimespan)
      .then(this._fetchBoundaries)
      .then(this._syncBoundaries)
      .then(this._fetchNewColumn);
  },

  _timestampChanged: function () {
    this._fetchBoundaries()
      .then(this._syncBoundaries)
      .then(this._fetchNewColumn);
  },

  _getNormalizeOptions: function () {
    var measurement = this.measurements.getSelectedItem();
    var options = this.normalize.models;

    if (measurement && measurement.get('type') === 'Numeric' && measurement.get('aggregate') === 'sum') {
      options = options.concat([{
        label: _t('analyses.data-observatory-measure.area'),
        val: 'area'
      }]);
    }

    return options;
  },

  _getTimespanOptions: function () {
    return this.timespan.models;
  },

  _getUserSelection: function () {
    var normalize = this.get('normalize');
    // We are inserting area option manually, so we need to filter that value for the backend
    normalize = (DENOMINATORS_BLACKLIST.indexOf(normalize) >= 0) ? null : wrap(normalize);

    var timespan = this.get('timespan');
    timespan = timespan != null ? wrap(timespan) : null;

    var region = wrap(this._form.model.get('area'));
    var measurement = wrap(this.get('segment_name'));

    return {
      region: region,
      measurement: measurement,
      normalize: normalize,
      timespan: timespan
    };
  },

  _setSchema: function () {
    var schema = {
      segment_name: {
        type: 'DataObservatoryDropdown',
        title: _t('editor.layers.analysis-form.data-observatory.measurements.label'),
        editorAttrs: {
          configModel: this._configModel,
          nodeDefModel: this._nodeDefModel,
          model: this._form.model,
          placeholder: _t('editor.layers.analysis-form.data-observatory.measurements.placeholder'),
          measurements: this.measurements,
          filters: this.filters,
          silentChangeInitially: this._silentChangeInitially
        },
        dialogMode: 'float',
        region: this._form.model.get('area'),
        validators: ['required']
      },
      normalize: {
        type: 'EnablerEditor',
        title: '',
        label: _t('editor.layers.analysis-form.data-observatory.normalize.label'),
        isDisabled: this.get('segment_name') == null,
        editor: {
          type: 'Select',
          options: this._getNormalizeOptions(),
          dialogMode: 'float',
          editorAttrs: {
            showSearch: false,
            showLabel: false
          }
        }
      },
      timespan: {
        type: 'Select',
        title: _t('editor.layers.analysis-form.data-observatory.timespan.label'),
        options: this._getTimespanOptions(),
        dialogMode: 'float',
        editorAttrs: {
          placeholder: _t('editor.layers.analysis-form.data-observatory.timespan.placeholder')
        }
      },
      boundaries: {
        type: 'Slider',
        title: _t('editor.layers.analysis-form.data-observatory.boundaries.label'),
        values: this.boundaries.getValues(),
        labels: this.boundaries.getLabels()
      },
      column_name: {
        type: 'Hidden',
        editor: {
          value: this.get('column_name')
        }
      },
      normalization: {
        type: 'Hidden',
        editor: {
          value: this.get('normalization')
        }
      }
    };

    this.schema = schema;
    this.trigger('updateSchema', this.schema);
  }

});
<commit_msg>Disable normalize if no options.<commit_after>var $ = require('jquery');
var Backbone = require('backbone');
var _ = require('underscore');
var DataObservatoryNormalize = require('../../../../../data/data-observatory/normalize-collection');
var DataObservatoryTimespan = require('../../../../../data/data-observatory/timespan-collection');
var DataObservatoryBoundaries = require('../../../../../data/data-observatory/boundaries-collection');
var MeasurementsCollection = require('../../../../../data/data-observatory/measurements-collection');
var DataObservatoryColumnName = require('../../../../../data/data-observatory/column-name');
var checkAndBuildOpts = require('../../../../../helpers/required-opts');

var REQUIRED_OPTS = [
  'regions',
  'form',
  'configModel',
  'nodeDefModel'
];

var DENOMINATORS_BLACKLIST = ['', 'area'];

var wrap = function (value) {
  var result = "'" + value + "'";
  return result;
};

module.exports = Backbone.Model.extend({
  defaults: {
    normalization: 'denormalized',
    normalize: '',
    column_name: ''
  },

  initialize: function (attrs, options) {
    checkAndBuildOpts(options, REQUIRED_OPTS, this);

    var fetchOptions = {
      configModel: this._configModel,
      nodeDefModel: this._nodeDefModel
    };

    this.measurements = new MeasurementsCollection([], fetchOptions);
    this.normalize = new DataObservatoryNormalize(null, fetchOptions);
    this.timespan = new DataObservatoryTimespan(null, fetchOptions);
    this.boundaries = new DataObservatoryBoundaries(null, fetchOptions);
    DataObservatoryColumnName.initialize(fetchOptions);

    // this form has async component that fetch data to the server
    // in order to ignore those changes when fetch to avoid aditional renders of the analysis-control-view
    // we update the model attributes silently
    this._silentChangeInitially = !_.isEmpty(attrs);

    this._fetchNormalize = this._fetchNormalize.bind(this);
    this._syncNormalize = this._syncNormalize.bind(this);
    this._fetchTimespan = this._fetchTimespan.bind(this);
    this._syncTimespan = this._syncTimespan.bind(this);
    this._fetchBoundaries = this._fetchBoundaries.bind(this);
    this._syncBoundaries = this._syncBoundaries.bind(this);
    this._fetchNewColumn = this._fetchNewColumn.bind(this);
    this._columnNameSuccess = this._columnNameSuccess.bind(this);
    this._setSchema = this._setSchema.bind(this);

    if (this.get('normalization') === 'area' && !this.get('normalize')) {
      this.set({ normalize: 'area' }, { silent: this._silentChangeInitially });
    }

    this._initBinds();
    this._setSchema();
  },

  _fetchRegionsError: function () {
    this._setSchema();
  },

  _columnNameSuccess: function (data) {
    var rows;
    var metadata;

    rows = data.rows;
    if (!rows || rows.length === 0) return;

    metadata = rows[0].obs_getmeta;
    if (!metadata || metadata.length === 0) return;

    var suggestedName = metadata[0].suggested_name;

    this.set({ column_name: suggestedName }, { silent: this._silentChangeInitially });
    this._setSchema();

    if (this._silentChangeInitially) {
      this._silentChangeInitially = false;
    } else {
      this._form.commit(); // To update the hidden input value
      this._form.trigger('change'); // To force validation
    }
  },

  _setNormalization: function () {
    var denom = this.get('normalize');
    denom = denom === '' ? null : denom;
    var normalization = 'denormalized';

    if (denom) {
      normalization = (denom === 'area') ? 'area' : 'denominated';
    }

    this.set('normalization', normalization);
  },

  _getColumnNameOptions: function () {
    var normalize = this.get('normalize') || null;
    var normalization = this.get('normalization');

    var defaults = {
      numer_id: null,
      denom_id: null,
      numer_timespan: null
    };

    var columnOptions = _.defaults({
      numer_id: this.get('segment_name'),
      denom_id: normalize,
      numer_timespan: this.get('timespan') || null,
      normalization: normalization
    }, defaults);

    return _.extend(
      {
        success: this._columnNameSuccess
      },
      _.pick(columnOptions, _.identity)
    );
  },

  _initBinds: function () {
    // If measurement exists, we wait to get all measurement to fetch the rest of collections
    // because we need the type and aggregation of the selected measurement for normalization
    var measurement = this.get('segment_name');
    if (measurement) {
      this.listenToOnce(this.measurements, 'reset', this._checkIfMeasurementExist);
    }

    this.listenTo(this._form.model, 'change:area', this._checkIfMeasurementExist);
    this.on('change:segment_name', this._measurementChanged, this);
    this.on('change:normalize', this._normalizeChanged, this);
    this.on('change:timespan', this._timestampChanged, this);
  },

  _checkIfMeasurementExist: function () {
    var measurement = this.get('segment_name');

    // If measurement is set, we fetch all collections
    if (measurement) {
      this._measurementChanged();
    }
  },

  _measurementChanged: function (mdl, measure) {
    this._fetchNormalize()
      .then(this._syncNormalize)
      .then(this._fetchTimespan)
      .then(this._syncTimespan)
      .then(this._fetchBoundaries)
      .then(this._syncBoundaries)
      .then(this._fetchNewColumn);
  },

  _fetchNormalize: function () {
    var fetchOptions = this._getUserSelection();
    return this.normalize.fetch(fetchOptions);
  },

  _syncNormalize: function () {
    var deferred = $.Deferred();
    var normalize = this.get('normalize');
    var selected = this.normalize.setSelected(normalize);
    if (!selected) {
      this.set({ normalize: '' }, { silent: true });
    }

    this._setNormalization();
    deferred.resolve();
    return deferred.promise();
  },

  _fetchTimespan: function () {
    var fetchOptions = this._getUserSelection();
    return this.timespan.fetch(fetchOptions);
  },

  _syncTimespan: function () {
    var deferred = $.Deferred();
    var timespan = this.get('timespan');
    var selected = this.timespan.setSelected(timespan);
    if (!selected) {
      this.set({ timespan: null }, { silent: true });
    }

    deferred.resolve();
    return deferred.promise();
  },

  _fetchBoundaries: function () {
    var fetchOptions = this._getUserSelection();
    return this.boundaries.fetch(fetchOptions);
  },

  _syncBoundaries: function () {
    var deferred = $.Deferred();
    var boundaries = this.get('boundaries');
    var selected = this.boundaries.setSelected(boundaries);
    if (!selected) {
      this.set({ boundaries: null }, { silent: true });
    }
    deferred.resolve();
    return deferred.promise();
  },

  _fetchNewColumn: function () {
    var columnOptions = this._getColumnNameOptions();
    DataObservatoryColumnName.fetch(columnOptions);
  },

  _normalizeChanged: function (mdl, normalize) {
    this._setNormalization();
    this._fetchTimespan()
      .then(this._syncTimespan)
      .then(this._fetchBoundaries)
      .then(this._syncBoundaries)
      .then(this._fetchNewColumn);
  },

  _timestampChanged: function () {
    this._fetchBoundaries()
      .then(this._syncBoundaries)
      .then(this._fetchNewColumn);
  },

  _getNormalizeOptions: function () {
    var measurement = this.measurements.getSelectedItem();
    var options = this.normalize.models;

    if (measurement && measurement.get('type') === 'Numeric' && measurement.get('aggregate') === 'sum') {
      options = options.concat([{
        label: _t('analyses.data-observatory-measure.area'),
        val: 'area'
      }]);
    }

    return options;
  },

  _getTimespanOptions: function () {
    return this.timespan.models;
  },

  _getUserSelection: function () {
    var normalize = this.get('normalize');
    // We are inserting area option manually, so we need to filter that value for the backend
    normalize = (DENOMINATORS_BLACKLIST.indexOf(normalize) >= 0) ? null : wrap(normalize);

    var timespan = this.get('timespan');
    timespan = timespan != null ? wrap(timespan) : null;

    var region = wrap(this._form.model.get('area'));
    var measurement = wrap(this.get('segment_name'));

    return {
      region: region,
      measurement: measurement,
      normalize: normalize,
      timespan: timespan
    };
  },

  _setSchema: function () {
    var normalizeOptions = this._getNormalizeOptions();

    var schema = {
      segment_name: {
        type: 'DataObservatoryDropdown',
        title: _t('editor.layers.analysis-form.data-observatory.measurements.label'),
        editorAttrs: {
          configModel: this._configModel,
          nodeDefModel: this._nodeDefModel,
          model: this._form.model,
          placeholder: _t('editor.layers.analysis-form.data-observatory.measurements.placeholder'),
          measurements: this.measurements,
          filters: this.filters,
          silentChangeInitially: this._silentChangeInitially
        },
        dialogMode: 'float',
        region: this._form.model.get('area'),
        validators: ['required']
      },
      normalize: {
        type: 'EnablerEditor',
        title: '',
        label: _t('editor.layers.analysis-form.data-observatory.normalize.label'),
        isDisabled: this.get('segment_name') == null || normalizeOptions.length === 0,
        editor: {
          type: 'Select',
          options: normalizeOptions,
          dialogMode: 'float',
          editorAttrs: {
            showSearch: false,
            showLabel: false
          }
        }
      },
      timespan: {
        type: 'Select',
        title: _t('editor.layers.analysis-form.data-observatory.timespan.label'),
        options: this._getTimespanOptions(),
        dialogMode: 'float',
        editorAttrs: {
          placeholder: _t('editor.layers.analysis-form.data-observatory.timespan.placeholder')
        }
      },
      boundaries: {
        type: 'Slider',
        title: _t('editor.layers.analysis-form.data-observatory.boundaries.label'),
        values: this.boundaries.getValues(),
        labels: this.boundaries.getLabels()
      },
      column_name: {
        type: 'Hidden',
        editor: {
          value: this.get('column_name')
        }
      },
      normalization: {
        type: 'Hidden',
        editor: {
          value: this.get('normalization')
        }
      }
    };

    this.schema = schema;
    this.trigger('updateSchema', this.schema);
  }

});
<|endoftext|>


<commit_before>(function($){

  /* Adapted from http://www.kryogenix.org/code/browser/searchhi/ */
  $.fn.highlightText = function(text, className) {
    function highlight(node) {
      if (node.nodeType == 3) { // Node.TEXT_NODE
        var val = node.nodeValue;
        var pos = val.toLowerCase().indexOf(text);
        if (pos >= 0 && !$.className.has(node.parentNode, className)) {
          var span = document.createElement("span");
          span.className = className;
          var txt = document.createTextNode(val.substr(pos, text.length));
          span.appendChild(txt);
          node.parentNode.insertBefore(span, node.parentNode.insertBefore(
            document.createTextNode(val.substr(pos + text.length)),
              node.nextSibling));
          node.nodeValue = val.substr(0, pos);
        }
      } else if (!$(node).is("button, select, textarea")) {
        $.each(node.childNodes, function() { highlight(this) });
      }
    }
    return this.each(function() { highlight(this) });
  }

  $(document).ready(function() {
    var elems = $(".searchable");
    if (!elems.length) return;

    function getSearchTerms(url) {
      if (url.indexOf("?") == -1) return [];
      var params = url.substr(url.indexOf("?") + 1).split("&");
      for (var p in params) {
        var param = params[p].split("=");
        if (param.length < 2) continue;
        if (param[0] == "q" || param[0] == "p") {// q= for Google, p= for Yahoo
          var query = decodeURIComponent(param[1].replace(/\+/g, " "));
          if (query[0] == "!") query = query.slice(1);
          var terms = [];
          $.each(query.split(/(".*?"|'.*?'|\s+)/), function() {
            if (terms.length < 10) {
              term = this.replace(/^\s+$/, "")
                         .replace(/^['"]/, "")
                         .replace(/['"]$/, "");
              if (term.length >= 3)
                terms.push(term);
            }
          });
          return terms;
        }
      }
      return [];
    }

    var terms = getSearchTerms(document.URL);
    if (!terms.length) terms = getSearchTerms(document.referrer);
    $.each(terms, function(idx) {
      elems.highlightText(this.toLowerCase(), "searchword" + (idx % 5));
    });
  });

})(jQuery);
<commit_msg>search.js: don't use internal jQuery API `$.className`, which will be gone in 1.4.<commit_after>(function($){

  /* Adapted from http://www.kryogenix.org/code/browser/searchhi/ */
  $.fn.highlightText = function(text, className) {
    function highlight(node) {
      if (node.nodeType == 3) { // Node.TEXT_NODE
        var val = node.nodeValue;
        var pos = val.toLowerCase().indexOf(text);
        if (pos >= 0 && !$(node.parentNode).hasClass(className)) {
          var span = document.createElement("span");
          span.className = className;
          var txt = document.createTextNode(val.substr(pos, text.length));
          span.appendChild(txt);
          node.parentNode.insertBefore(span, node.parentNode.insertBefore(
            document.createTextNode(val.substr(pos + text.length)),
              node.nextSibling));
          node.nodeValue = val.substr(0, pos);
        }
      } else if (!$(node).is("button, select, textarea")) {
        $.each(node.childNodes, function() { highlight(this) });
      }
    }
    return this.each(function() { highlight(this) });
  }

  $(document).ready(function() {
    var elems = $(".searchable");
    if (!elems.length) return;

    function getSearchTerms(url) {
      if (url.indexOf("?") == -1) return [];
      var params = url.substr(url.indexOf("?") + 1).split("&");
      for (var p in params) {
        var param = params[p].split("=");
        if (param.length < 2) continue;
        if (param[0] == "q" || param[0] == "p") {// q= for Google, p= for Yahoo
          var query = decodeURIComponent(param[1].replace(/\+/g, " "));
          if (query[0] == "!") query = query.slice(1);
          var terms = [];
          $.each(query.split(/(".*?"|'.*?'|\s+)/), function() {
            if (terms.length < 10) {
              term = this.replace(/^\s+$/, "")
                         .replace(/^['"]/, "")
                         .replace(/['"]$/, "");
              if (term.length >= 3)
                terms.push(term);
            }
          });
          return terms;
        }
      }
      return [];
    }

    var terms = getSearchTerms(document.URL);
    if (!terms.length) terms = getSearchTerms(document.referrer);
    $.each(terms, function(idx) {
      elems.highlightText(this.toLowerCase(), "searchword" + (idx % 5));
    });
  });

})(jQuery);
<|endoftext|>


<commit_before>var Gmail =  function() {

  var api = {
              get : {},
              observe : {},
              check : {},
              detect : {},
              dom : {}
            };

  api.get.user_email = function() {
    var user_email = null;

    if ($("#gbgs4dn").length == 0) {
      user_email = $("#gbi4t").text();
    } else {
      user_email = $("#gbgs4dn").text();
    }

    if(user_email.indexOf('@') == -1) {
      user_email = $($('.gbps2')[0]).text();
    }

    return user_email.replace(/['"]/g, '').trim();
  };

  api.check.is_thread = function() {
    var check_1 = $($($('.nH .if').children()[1]).children().children()[1]).children();
    var check_2 = $($($($('.nH .if').children()[1]).children()).children()[1]).children();

    return check_1.length > 1 || check_2.length > 1;
  };

  api.dom.inbox_content = function() {
    return $('div[role=main]:first');
  }

  api.check.is_preview_pane = function() {
    var dom = api.dom.inbox_content();
    var box = dom.find("[gh=tl]")[0];

    return box.getAttribute('class').indexOf('aia') != -1;
  }

  api.check.is_horizontal_split = function() {
    var dom = api.dom.inbox_content();
    var box = dom.find("[gh=tl]").find('.nn');

    return box.length == 0;
  }

  api.check.is_vertical_split = function() {
    return api.check.is_horizontal_split() == false;
  }

  api.check.is_tabbed_inbox = function() {
    return $(".aKh").length == 1;
  }

  return api;
}<commit_msg>Added rightside chat check<commit_after>var Gmail =  function() {

  var api = {
              get : {},
              observe : {},
              check : {},
              detect : {},
              dom : {}
            };

  api.get.user_email = function() {
    var user_email = null;

    if ($("#gbgs4dn").length == 0) {
      user_email = $("#gbi4t").text();
    } else {
      user_email = $("#gbgs4dn").text();
    }

    if(user_email.indexOf('@') == -1) {
      user_email = $($('.gbps2')[0]).text();
    }

    return user_email.replace(/['"]/g, '').trim();
  };

  api.check.is_thread = function() {
    var check_1 = $($($('.nH .if').children()[1]).children().children()[1]).children();
    var check_2 = $($($($('.nH .if').children()[1]).children()).children()[1]).children();

    return check_1.length > 1 || check_2.length > 1;
  };

  api.dom.inbox_content = function() {
    return $('div[role=main]:first');
  }

  api.check.is_preview_pane = function() {
    var dom = api.dom.inbox_content();
    var box = dom.find("[gh=tl]")[0];

    return box.getAttribute('class').indexOf('aia') != -1;
  }

  api.check.is_horizontal_split = function() {
    var dom = api.dom.inbox_content();
    var box = dom.find("[gh=tl]").find('.nn');

    return box.length == 0;
  }

  api.check.is_vertical_split = function() {
    return api.check.is_horizontal_split() == false;
  }

  api.check.is_tabbed_inbox = function() {
    return $(".aKh").length == 1;
  }

  api.check.is_right_side_chat = function() {
    return $('.ApVoH')[0].getAttribute('aria-labelledby') == ':wf';
  }

  return api;
}<|endoftext|>


<commit_before>var Utils = require("./utilities");
var DeepClone = require("lodash.clonedeep");
var Merge = require("lodash.mergewith");

function validate(input) {
    // Check it's an object.
    if (!Utils.isObject(input))
        throw new Error("You must provide a loader config object.");

    // Ensure 'test', 'exclude', and 'include' properties are regex values.
    ["test", "exclude", "include"].forEach(function(property) {
        var value = input[property];

        if (Utils.isDefined(value) && !Utils.isRegex(value))
            throw new Error("You must provide a regex value for '" + property + "'.");
    });

    // Ensure the 'loader' property is a string.
    if (Utils.isDefined(input.loader) && !Utils.isString(input.loader))
        throw new Error("You must provide a string value for 'loader'.");

    // Ensure the 'loaders' property is an array of strings.
    if (Utils.isDefined(input.loaders)) {
        if (!Utils.isArray(input.loaders))
            throw new Error("You must provide an array of string values for 'loaders'.");

        // Check each array item is a string.
        input.loaders.forEach(function(loader) {
            if (!Utils.isString(loader))
                throw new Error("You must provide an array of string values for 'loaders'.");
        });
    }

    // Ensure the 'query' property is an object.
    if (Utils.isDefined(input.query) && !Utils.isObject(input.query))
        throw new Error("You must provide an object for 'query'.");

    // Ensure the 'queries' property is an object of objects.
    if (Utils.isDefined(input.queries)) {
        if (!Utils.isObject(input.queries))
            throw new Error("You must provide an object of objects for 'queries'.");

        // Check each array item is a string.
        Object.keys(input.queries).forEach(function(key) {
            if (!Utils.isObject(input.queries[key]))
                throw new Error("You must provide an object of objects for 'queries'.");
        });
    }

    return input;
}

function merge(config, changes, property, customizer) {
    var source = changes;

    // Handle 'changes' as a function.
    if (Utils.isFunction(changes))
        source = changes(config);

    // Ensure 'changes' resolves to an object.
    if (!Utils.isObject(source))
        throw new Error("You must provide an object or function (that returns an object) for 'changes'.");

    // Validate the resolved value of 'changes'.
    try {
        validate(source);
    } catch(err) {
        throw new Error("Invalid 'changes' value. " + err.message);
    }

    if (Utils.isDefined(property)) {
        if (!Utils.isString(property))
            throw new Error("You must provide a string value for 'property'.");

        // Wrap 'changes' within the property: e.g. {[property]: changes}.
        var wrapper = {};
        wrapper[property] = source;
        source = wrapper;
    }

    if (Utils.isDefined(customizer) && !Utils.isFunction(customizer))
        throw new Error("You must provide a function for 'customizer'.");

    Merge(config, source, customizer);
}

function set(config, changes, property) {

}

function resolve(config) {
    return DeepClone(config);
}

module.exports = function loader(input) {
    // Validate and clone the input.
    var config = DeepClone(validate(input));

    return {
        merge: function(property, changes, customizer) {
            var args = Array.prototype.slice.call(arguments);

            (function() {
                // Handle if no arguments are passed.
                if (!args.length)
                    throw new Error("You must provide either an object or function value for 'changes'.");

                // Handle when just changes are passed.
                // Note: This will either be an object or a function.
                if (args.length == 1)
                    return merge(config, property);

                // Handle when a property or customizer is passed.
                if (args.length == 2) {
                    // If the second parameter is a function, we assume they
                    // passed 'changes' and 'customizer'.
                    if (Utils.isFunction(changes))
                        return merge(config, property, undefined, changes);

                    // Assume they passed a 'property' and 'changes'.
                    return merge(config, changes, property);
                }

                // Handle when all arguments are passed.
                // Note: we ignore any other parameters.
                if (args.length >= 3)
                    return merge(config, changes, property, customizer);
            })();

            // Return 'this' to enable function chaining.
            return this;
        },
        set: function(changes) {
            set(config, changes);

            return this;
        },
        resolve: function() {
            return resolve(config);
        }
    };
};
<commit_msg>Removed the need to convert arguments to a real array.<commit_after>var Utils = require("./utilities");
var DeepClone = require("lodash.clonedeep");
var Merge = require("lodash.mergewith");

function validate(input) {
    // Check it's an object.
    if (!Utils.isObject(input))
        throw new Error("You must provide a loader config object.");

    // Ensure 'test', 'exclude', and 'include' properties are regex values.
    ["test", "exclude", "include"].forEach(function(property) {
        var value = input[property];

        if (Utils.isDefined(value) && !Utils.isRegex(value))
            throw new Error("You must provide a regex value for '" + property + "'.");
    });

    // Ensure the 'loader' property is a string.
    if (Utils.isDefined(input.loader) && !Utils.isString(input.loader))
        throw new Error("You must provide a string value for 'loader'.");

    // Ensure the 'loaders' property is an array of strings.
    if (Utils.isDefined(input.loaders)) {
        if (!Utils.isArray(input.loaders))
            throw new Error("You must provide an array of string values for 'loaders'.");

        // Check each array item is a string.
        input.loaders.forEach(function(loader) {
            if (!Utils.isString(loader))
                throw new Error("You must provide an array of string values for 'loaders'.");
        });
    }

    // Ensure the 'query' property is an object.
    if (Utils.isDefined(input.query) && !Utils.isObject(input.query))
        throw new Error("You must provide an object for 'query'.");

    // Ensure the 'queries' property is an object of objects.
    if (Utils.isDefined(input.queries)) {
        if (!Utils.isObject(input.queries))
            throw new Error("You must provide an object of objects for 'queries'.");

        // Check each array item is a string.
        Object.keys(input.queries).forEach(function(key) {
            if (!Utils.isObject(input.queries[key]))
                throw new Error("You must provide an object of objects for 'queries'.");
        });
    }

    return input;
}

function merge(config, changes, property, customizer) {
    var source = changes;

    // Handle 'changes' as a function.
    if (Utils.isFunction(changes))
        source = changes(config);

    // Ensure 'changes' resolves to an object.
    if (!Utils.isObject(source))
        throw new Error("You must provide an object or function (that returns an object) for 'changes'.");

    // Validate the resolved value of 'changes'.
    try {
        validate(source);
    } catch(err) {
        throw new Error("Invalid 'changes' value. " + err.message);
    }

    if (Utils.isDefined(property)) {
        if (!Utils.isString(property))
            throw new Error("You must provide a string value for 'property'.");

        // Wrap 'changes' within the property: e.g. {[property]: changes}.
        var wrapper = {};
        wrapper[property] = source;
        source = wrapper;
    }

    if (Utils.isDefined(customizer) && !Utils.isFunction(customizer))
        throw new Error("You must provide a function for 'customizer'.");

    Merge(config, source, customizer);
}

function set(config, changes, property) {

}

function resolve(config) {
    return DeepClone(config);
}

module.exports = function loader(input) {
    // Validate and clone the input.
    var config = DeepClone(validate(input));

    return {
        merge: function(property, changes, customizer) {
            var argsLength = arguments.length;

            (function() {
                // Handle if no arguments are passed.
                if (!argsLength)
                    throw new Error("You must provide either an object or function value for 'changes'.");

                // Handle when just changes are passed.
                // Note: This will either be an object or a function.
                if (argsLength == 1)
                    return merge(config, property);

                // Handle when a property or customizer is passed.
                if (argsLength == 2) {
                    // If the second parameter is a function, we assume they
                    // passed 'changes' and 'customizer'.
                    if (Utils.isFunction(changes))
                        return merge(config, property, undefined, changes);

                    // Assume they passed a 'property' and 'changes'.
                    return merge(config, changes, property);
                }

                // Handle when all arguments are passed.
                // Note: we ignore any other parameters.
                if (argsLength >= 3)
                    return merge(config, changes, property, customizer);
            })();

            // Return 'this' to enable function chaining.
            return this;
        },
        set: function(changes) {
            set(config, changes);

            return this;
        },
        resolve: function() {
            return resolve(config);
        }
    };
};
<|endoftext|>