<commit_before>/* global _su, strWrap, Point, HexGrid */

class SkillPlanner {
  constructor () {
    this.checkDependencies()

    // Skill tree data
    this.skills = {}
    this.groups = {}
    this.coords = {}
    this.links = {}
    this.tree = {}
    this.locale = {}
    this.colors = {}

    this.reset()

    // DEBUG
    this.DEBUG = false
    // END DEBUG
    this.initGrid()
  }

  checkDependencies () {
    if (typeof HexGrid !== 'function' || !_su) {
      console.log('Warning! Required dependencies not found. Please contact the dev for more info.')
    }
  }

  reset () {
    this.category = 'Firepower'
    this.active = new Set()
    this.orphan = new Set()
    this.possible = new Set()
    this.visibleActive = new Set()
    this.visibleOrphan = new Set()
    this.visiblePossible = new Set()
    this.effects = {}
    this.mouseDown = false
    this.prevHexId = null
  }

  initGrid () {
    let radius = 48
    let padding = 16
    let origin = new Point(-96, 128)

    let hexGrid = new HexGrid(radius, padding, origin)
    hexGrid.bindCanvas(document.getElementById('hex'))
    this.hexGrid = hexGrid

    let linkGrid = new HexGrid(radius, padding, origin)
    linkGrid.bindCanvas(document.getElementById('link'))
    this.linkGrid = linkGrid

    this.initEvents()
    this.initInterface()
  }

  initEvents () {
    let hexGrid = this.hexGrid
    let onMouseDown = (e) => {
      e.preventDefault()
      this.mouseDown = true
    }

    let onMouseMove = (e) => {
      e.preventDefault()
      if (this.mouseDown) {
        let point = hexGrid.getCursorCanvasPoint(e)
        let hex = hexGrid.getHexAtPoint(point)

        if (hex) {
          let hexId = this.skillHexToId(hex)

          if (hexId !== this.prevHexId) {
            this.toggleNode(hexId)
            this.prevHexId = hexId
          }
        }
      }
    }

    let onMouseUp = (e) => {
      e.preventDefault()
      let point = hexGrid.getCursorCanvasPoint(e)
      let hex = hexGrid.getHexAtPoint(point)

      if (hex) {
        let hexId = this.skillHexToId(hex)

        if (e.ctrlKey) {
          let activate = !this.active.has(hexId)
          let nodeGroup = this.groups[this.skills[hexId].group]
          for (let node of nodeGroup) {
            if (activate) {
              this.activateNode(node)
            } else {
              this.deactivateNode(node)
            }
          }
        } else {
          if (hexId !== this.prevHexId) {
            this.toggleNode(hexId)
            this.prevHexId = hexId
          }
        }
      }
      this.prevHexId = null
      this.mouseDown = false
    }

    this.bind('mousedown', onMouseDown, this)
    this.bind('mousemove', onMouseMove, this)
    this.bind('mouseup', onMouseUp, this)
  }

  initInterface () {
    let bindEvent = (element, event, fn) => {
      document.getElementById(element).addEventListener('click', fn, false)
    }

    bindEvent('toggle-firepower', 'click', () => {
      this.category = 'Firepower'
      this.draw()
    })

    bindEvent('toggle-survival', 'click', () => {
      this.category = 'Survival'
      this.draw()
    })

    bindEvent('toggle-mobility', 'click', () => {
      this.category = 'Mobility'
      this.draw()
    })

    bindEvent('toggle-jumpjets', 'click', () => {
      this.category = 'Jump Jets'
      this.draw()
    })

    bindEvent('toggle-operations', 'click', () => {
      this.category = 'Operations'
      this.draw()
    })

    bindEvent('toggle-sensors', 'click', () => {
      this.category = 'Sensors'
      this.draw()
    })

    bindEvent('toggle-auxiliary', 'click', () => {
      this.category = 'Auxiliary'
      this.draw()
    })

    bindEvent('add-section', 'click', () => {
      this.active = _su.union(this.active, this.tree[this.category].nodes)
      this.draw()
    })

    bindEvent('clear-section', 'click', () => {
      this.active = _su.difference(this.active, this.tree[this.category].nodes)
      this.draw()
    })

    bindEvent('clear-all', 'click', () => {
      this.active = new Set()
      this.draw()
    })

    bindEvent('export', 'click', () => {
      window.prompt('Please copy this Session String:', this.exportSession())
    })

    bindEvent('import', 'click', () => {
      let sessionString = window.prompt('Please enter the Session String to import:')
      this.importSession(sessionString)
    })
  }

  loadData (cb) {
    let loadColors = this.getJSON('./data/colors.json')
      .then((colors) => {
        this.colors = colors
      })
      .catch((err) => {
        console.error(err)
      })

    let loadSkills = this.getJSON('./data/latest.json')
      .then((tree) => {
        for (let category in tree) {
          let subtree = tree[category]
          // Load all skill nodes
          Object.assign(this.skills, subtree.nodes)

          this.tree[category] = {
            'root': subtree['root'],
            'nodes': new Set()
          }

          // Hex coord to node lookup
          this.coords[category] = {}
          for (let id in subtree.nodes) {
            let skill = this.skills[id]
            let coord = `(${skill.col}, ${skill.row})`
            this.coords[category][coord] = id

            // Add node to subtree
            this.tree[category]['nodes'].add(id)
          }
        }

        for (let id in this.skills) {
          let skill = this.skills[id]

          // Add node to group
          let group = skill['group']
          if (this.groups[group]) {
            this.groups[group].add(id)
          } else {
            this.groups[group] = new Set([id])
          }

          // Process node links
          this.links[id] = []
          let links = skill.links
          for (let i = 0, len = links.length; i < len; i++) {
            if (links[i]) {
              this.links[id].push(links[i])
            }
          }
        }
      })
      .catch((err) => {
        console.error(err)
      })

    let loadLocale = this.getJSON('./data/locale.json')
      .then((locale) => {
        this.locale = locale
      })
      .catch((err) => {
        console.error(err)
      })

    Promise.all([loadColors, loadSkills, loadLocale])
      .then(() => {
        typeof cb === 'function' && cb()
      })
  }

  bind (action, fn, ctx) {
    document.addEventListener(action, fn.bind(ctx || this), false)
  }

  getJSON (url) {
    return new Promise((resolve, reject) => {
      let req = new window.XMLHttpRequest()
      req.open('GET', url, true)

      req.onload = function () {
        if (this.status >= 200 && this.status < 400) {
          resolve(JSON.parse(this.response))
        } else {
          reject(new Error('Failed to retrieve URL!'))
        }
      }

      req.send()
    })
  }

  skillIdToHex (id) {
    let col = this.skills[id].col
    let row = this.skills[id].row
    return `(${col}, ${row})`
  }

  skillHexToId (hex) {
    let col = hex.col
    let row = hex.row
    return this.coords[this.category][`(${col}, ${row})`]
  }

  toggleNode (nodeId) {
    // Ignore invalid/non-visible nodes
    if (!nodeId || !this.tree[this.category].nodes.has(nodeId)) { return }

    if (this.active.has(nodeId)) {
      this.active.delete(nodeId)
    } else {
      this.active.add(nodeId)
    }
    this.draw()
  }

  activateNode (nodeId) {
    // Ignore invalid/non-visible nodes
    if (!nodeId || !this.tree[this.category].nodes.has(nodeId)) { return }
    this.active.add(nodeId)
    this.draw()
  }

  deactivateNode (nodeId) {
    // Ignore invalid/non-visible nodes
    if (!nodeId || !this.tree[this.category].nodes.has(nodeId)) { return }
    this.active.delete(nodeId)
    this.draw()
  }

  // Draw skill node and label
  drawNode (skill, state) {
    let col = skill.col
    let row = skill.row
    this.hexGrid.drawHexCell(col, row, this.colors['cell'][state])

    let name = skill.name
    let text = (name.length > 12) ? strWrap(name) : name
    this.hexGrid.drawHexLabel(col, row, text, undefined, this.colors['label'][state])
  }

  // Recalculate state using DFS
  update () {
    let updateNodeState = () => {
      let active = new Set(this.active)
      let possible = new Set()
      let visited = new Set()

      let root = this.tree[this.category].root
      let stack = [root]

      if (!active.has(root)) {
        this.orphan = this.active
        this.possible = new Set(stack)
        return
      }

      while (stack.length) {
        let cur = stack.pop()
        active.delete(cur)
        visited.add(cur)
        let neighbors = planner.links[cur]
        for (let i = 0, len = neighbors.length; i < len; i++) {
          let neighbor = neighbors[i]
          // Neighbor not visited nor queued
          if (!visited.has(neighbor) && stack.indexOf(neighbor) < 0) {
            // Neighbor active and reachable
            if (active.has(neighbor)) {
              stack.push(neighbor)
            } else {
              possible.add(neighbor)
            }
          }
        }
      }

      this.orphan = active
      this.possible = possible
    }

    let updateNodeVisibility = () => {
      let categoryNodes = this.tree[this.category].nodes
      this.visibleActive = _su.intersect(this.active, categoryNodes)
      this.visibleOrphan = _su.intersect(this.orphan, categoryNodes)
      this.visiblePossible = _su.intersect(this.possible, categoryNodes)
    }

    let updateNodeEffects = () => {
      this.effects = {}

      for (let id of this.active) {
        let effects = this.skills[id]['effects']

        for (let i = 0, len = effects.length; i < len; i++) {
          let effect = effects[i]
          if (!this.effects[effect]) {
            this.effects[effect] = 0
          }
          this.effects[effect] += 1
        }
      }
    }

    // TODO: make interface updates a separate step
    let updateInterface = () => {
      document.getElementById('active-nodes').value = `${this.active.size} / 91`
      document.getElementById('category-nodes').value = `${this.visibleActive.size} / ${this.tree[this.category].nodes.size}`

      let effectText = ''
      for (let effect in this.effects) {
        // TODO: load proper values later!
        let effectValue = 1
        effectText += `${this.locale[effect]}: ${this.effects[effect] * effectValue}\n`
      }
      document.getElementById('effects-display').value = effectText
    }

    updateNodeState()
    updateNodeVisibility()
    updateNodeEffects()
    updateInterface()
    this.saveSession()
  }

  // Draw planner; calls state change internally
  draw () {
    this.update()
    this.hexGrid.clear()
    this.linkGrid.clear()
    let categoryNodes = this.tree[this.category].nodes

    // Draw nodes of current category
    for (let id of categoryNodes) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'inactive')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['inactive'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['inactive'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, this.colors['label']['inactive'])
        }  // END DEBUG
      }
    }

    // Draw visible active nodes and links
    for (let id of this.visibleActive) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'active')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['active'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['active'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, undefined, this.colors['cell']['active'])
        }  // END DEBUG
      }
    }

    // Draw visible possible nodes and links
    for (let id of this.visiblePossible) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'possible')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['possible'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['possible'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, undefined, this.colors['cell']['possible'])
        }  // END DEBUG
      }
    }

    // Draw visible orphan nodes and links
    for (let id of this.visibleOrphan) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'orphan')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['orphan'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['orphan'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, undefined, this.colors['cell']['orphan'])
        }  // END DEBUG
      }
    }
  }

  exportImage () {
    let image = this.hexGrid.canvas.toDataURL('image/png')
    let file = image.replace('image/png', 'image/octet-stream')
    window.location.href = file
  }

  saveSession () {
    let session = JSON.stringify([...this.active])
    window.sessionStorage.setItem('active', session)
  }

  loadSesssion () {
    let session = window.sessionStorage.getItem('active')
    if (session) {
      this.active = new Set(JSON.parse(session))
    }
  }

  exportSession () {
    return [...this.active].map((id) => parseInt(id, 10)).join('-')
  }

  importSession (sessionString) {
    this.active = new Set(sessionString.split('-')) || new Set()
    this.draw()
  }
}

const planner = new SkillPlanner()
planner.loadData(() => {
  planner.loadSesssion()
  planner.draw()
})
<commit_msg>Now uses hex encoding for exported session strings<commit_after>/* global _su, strWrap, Point, HexGrid */

class SkillPlanner {
  constructor () {
    this.checkDependencies()

    // Skill tree data
    this.skills = {}
    this.groups = {}
    this.coords = {}
    this.links = {}
    this.tree = {}
    this.locale = {}
    this.colors = {}

    this.reset()

    // DEBUG
    this.DEBUG = false
    // END DEBUG
    this.initGrid()
  }

  checkDependencies () {
    if (typeof HexGrid !== 'function' || !_su) {
      console.log('Warning! Required dependencies not found. Please contact the dev for more info.')
    }
  }

  reset () {
    this.category = 'Firepower'
    this.active = new Set()
    this.orphan = new Set()
    this.possible = new Set()
    this.visibleActive = new Set()
    this.visibleOrphan = new Set()
    this.visiblePossible = new Set()
    this.effects = {}
    this.mouseDown = false
    this.prevHexId = null
  }

  initGrid () {
    let radius = 48
    let padding = 16
    let origin = new Point(-96, 128)

    let hexGrid = new HexGrid(radius, padding, origin)
    hexGrid.bindCanvas(document.getElementById('hex'))
    this.hexGrid = hexGrid

    let linkGrid = new HexGrid(radius, padding, origin)
    linkGrid.bindCanvas(document.getElementById('link'))
    this.linkGrid = linkGrid

    this.initEvents()
    this.initInterface()
  }

  initEvents () {
    let hexGrid = this.hexGrid
    let onMouseDown = (e) => {
      e.preventDefault()
      this.mouseDown = true
    }

    let onMouseMove = (e) => {
      e.preventDefault()
      if (this.mouseDown) {
        let point = hexGrid.getCursorCanvasPoint(e)
        let hex = hexGrid.getHexAtPoint(point)

        if (hex) {
          let hexId = this.skillHexToId(hex)

          if (hexId !== this.prevHexId) {
            this.toggleNode(hexId)
            this.prevHexId = hexId
          }
        }
      }
    }

    let onMouseUp = (e) => {
      e.preventDefault()
      let point = hexGrid.getCursorCanvasPoint(e)
      let hex = hexGrid.getHexAtPoint(point)

      if (hex) {
        let hexId = this.skillHexToId(hex)

        if (e.ctrlKey) {
          let activate = !this.active.has(hexId)
          let nodeGroup = this.groups[this.skills[hexId].group]
          for (let node of nodeGroup) {
            if (activate) {
              this.activateNode(node)
            } else {
              this.deactivateNode(node)
            }
          }
        } else {
          if (hexId !== this.prevHexId) {
            this.toggleNode(hexId)
            this.prevHexId = hexId
          }
        }
      }
      this.prevHexId = null
      this.mouseDown = false
    }

    this.bind('mousedown', onMouseDown, this)
    this.bind('mousemove', onMouseMove, this)
    this.bind('mouseup', onMouseUp, this)
  }

  initInterface () {
    let bindEvent = (element, event, fn) => {
      document.getElementById(element).addEventListener('click', fn, false)
    }

    bindEvent('toggle-firepower', 'click', () => {
      this.category = 'Firepower'
      this.draw()
    })

    bindEvent('toggle-survival', 'click', () => {
      this.category = 'Survival'
      this.draw()
    })

    bindEvent('toggle-mobility', 'click', () => {
      this.category = 'Mobility'
      this.draw()
    })

    bindEvent('toggle-jumpjets', 'click', () => {
      this.category = 'Jump Jets'
      this.draw()
    })

    bindEvent('toggle-operations', 'click', () => {
      this.category = 'Operations'
      this.draw()
    })

    bindEvent('toggle-sensors', 'click', () => {
      this.category = 'Sensors'
      this.draw()
    })

    bindEvent('toggle-auxiliary', 'click', () => {
      this.category = 'Auxiliary'
      this.draw()
    })

    bindEvent('add-section', 'click', () => {
      this.active = _su.union(this.active, this.tree[this.category].nodes)
      this.draw()
    })

    bindEvent('clear-section', 'click', () => {
      this.active = _su.difference(this.active, this.tree[this.category].nodes)
      this.draw()
    })

    bindEvent('clear-all', 'click', () => {
      this.active = new Set()
      this.draw()
    })

    bindEvent('export', 'click', () => {
      window.prompt('Please copy this Session String:', this.exportSession())
    })

    bindEvent('import', 'click', () => {
      let sessionString = window.prompt('Please enter the Session String to import:')
      this.importSession(sessionString)
    })
  }

  loadData (cb) {
    let loadColors = this.getJSON('./data/colors.json')
      .then((colors) => {
        this.colors = colors
      })
      .catch((err) => {
        console.error(err)
      })

    let loadSkills = this.getJSON('./data/latest.json')
      .then((tree) => {
        for (let category in tree) {
          let subtree = tree[category]
          // Load all skill nodes
          Object.assign(this.skills, subtree.nodes)

          this.tree[category] = {
            'root': subtree['root'],
            'nodes': new Set()
          }

          // Hex coord to node lookup
          this.coords[category] = {}
          for (let id in subtree.nodes) {
            let skill = this.skills[id]
            let coord = `(${skill.col}, ${skill.row})`
            this.coords[category][coord] = id

            // Add node to subtree
            this.tree[category]['nodes'].add(id)
          }
        }

        for (let id in this.skills) {
          let skill = this.skills[id]

          // Add node to group
          let group = skill['group']
          if (this.groups[group]) {
            this.groups[group].add(id)
          } else {
            this.groups[group] = new Set([id])
          }

          // Process node links
          this.links[id] = []
          let links = skill.links
          for (let i = 0, len = links.length; i < len; i++) {
            if (links[i]) {
              this.links[id].push(links[i])
            }
          }
        }
      })
      .catch((err) => {
        console.error(err)
      })

    let loadLocale = this.getJSON('./data/locale.json')
      .then((locale) => {
        this.locale = locale
      })
      .catch((err) => {
        console.error(err)
      })

    Promise.all([loadColors, loadSkills, loadLocale])
      .then(() => {
        typeof cb === 'function' && cb()
      })
  }

  bind (action, fn, ctx) {
    document.addEventListener(action, fn.bind(ctx || this), false)
  }

  getJSON (url) {
    return new Promise((resolve, reject) => {
      let req = new window.XMLHttpRequest()
      req.open('GET', url, true)

      req.onload = function () {
        if (this.status >= 200 && this.status < 400) {
          resolve(JSON.parse(this.response))
        } else {
          reject(new Error('Failed to retrieve URL!'))
        }
      }

      req.send()
    })
  }

  skillIdToHex (id) {
    let col = this.skills[id].col
    let row = this.skills[id].row
    return `(${col}, ${row})`
  }

  skillHexToId (hex) {
    let col = hex.col
    let row = hex.row
    return this.coords[this.category][`(${col}, ${row})`]
  }

  toggleNode (nodeId) {
    // Ignore invalid/non-visible nodes
    if (!nodeId || !this.tree[this.category].nodes.has(nodeId)) { return }

    if (this.active.has(nodeId)) {
      this.active.delete(nodeId)
    } else {
      this.active.add(nodeId)
    }
    this.draw()
  }

  activateNode (nodeId) {
    // Ignore invalid/non-visible nodes
    if (!nodeId || !this.tree[this.category].nodes.has(nodeId)) { return }
    this.active.add(nodeId)
    this.draw()
  }

  deactivateNode (nodeId) {
    // Ignore invalid/non-visible nodes
    if (!nodeId || !this.tree[this.category].nodes.has(nodeId)) { return }
    this.active.delete(nodeId)
    this.draw()
  }

  // Draw skill node and label
  drawNode (skill, state) {
    let col = skill.col
    let row = skill.row
    this.hexGrid.drawHexCell(col, row, this.colors['cell'][state])

    let name = skill.name
    let text = (name.length > 12) ? strWrap(name) : name
    this.hexGrid.drawHexLabel(col, row, text, undefined, this.colors['label'][state])
  }

  // Recalculate state using DFS
  update () {
    let updateNodeState = () => {
      let active = new Set(this.active)
      let possible = new Set()
      let visited = new Set()

      let root = this.tree[this.category].root
      let stack = [root]

      if (!active.has(root)) {
        this.orphan = this.active
        this.possible = new Set(stack)
        return
      }

      while (stack.length) {
        let cur = stack.pop()
        active.delete(cur)
        visited.add(cur)
        let neighbors = planner.links[cur]
        for (let i = 0, len = neighbors.length; i < len; i++) {
          let neighbor = neighbors[i]
          // Neighbor not visited nor queued
          if (!visited.has(neighbor) && stack.indexOf(neighbor) < 0) {
            // Neighbor active and reachable
            if (active.has(neighbor)) {
              stack.push(neighbor)
            } else {
              possible.add(neighbor)
            }
          }
        }
      }

      this.orphan = active
      this.possible = possible
    }

    let updateNodeVisibility = () => {
      let categoryNodes = this.tree[this.category].nodes
      this.visibleActive = _su.intersect(this.active, categoryNodes)
      this.visibleOrphan = _su.intersect(this.orphan, categoryNodes)
      this.visiblePossible = _su.intersect(this.possible, categoryNodes)
    }

    let updateNodeEffects = () => {
      this.effects = {}

      for (let id of this.active) {
        let effects = this.skills[id]['effects']

        for (let i = 0, len = effects.length; i < len; i++) {
          let effect = effects[i]
          if (!this.effects[effect]) {
            this.effects[effect] = 0
          }
          this.effects[effect] += 1
        }
      }
    }

    // TODO: make interface updates a separate step
    let updateInterface = () => {
      document.getElementById('active-nodes').value = `${this.active.size} / 91`
      document.getElementById('category-nodes').value = `${this.visibleActive.size} / ${this.tree[this.category].nodes.size}`

      let effectText = ''
      for (let effect in this.effects) {
        // TODO: load proper values later!
        let effectValue = 1
        effectText += `${this.locale[effect]}: ${this.effects[effect] * effectValue}\n`
      }
      document.getElementById('effects-display').value = effectText
    }

    updateNodeState()
    updateNodeVisibility()
    updateNodeEffects()
    updateInterface()
    this.saveSession()
  }

  // Draw planner; calls state change internally
  draw () {
    this.update()
    this.hexGrid.clear()
    this.linkGrid.clear()
    let categoryNodes = this.tree[this.category].nodes

    // Draw nodes of current category
    for (let id of categoryNodes) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'inactive')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['inactive'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['inactive'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, this.colors['label']['inactive'])
        }  // END DEBUG
      }
    }

    // Draw visible active nodes and links
    for (let id of this.visibleActive) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'active')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['active'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['active'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, undefined, this.colors['cell']['active'])
        }  // END DEBUG
      }
    }

    // Draw visible possible nodes and links
    for (let id of this.visiblePossible) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'possible')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['possible'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['possible'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, undefined, this.colors['cell']['possible'])
        }  // END DEBUG
      }
    }

    // Draw visible orphan nodes and links
    for (let id of this.visibleOrphan) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'orphan')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['orphan'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['orphan'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, undefined, this.colors['cell']['orphan'])
        }  // END DEBUG
      }
    }
  }

  exportImage () {
    let image = this.hexGrid.canvas.toDataURL('image/png')
    let file = image.replace('image/png', 'image/octet-stream')
    window.location.href = file
  }

  saveSession () {
    let session = JSON.stringify([...this.active])
    window.sessionStorage.setItem('active', session)
  }

  loadSesssion () {
    let session = window.sessionStorage.getItem('active')
    if (session) {
      this.active = new Set(JSON.parse(session))
    }
  }

  exportSession () {
    return [...this.active].map((id) => parseInt(id, 10).toString(16)).join('-')
  }

  importSession (sessionString) {
    this.active = new Set(sessionString.split('-').map((id) => parseInt(id, 16).toString())) || new Set()
    this.draw()
  }
}

const planner = new SkillPlanner()
planner.loadData(() => {
  planner.loadSesssion()
  planner.draw()
})
<|endoftext|>


<commit_before>import PropTypes from "prop-types";
import React, { Component } from "react";
import { connect } from "react-redux";

import { Link } from "react-router-dom";
import { Icon, Row, Spin, Table, Tabs } from "antd";
const TabPane = Tabs.TabPane;

import { emitAPIRequest } from "../../actions/api.actions";

import PlayButton from "../PlayButton";

class SeasonTabs extends Component {
    static propTypes = {
        activeSeasonNum: PropTypes.number.isRequired,
        show: PropTypes.object.isRequired
    };

    constructor(props) {
        super(props);

        this.state = {
            isLoadingSeasons: false,
            seasons: []
        };
    }

    componentWillMount() {
        this._getSeasons();
    }

    _getSeasons() {
        const { emitAPIRequest, show } = this.props;

        this.setState({
            isLoadingSeasons: true
        });

        const options = {
            show_id: show.id
        };

        emitAPIRequest(
            "shows.seasons.get",
            options,
            this._seasonsReceived.bind(this),
            false
        );
    }

    _seasonsReceived(seasons) {
        this.setState({
            isLoadingSeasons: false,
            seasons
        });
    }

    _handleClickToggleLock(seasonID, newLockStatus) {
        const { emitAPIRequest } = this.props;

        const params = {
            season_id: seasonID,
            lock_status: newLockStatus
        };

        emitAPIRequest(
            "shows.season.toggle_lock",
            params,
            this._getSeasons.bind(this),
            false
        );
    }

    _buildSeasonBar() {
        const { seasons } = this.state;
        const { activeSeasonNum, show } = this.props;

        let seasonList = [];
        seasons.forEach(season => {
            let boxClass = "ib-show-seasonlist-season-box";
            let seasonNumEl = "";
            if (season.season_number === activeSeasonNum) {
                boxClass += " ib-show-seasonlist-season-box-active";
                seasonNumEl = season.season_number;
            } else {
                seasonNumEl = (
                    <Link
                        to={"/show/" + show.slug + "/" + season.season_number}
                    >
                        {season.season_number}
                    </Link>
                );
            }

            let lockedIcon = "";
            if (season.locked === 1) {
                lockedIcon = (

                        <Icon type="lock" />

                );
            } else {
                lockedIcon = (

                        <Icon type="unlock" />

                );
            }

            const el = (

                    {seasonNumEl} | {lockedIcon}

            );

            seasonList.push(el);
        });

        return seasonList;
    }

    _handleClickTab(seasonNumber) {
        const { history, show } = this.props;
        history.push("/show/" + show.slug + "/" + seasonNumber);
    }

    _buildSeasonTabs() {
        const { seasons } = this.state;
        const tabpanes = seasons.map((season, index) => {
            let lockIcon = "";
            if (season.locked === 1) {
                lockIcon = <Icon type="lock" />;
            } else {
                lockIcon = <Icon type="unlock" />;
            }

            const tabTitle = (

                    {season.season_number}
                    {lockIcon}

            );
            return <TabPane tab={tabTitle} key={season.season_number} />;
        });

        return (
            <Tabs
                defaultActiveKey={seasons[0]["season_number"]}
                onTabClick={this._handleClickTab.bind(this)}
            >
                {tabpanes}
            </Tabs>
        );
    }

    render() {
        const { isLoadingSeasons } = this.state;

        let seasonBar = "";
        if (isLoadingSeasons) {
            seasonBar = <Spin />;
        } else {
            seasonBar = this._buildSeasonTabs();
        }

        return
{seasonBar}
;
    }
}

const mapStateToProps = state => {
    const { settings } = state;
    return {
        directories: settings.settings.directories
    };
};

const mapDispatchToProps = dispatch => {
    return {
        emitAPIRequest: (endpoint, params, callback, shouldDispatch) =>
            dispatch(emitAPIRequest(endpoint, params, callback, shouldDispatch))
    };
};

export default connect(mapStateToProps, mapDispatchToProps)(SeasonTabs);
<commit_msg>Added default active key; made tabs small.<commit_after>import PropTypes from "prop-types";
import React, { Component } from "react";
import { connect } from "react-redux";

import { Link } from "react-router-dom";
import { Icon, Row, Spin, Table, Tabs } from "antd";
const TabPane = Tabs.TabPane;

import { emitAPIRequest } from "../../actions/api.actions";

import PlayButton from "../PlayButton";

class SeasonTabs extends Component {
    static propTypes = {
        activeSeasonNum: PropTypes.number.isRequired,
        show: PropTypes.object.isRequired
    };

    constructor(props) {
        super(props);

        this.state = {
            isLoadingSeasons: false,
            seasons: []
        };
    }

    componentWillMount() {
        this._getSeasons();
    }

    _getSeasons() {
        const { emitAPIRequest, show } = this.props;

        this.setState({
            isLoadingSeasons: true
        });

        const options = {
            show_id: show.id
        };

        emitAPIRequest(
            "shows.seasons.get",
            options,
            this._seasonsReceived.bind(this),
            false
        );
    }

    _seasonsReceived(seasons) {
        this.setState({
            isLoadingSeasons: false,
            seasons
        });
    }

    _handleClickToggleLock(seasonID, newLockStatus) {
        const { emitAPIRequest } = this.props;

        const params = {
            season_id: seasonID,
            lock_status: newLockStatus
        };

        emitAPIRequest(
            "shows.season.toggle_lock",
            params,
            this._getSeasons.bind(this),
            false
        );
    }

    _buildSeasonBar() {
        const { seasons } = this.state;
        const { activeSeasonNum, show } = this.props;

        let seasonList = [];
        seasons.forEach(season => {
            let boxClass = "ib-show-seasonlist-season-box";
            let seasonNumEl = "";
            if (season.season_number === activeSeasonNum) {
                boxClass += " ib-show-seasonlist-season-box-active";
                seasonNumEl = season.season_number;
            } else {
                seasonNumEl = (
                    <Link
                        to={"/show/" + show.slug + "/" + season.season_number}
                    >
                        {season.season_number}
                    </Link>
                );
            }

            let lockedIcon = "";
            if (season.locked === 1) {
                lockedIcon = (

                        <Icon type="lock" />

                );
            } else {
                lockedIcon = (

                        <Icon type="unlock" />

                );
            }

            const el = (

                    {seasonNumEl} | {lockedIcon}

            );

            seasonList.push(el);
        });

        return seasonList;
    }

    _handleClickTab(seasonNumber) {
        const { history, show } = this.props;
        history.push("/show/" + show.slug + "/" + seasonNumber);
    }

    _buildSeasonTabs() {
        const { seasons } = this.state;
        const tabpanes = seasons.map((season, index) => {
            let lockIcon = "";
            if (season.locked === 1) {
                lockIcon = <Icon type="lock" />;
            } else {
                lockIcon = <Icon type="unlock" />;
            }

            const tabTitle = (

                    {season.season_number}
                    {lockIcon}

            );
            return <TabPane tab={tabTitle} key={season.season_number} />;
        });
        return (
            <Tabs
                defaultActiveKey={this.props.activeSeasonNum}
                onTabClick={this._handleClickTab.bind(this)}
                size="small"
            >
                {tabpanes}
            </Tabs>
        );
    }

    render() {
        const { isLoadingSeasons } = this.state;

        let seasonBar = "";
        if (isLoadingSeasons) {
            seasonBar = <Spin />;
        } else {
            seasonBar = this._buildSeasonTabs();
        }

        return
{seasonBar}
;
    }
}

const mapStateToProps = state => {
    const { settings } = state;
    return {
        directories: settings.settings.directories
    };
};

const mapDispatchToProps = dispatch => {
    return {
        emitAPIRequest: (endpoint, params, callback, shouldDispatch) =>
            dispatch(emitAPIRequest(endpoint, params, callback, shouldDispatch))
    };
};

export default connect(mapStateToProps, mapDispatchToProps)(SeasonTabs);
<|endoftext|>


<commit_before>// Filter out tasks that don't need to be built. Determines which tasks need to be built based on the type of build.
// If its a CI build, all tasks whose package numbers have been bumped will be built.
// If its a PR build, all tasks that have been changed will be built.
// Any other type of build will build all tasks.
var fs = require('fs');
var path = require('path');
var semver = require('semver');
var restClient = require('typed-rest-client/RestClient');
var httpHandler = require('typed-rest-client/Handlers');
var run = require('./ci-util').run;

var makeOptionsPath = path.join(__dirname, '..', 'make-options.json');
var makeOptions = JSON.parse(fs.readFileSync(makeOptionsPath).toString());

var getTasksToBuildForCI = async function() {
    // Returns a list of tasks that have different version numbers than their current published version.
    var packageInfo;
    try {
        var packageToken = process.env['PACKAGE_TOKEN'];
        if (!packageToken) {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=21;]Failed to get info from package endpoint because no token was provided. Try setting the PACKAGE_TOKEN environment variable.`);
            return makeOptions.tasks;
        }
        var handler = new httpHandler.PersonalAccessTokenCredentialHandler(packageToken);
        var client = new restClient.RestClient('azure-pipelines-tasks-ci', '', [handler]);
        var packageEndpoint = process.env['PACKAGE_ENDPOINT'];
        if (!packageEndpoint) {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=28;]Failed to get info from package endpoint because no endpoint was specified. Try setting the PACKAGE_ENDPOINT environment variable.`);
            return makeOptions.tasks;
        }
        packageInfo = await client.get(packageEndpoint);
        if (packageInfo.statusCode !== 200) {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=33;]Failed to get info from package endpoint, returned with status code ${packageInfo.statusCode}`);
            return makeOptions.tasks;
        }
    }
    catch (err) {
        // If unable to reach rest client, build everything.
        console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=39;]Failed to get info from package endpoint, client failed with error ${err.message}`);
        return makeOptions.tasks;
    }
    var packageMap = {};
    if(!packageInfo.result || !packageInfo.result.value) {
        console.log('##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=44;]Failed to get info from package endpoint, returned no packages');
        return makeOptions.tasks;
    }
    packageInfo.result.value.forEach(package => {
        if (package.name && package.versions) {
            var packageName = package.name.slice('Mseng.MS.TF.DistributedTask.Tasks.'.length).toLowerCase();
            packageMap[packageName] = package.versions[0].version;
            package.versions.some(versionInfo => {
                if (versionInfo.isLatest) {
                    packageMap[packageName] = versionInfo.version;
                    return true;
                }
                return false;
            });
        }
    });

    return makeOptions.tasks.filter(function (taskName) {
        var taskJsonPath = path.join(__dirname, '..', 'Tasks' , taskName, 'task.json');
        if (fs.existsSync(taskJsonPath)){
            var taskJson = JSON.parse(fs.readFileSync(taskJsonPath).toString());
            var lowerCaseName = taskJson.name.toLowerCase();
            if (isNaN(parseInt(lowerCaseName.slice(-1), 10))) {
                lowerCaseName += "v" + taskJson.version.Major;
            }
            if (lowerCaseName in packageMap || taskName.toLowerCase() in packageMap) {
                if (taskName.toLowerCase() in packageMap) {
                    lowerCaseName = taskName.toLowerCase();
                }
                var packageVersion = packageMap[lowerCaseName];
                var localVersion = `${taskJson.version.Major}.${taskJson.version.Minor}.${taskJson.version.Patch}`;

                // Build if local version and package version are different.
                return semver.neq(localVersion, packageVersion);
            }
            else {
                console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=74;]${taskName} has not been published before`);
                return true;
            }
        }
        else {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=79;]${taskJsonPath} does not exist`);
            return true;
        }
    });
}

var getTasksDependentOnChangedCommonFiles = function(commonFilePaths) {
    // Takes in an array of filepaths that have been changed in the Tasks/Common folder, returns any tasks that could be affected.
    if (commonFilePaths.length == 0) {
        return [];
    }
    changedTasks = [];
    makeOptions.tasks.forEach(taskName => {
        var makeJsonPath = path.join(__dirname, '..', 'Tasks' , taskName, 'make.json')
        if (fs.existsSync(makeJsonPath)){
            var makeJson = JSON.parse(fs.readFileSync(makeJsonPath).toString());
            if (makeJson.common) {
                makeJson.common.forEach(commonModule => {
                    if (commonFilePaths.includes(commonModule.module.toLowerCase()) && !changedTasks.includes(taskName)) {
                        changedTasks.push(taskName);
                    }
                });
            }
        }
    });
    return changedTasks;
}

var getTasksToBuildForPR = function() {
    // Takes in a git source branch, diffs it with master, and returns a list of tasks that could have been affected by the changes.
    var sourceBranch = process.env['SYSTEM_PULLREQUEST_SOURCEBRANCH'];
    var prId = process.env['SYSTEM_PULLREQUEST_PULLREQUESTNUMBER'];
    var targetBranch = process.env['SYSTEM_PULLREQUEST_TARGETBRANCH'];
    var commonChanges = [];
    var toBeBuilt = [];
    try {
        if (sourceBranch.includes(':')) {
            // We only care about the branch name, not the source repo
            sourceBranch = sourceBranch.split(':')[1];
        }
        run('git fetch origin pull/' + prId + '/head:' + sourceBranch);
    }
    catch (err) {
        // If unable to reach github, build everything.
        console.log('##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=125;]Unable to reach github, building all tasks', err);
        return makeOptions.tasks;
    }
    var baseCommit = run('git merge-base ' + sourceBranch + ' origin/' + targetBranch);
    run('git --no-pager diff --name-only ' + baseCommit + ' ' + sourceBranch)
        .split('\n')
        .forEach(filePath => {
        if (filePath.slice(0, 5) == 'Tasks') {
            var taskPath = filePath.slice(6);
            if(taskPath.slice(0, 6) == 'Common') {
                var commonName = taskPath.slice(7);
                commonChanges.push('../common/' + commonName.slice(0, commonName.indexOf('/')).toLowerCase());
            }
            else {
                var taskName = taskPath.slice(0, taskPath.indexOf('/'));
                if (!toBeBuilt.includes(taskName)) {
                    toBeBuilt.push(taskName);
                }
            }
        }
    });
    var changedTasks = getTasksDependentOnChangedCommonFiles(commonChanges);
    var shouldBeBumped = [];
    changedTasks.forEach(task => {
        if (!toBeBuilt.includes(task)) {
            shouldBeBumped.push(task);
            toBeBuilt.push(task);
        }
    });
    if (shouldBeBumped.length > 0) {
        // TODO - change this to an error once its proven to be working.
        console.log('##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=160;]The following tasks should have their versions bumped due to changes in common: ' + shouldBeBumped, err);
        // throw new Error('The following tasks should have their versions bumped due to changes in common: ' + shouldBeBumped);
    }

    return toBeBuilt;
}

var setTaskVariables = function(tasks) {
    console.log('tasks: ' + JSON.stringify(tasks));
    console.log('##vso[task.setVariable variable=task_pattern]@(' + tasks.join('|') + ')');
    console.log('##vso[task.setVariable variable=numTasks]' + tasks.length);
}

var buildReason = process.env['BUILD_REASON'].toLowerCase();
var tasks;
if (buildReason == 'individualci' || buildReason == 'batchedci') {
    // If CI, we will compare any tasks that have updated versions.
    getTasksToBuildForCI().then(tasks => {
        setTaskVariables(tasks)
    });
}
else {
    if (buildReason == 'pullrequest') {
        // If PR, we will compare any tasks that could have been affected based on the diff.
        tasks = getTasksToBuildForPR();
        setTaskVariables(tasks);
    }
    else {
        // If manual or other, build everything.
        setTaskVariables(makeOptions.tasks);
    }
}
<commit_msg>typo/formatting<commit_after>// Filter out tasks that don't need to be built. Determines which tasks need to be built based on the type of build.
// If its a CI build, all tasks whose package numbers have been bumped will be built.
// If its a PR build, all tasks that have been changed will be built.
// Any other type of build will build all tasks.
var fs = require('fs');
var path = require('path');
var semver = require('semver');
var restClient = require('typed-rest-client/RestClient');
var httpHandler = require('typed-rest-client/Handlers');
var run = require('./ci-util').run;

var makeOptionsPath = path.join(__dirname, '..', 'make-options.json');
var makeOptions = JSON.parse(fs.readFileSync(makeOptionsPath).toString());

var getTasksToBuildForCI = async function() {
    // Returns a list of tasks that have different version numbers than their current published version.
    var packageInfo;
    try {
        var packageToken = process.env['PACKAGE_TOKEN'];
        if (!packageToken) {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=21;]Failed to get info from package endpoint because no token was provided. Try setting the PACKAGE_TOKEN environment variable.`);
            return makeOptions.tasks;
        }
        var handler = new httpHandler.PersonalAccessTokenCredentialHandler(packageToken);
        var client = new restClient.RestClient('azure-pipelines-tasks-ci', '', [handler]);
        var packageEndpoint = process.env['PACKAGE_ENDPOINT'];
        if (!packageEndpoint) {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=28;]Failed to get info from package endpoint because no endpoint was specified. Try setting the PACKAGE_ENDPOINT environment variable.`);
            return makeOptions.tasks;
        }
        packageInfo = await client.get(packageEndpoint);
        if (packageInfo.statusCode !== 200) {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=33;]Failed to get info from package endpoint, returned with status code ${packageInfo.statusCode}`);
            return makeOptions.tasks;
        }
    }
    catch (err) {
        // If unable to reach rest client, build everything.
        console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=39;]Failed to get info from package endpoint, client failed with error ${err.message}`);
        return makeOptions.tasks;
    }
    var packageMap = {};
    if(!packageInfo.result || !packageInfo.result.value) {
        console.log('##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=44;]Failed to get info from package endpoint, returned no packages');
        return makeOptions.tasks;
    }
    packageInfo.result.value.forEach(package => {
        if (package.name && package.versions) {
            var packageName = package.name.slice('Mseng.MS.TF.DistributedTask.Tasks.'.length).toLowerCase();
            packageMap[packageName] = package.versions[0].version;
            package.versions.some(versionInfo => {
                if (versionInfo.isLatest) {
                    packageMap[packageName] = versionInfo.version;
                    return true;
                }
                return false;
            });
        }
    });

    return makeOptions.tasks.filter(function (taskName) {
        var taskJsonPath = path.join(__dirname, '..', 'Tasks' , taskName, 'task.json');
        if (fs.existsSync(taskJsonPath)){
            var taskJson = JSON.parse(fs.readFileSync(taskJsonPath).toString());
            var lowerCaseName = taskJson.name.toLowerCase();
            if (isNaN(parseInt(lowerCaseName.slice(-1), 10))) {
                lowerCaseName += "v" + taskJson.version.Major;
            }
            if (lowerCaseName in packageMap || taskName.toLowerCase() in packageMap) {
                if (taskName.toLowerCase() in packageMap) {
                    lowerCaseName = taskName.toLowerCase();
                }
                var packageVersion = packageMap[lowerCaseName];
                var localVersion = `${taskJson.version.Major}.${taskJson.version.Minor}.${taskJson.version.Patch}`;

                // Build if local version and package version are different.
                return semver.neq(localVersion, packageVersion);
            }
            else {
                console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=74;]${taskName} has not been published before`);
                return true;
            }
        }
        else {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=79;]${taskJsonPath} does not exist`);
            return true;
        }
    });
}

var getTasksDependentOnChangedCommonFiles = function(commonFilePaths) {
    // Takes in an array of filepaths that have been changed in the Tasks/Common folder, returns any tasks that could be affected.
    if (commonFilePaths.length == 0) {
        return [];
    }
    changedTasks = [];
    makeOptions.tasks.forEach(taskName => {
        var makeJsonPath = path.join(__dirname, '..', 'Tasks' , taskName, 'make.json')
        if (fs.existsSync(makeJsonPath)){
            var makeJson = JSON.parse(fs.readFileSync(makeJsonPath).toString());
            if (makeJson.common) {
                makeJson.common.forEach(commonModule => {
                    if (commonFilePaths.includes(commonModule.module.toLowerCase()) && !changedTasks.includes(taskName)) {
                        changedTasks.push(taskName);
                    }
                });
            }
        }
    });
    return changedTasks;
}

var getTasksToBuildForPR = function() {
    // Takes in a git source branch, diffs it with master, and returns a list of tasks that could have been affected by the changes.
    var sourceBranch = process.env['SYSTEM_PULLREQUEST_SOURCEBRANCH'];
    var prId = process.env['SYSTEM_PULLREQUEST_PULLREQUESTNUMBER'];
    var targetBranch = process.env['SYSTEM_PULLREQUEST_TARGETBRANCH'];
    var commonChanges = [];
    var toBeBuilt = [];
    try {
        if (sourceBranch.includes(':')) {
            // We only care about the branch name, not the source repo
            sourceBranch = sourceBranch.split(':')[1];
        }
        run('git fetch origin pull/' + prId + '/head:' + sourceBranch);
    }
    catch (err) {
        // If unable to reach github, build everything.
        console.log('##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=125;]Unable to reach github, building all tasks', err);
        return makeOptions.tasks;
    }
    var baseCommit = run('git merge-base ' + sourceBranch + ' origin/' + targetBranch);
    run('git --no-pager diff --name-only ' + baseCommit + ' ' + sourceBranch)
        .split('\n')
        .forEach(filePath => {
        if (filePath.slice(0, 5) == 'Tasks') {
            var taskPath = filePath.slice(6);
            if(taskPath.slice(0, 6) == 'Common') {
                var commonName = taskPath.slice(7);
                commonChanges.push('../common/' + commonName.slice(0, commonName.indexOf('/')).toLowerCase());
            }
            else {
                var taskName = taskPath.slice(0, taskPath.indexOf('/'));
                if (!toBeBuilt.includes(taskName)) {
                    toBeBuilt.push(taskName);
                }
            }
        }
    });
    var changedTasks = getTasksDependentOnChangedCommonFiles(commonChanges);
    var shouldBeBumped = [];
    changedTasks.forEach(task => {
        if (!toBeBuilt.includes(task)) {
            shouldBeBumped.push(task);
            toBeBuilt.push(task);
        }
    });
    if (shouldBeBumped.length > 0) {
        // TODO - change this to an error once its proven to be working.
        console.log('##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=160;]The following tasks should have their versions bumped due to changes in common: ', shouldBeBumped);
        // throw new Error('The following tasks should have their versions bumped due to changes in common: ' + shouldBeBumped);
    }

    return toBeBuilt;
}

var setTaskVariables = function(tasks) {
    console.log('tasks: ' + JSON.stringify(tasks));
    console.log('##vso[task.setVariable variable=task_pattern]@(' + tasks.join('|') + ')');
    console.log('##vso[task.setVariable variable=numTasks]' + tasks.length);
}

var buildReason = process.env['BUILD_REASON'].toLowerCase();
var tasks;
if (buildReason == 'individualci' || buildReason == 'batchedci') {
    // If CI, we will compare any tasks that have updated versions.
    getTasksToBuildForCI().then(tasks => {
        setTaskVariables(tasks)
    });
}
else {
    if (buildReason == 'pullrequest') {
        // If PR, we will compare any tasks that could have been affected based on the diff.
        tasks = getTasksToBuildForPR();
        setTaskVariables(tasks);
    }
    else {
        // If manual or other, build everything.
        setTaskVariables(makeOptions.tasks);
    }
}
<|endoftext|>


<commit_before>app.factory('QueryService', ['$resource', '$injector', '$q', '$filter', 'PollingService', 'PlotService', 'DownloadService', 'TableService', 'BrowserService', function($resource, $injector, $q, $filter, PollingService, PlotService, DownloadService, TableService, BrowserService) {

    /* get the base url */

    var baseurl = angular.element('meta[name="baseurl"]').attr('content');

    /* configure resources */

    var resources = {
        status: $resource(baseurl + 'query/api/status/'),
        forms: $resource(baseurl + 'query/api/forms/'),
        dropdowns: $resource(baseurl + 'query/api/dropdowns/'),
        jobs: $resource(baseurl + 'query/api/jobs/:id/:detail_route/'),
        examples: $resource(baseurl + 'query/api/examples/user/'),
        queues: $resource(baseurl + 'query/api/queues/'),
        querylanguages: $resource(baseurl + 'query/api/querylanguages/'),
        schemas: $resource(baseurl + 'metadata/api/schemas/user/'),
        functions: $resource(baseurl + 'metadata/api/functions/user/'),
    };

    /* initialise the browser service */

    BrowserService.init('schemas', ['schemas', 'tables', 'columns'])
    BrowserService.init('columns', ['columns'], true)
    BrowserService.init('functions', ['functions'])
    BrowserService.init('examples', ['examples'])

    /* create the query service */

    var service = {
        show: {null: true},
        forms: {},
        dropdowns: {},
        values: {},
        errors: {}
    };

    service.init = function() {
        // fetch queues,  query languages and then load forms
        service.queues = resources.queues.query();
        service.query_languages = resources.querylanguages.query();

        $q.all([service.queues.$promise, service.query_languages.$promise]).then(function(){
            service.active_queue = service.queues[0].id;

            resources.forms.query(function(response) {
                angular.forEach(response, function(form) {
                    service.forms[form.key] = $injector.get(form.form_service);
                });

                // activate first form
                service.forms[response[0].key].activate();
            });
        });

        // load dropdowns
        resources.dropdowns.query(function(response) {
            angular.forEach(response, function(dropdown) {
                service.dropdowns[dropdown.key] = $injector.get(dropdown.dropdown_service);
                service.dropdowns[dropdown.key].options = dropdown.options;
            });
        });

        // fetch status
        service.fetchStatus();

        // fetch functions
        resources.functions.query(function(response) {
            BrowserService.render('functions', response);
        });

        // fetch examples
        resources.examples.query(function(response) {
            BrowserService.render('examples', response);
        });

        // fetch schemas
        resources.schemas.query(function(response) {
            service.schemas = response;

            service.columns = []
            angular.forEach(service.schemas, function(schema) {
                angular.forEach(schema.tables, function(table) {
                    angular.forEach(table.columns, function(column) {
                        var column_copy = angular.copy(column);
                        column_copy.name = schema.name + '.' + table.name + '.' + column.name;
                        service.columns.push(column_copy);
                    });
                });
            });

            // load user schema when schemas have been fetched
            service.fetchUserSchema();
        });

        // fetch joblist
        service.fetchJobs();

        // activate overview tab
        service.tab = 'overview';

        // start the polling service
        service.polling = PollingService
        service.polling.init();
        service.polling.register('status', service.fetchStatus, {}, true, false);
        service.polling.register('jobs', service.fetchJobs, {}, true, false);
        service.polling.register('schema', service.fetchUserSchema, {}, true, false);

        // load the other services
        service.table = TableService;
        service.plot = PlotService;
        service.downloads = DownloadService;
    };

    service.fetchStatus = function() {
        return resources.status.query(function(response) {
            service.status = response[0];
        }).$promise;
    };

    service.fetchUserSchema = function() {
        return resources.jobs.query({
            'detail_route': 'tables'
        }, function(response) {
            var user_schema = response[0];

            var user_columns = [];
            angular.forEach(user_schema.tables, function(table) {
                angular.forEach(table.columns, function(column) {
                    var column_copy = angular.copy(column);
                    column_copy.name = user_schema.name + '.' + table.name + '.' + column.name;
                    user_columns.push(column_copy);
                });
            });

            BrowserService.render('schemas', service.schemas.concat(user_schema));
            BrowserService.render('columns', service.columns.concat(user_columns));
        }).$promise;
    }

    service.fetchJobs = function() {
        return resources.jobs.query(function(response) {
            service.jobs = response;

            service.run_ids = service.jobs.map(function(job) {
                return job.run_id;
            }).filter(function(run_id, index, run_ids) {
                return run_id && run_ids.indexOf(run_id) == index;
            }).sort();
            service.run_ids.push(null);

            if (service.job) {
                // get the phase of the current job in the jobs list
                var phase = $filter('filter')(service.jobs, {'id': service.job.id})[0].phase;

                // if the phase has changed, fetch it again
                if (phase != service.job.phase) {
                    service.activateJob(service.job);
                }
            }

        }).$promise;
    };

    service.fetchJob = function(job) {
        return resources.jobs.get({id: job.id}, function(response) {
            service.job = response;

            // get the current job in the jobs list
            var jobs_job = $filter('filter')(service.jobs, {'id': service.job.id})[0]

            if (angular.isUndefined(jobs_job) || jobs_job.phase != service.job.phase) {
                // if the phase has changed, fetch the job list again
                service.fetchJobs();
            }
        }).$promise;
    };

    service.activateForm = function(key) {
        service.form = key;
        service.job = null;
    };

    service.activateJob = function(job) {
        service.form = null;
        service.fetchJob(job).then(function() {
            service.table.ready = false;
            service.plot.ready = false;

            if (service.job.phase == 'COMPLETED') {
                // re-init current tab
                service.initTab(service.tab);
            } else {
                // activate overview tab
                service.activateTab('overview');
            }

            CodeMirror.runMode(service.job.query, "text/x-sql", angular.element('#query')[0]);

            if (service.job.native_query) {
                CodeMirror.runMode(service.job.native_query, "text/x-sql", angular.element('#native-query')[0]);
            }
            if (service.job.actual_query) {
                CodeMirror.runMode(service.job.actual_query, "text/x-sql", angular.element('#actual-query')[0]);
            }
        });
    };

    service.submitJob = function(values) {
        values = angular.extend({}, values, {
            'queue': service.active_queue
        });

        return resources.jobs.save(values).$promise.then(function(job) {
            service.fetchStatus();
            service.activateJob(job);
        });
    };

    service.showModal = function(modal, job) {
        service.errors = {};

        if (angular.isDefined(job)) {
            service.values = job;
        }

        $('#' + modal + '-modal').modal('show');
    };

    service.updateJob = function() {
        resources.jobs.update({id: service.values.id}, service.values).$promise.then(function() {
            service.fetchJobs();
            $('.modal').modal('hide');
        }, function(response) {
            service.errors = response.data;
        });
    };

    service.abortJob = function() {
        resources.jobs.update({id: service.values.id, detail_route: 'abort'}, {}, function() {
            service.fetchStatus();
            service.fetchJobs();
            $('.modal').modal('hide');
        });
    };

    service.removeJob = function() {
        var index = service.jobs.indexOf($filter('filter')(service.jobs, {'id': service.job.id})[0]);

        var next_job;
        if (index == 0) {
            next_job = service.jobs[1];
        } else {
            next_job = service.jobs[index - 1];
        }

        resources.jobs.delete({id: service.values.id}, function() {
            service.fetchStatus();
            service.fetchJobs();
            service.activateJob(next_job);
            $('.modal').modal('hide');
        });
    };

    service.activateTab = function(tab) {
        service.initTab(tab);
        service.tab = tab;
    };

    service.initTab = function(tab) {
        if (tab == 'results') {
            if (!service.table.ready) {
                if (service.job && service.job.phase == 'COMPLETED') {
                    service.table.init({
                        rows_url: 'query/api/jobs/' + service.job.id +'/rows/',
                        columns_url: 'query/api/jobs/' + service.job.id +'/columns/',
                        files_url: 'files/api/files/',
                        references_url: 'serve/api/references/',
                        params: {
                            job: service.job.id
                        }
                    });
                } else {
                    service.table.clear();
                }
            }
        } else if (tab == 'plot') {
            if (!service.plot.ready) {
                if (service.job && service.job.phase == 'COMPLETED') {
                    service.plot.init({
                        rows_url: 'query/api/jobs/' + service.job.id +'/rows/',
                        columns: service.job.columns
                    });
                } else {
                    service.plot.clear();
                }
            }
        } else if (tab == 'download') {
            service.downloads.init({
                job: service.job
            });
        }
    }

    return service;

}]);
<commit_msg>Fix remove job<commit_after>app.factory('QueryService', ['$resource', '$injector', '$q', '$filter', 'PollingService', 'PlotService', 'DownloadService', 'TableService', 'BrowserService', function($resource, $injector, $q, $filter, PollingService, PlotService, DownloadService, TableService, BrowserService) {

    /* get the base url */

    var baseurl = angular.element('meta[name="baseurl"]').attr('content');

    /* configure resources */

    var resources = {
        status: $resource(baseurl + 'query/api/status/'),
        forms: $resource(baseurl + 'query/api/forms/'),
        dropdowns: $resource(baseurl + 'query/api/dropdowns/'),
        jobs: $resource(baseurl + 'query/api/jobs/:id/:detail_route/'),
        examples: $resource(baseurl + 'query/api/examples/user/'),
        queues: $resource(baseurl + 'query/api/queues/'),
        querylanguages: $resource(baseurl + 'query/api/querylanguages/'),
        schemas: $resource(baseurl + 'metadata/api/schemas/user/'),
        functions: $resource(baseurl + 'metadata/api/functions/user/'),
    };

    /* initialise the browser service */

    BrowserService.init('schemas', ['schemas', 'tables', 'columns'])
    BrowserService.init('columns', ['columns'], true)
    BrowserService.init('functions', ['functions'])
    BrowserService.init('examples', ['examples'])

    /* create the query service */

    var service = {
        show: {null: true},
        forms: {},
        dropdowns: {},
        values: {},
        errors: {}
    };

    service.init = function() {
        // fetch queues,  query languages and then load forms
        service.queues = resources.queues.query();
        service.query_languages = resources.querylanguages.query();

        $q.all([service.queues.$promise, service.query_languages.$promise]).then(function(){
            service.active_queue = service.queues[0].id;

            resources.forms.query(function(response) {
                angular.forEach(response, function(form) {
                    service.forms[form.key] = $injector.get(form.form_service);
                });

                service.first_form = service.forms[response[0].key]
                service.activateFirstForm();
            });
        });

        // load dropdowns
        resources.dropdowns.query(function(response) {
            angular.forEach(response, function(dropdown) {
                service.dropdowns[dropdown.key] = $injector.get(dropdown.dropdown_service);
                service.dropdowns[dropdown.key].options = dropdown.options;
            });
        });

        // fetch status
        service.fetchStatus();

        // fetch functions
        resources.functions.query(function(response) {
            BrowserService.render('functions', response);
        });

        // fetch examples
        resources.examples.query(function(response) {
            BrowserService.render('examples', response);
        });

        // fetch schemas
        resources.schemas.query(function(response) {
            service.schemas = response;

            service.columns = []
            angular.forEach(service.schemas, function(schema) {
                angular.forEach(schema.tables, function(table) {
                    angular.forEach(table.columns, function(column) {
                        var column_copy = angular.copy(column);
                        column_copy.name = schema.name + '.' + table.name + '.' + column.name;
                        service.columns.push(column_copy);
                    });
                });
            });

            // load user schema when schemas have been fetched
            service.fetchUserSchema();
        });

        // fetch joblist
        service.fetchJobs();

        // activate overview tab
        service.tab = 'overview';

        // start the polling service
        service.polling = PollingService
        service.polling.init();
        service.polling.register('status', service.fetchStatus, {}, true, false);
        service.polling.register('jobs', service.fetchJobs, {}, true, false);
        service.polling.register('schema', service.fetchUserSchema, {}, true, false);

        // load the other services
        service.table = TableService;
        service.plot = PlotService;
        service.downloads = DownloadService;
    };

    service.fetchStatus = function() {
        return resources.status.query(function(response) {
            service.status = response[0];
        }).$promise;
    };

    service.fetchUserSchema = function() {
        return resources.jobs.query({
            'detail_route': 'tables'
        }, function(response) {
            var user_schema = response[0];

            var user_columns = [];
            angular.forEach(user_schema.tables, function(table) {
                angular.forEach(table.columns, function(column) {
                    var column_copy = angular.copy(column);
                    column_copy.name = user_schema.name + '.' + table.name + '.' + column.name;
                    user_columns.push(column_copy);
                });
            });

            BrowserService.render('schemas', service.schemas.concat(user_schema));
            BrowserService.render('columns', service.columns.concat(user_columns));
        }).$promise;
    }

    service.fetchJobs = function() {
        return resources.jobs.query(function(response) {
            service.jobs = response;

            service.run_ids = service.jobs.map(function(job) {
                return job.run_id;
            }).filter(function(run_id, index, run_ids) {
                return run_id && run_ids.indexOf(run_id) == index;
            }).sort();
            service.run_ids.push(null);

            if (service.job) {
                // get the current job from the jobs list
                var current_job = $filter('filter')(service.jobs, {'id': service.job.id})[0];

                // if the phase has changed, fetch it again
                if (current_job && current_job.phase != service.job.phase) {
                    service.activateJob(service.job);
                }
            }

        }).$promise;
    };

    service.fetchJob = function(job) {
        return resources.jobs.get({id: job.id}, function(response) {
            service.job = response;

            // get the current job in the jobs list
            var jobs_job = $filter('filter')(service.jobs, {'id': service.job.id})[0]

            if (angular.isUndefined(jobs_job) || jobs_job.phase != service.job.phase) {
                // if the phase has changed, fetch the job list again
                service.fetchJobs();
            }
        }).$promise;
    };

    service.activateForm = function(key) {
        service.form = key;
        service.job = null;
    };

    service.activateFirstForm = function() {
        service.first_form.activate();
    };

    service.activateJob = function(job) {
        service.form = null;
        service.fetchJob(job).then(function() {
            service.table.ready = false;
            service.plot.ready = false;

            if (service.job.phase == 'COMPLETED') {
                // re-init current tab
                service.initTab(service.tab);
            } else {
                // activate overview tab
                service.activateTab('overview');
            }

            CodeMirror.runMode(service.job.query, "text/x-sql", angular.element('#query')[0]);

            if (service.job.native_query) {
                CodeMirror.runMode(service.job.native_query, "text/x-sql", angular.element('#native-query')[0]);
            }
            if (service.job.actual_query) {
                CodeMirror.runMode(service.job.actual_query, "text/x-sql", angular.element('#actual-query')[0]);
            }
        });
    };

    service.submitJob = function(values) {
        values = angular.extend({}, values, {
            'queue': service.active_queue
        });

        return resources.jobs.save(values).$promise.then(function(job) {
            service.fetchStatus();
            service.activateJob(job);
        });
    };

    service.showModal = function(modal, job) {
        service.errors = {};

        if (angular.isDefined(job)) {
            service.values = job;
        }

        $('#' + modal + '-modal').modal('show');
    };

    service.updateJob = function() {
        resources.jobs.update({id: service.values.id}, service.values).$promise.then(function() {
            service.fetchJobs();
            $('.modal').modal('hide');
        }, function(response) {
            service.errors = response.data;
        });
    };

    service.abortJob = function() {
        resources.jobs.update({id: service.values.id, detail_route: 'abort'}, {}, function() {
            service.fetchStatus();
            service.fetchJobs();
            $('.modal').modal('hide');
        });
    };

    service.removeJob = function() {
        var index = service.jobs.indexOf($filter('filter')(service.jobs, {
            'id': service.job.id
        })[0]);

        var next_job = null;
        if (index == 0 && angular.isDefined(service.jobs[1])) {
            next_job = service.jobs[1];
        } else if (angular.isDefined(service.jobs[index - 1])){
            next_job = service.jobs[index - 1];
        }

        resources.jobs.delete({id: service.values.id}, function() {
            service.fetchStatus();
            service.fetchJobs();

            if (next_job) {
                service.activateJob(next_job);
            } else {
                service.activateFirstForm();
            }

            $('.modal').modal('hide');
        });
    };

    service.activateTab = function(tab) {
        service.initTab(tab);
        service.tab = tab;
    };

    service.initTab = function(tab) {
        if (tab == 'results') {
            if (!service.table.ready) {
                if (service.job && service.job.phase == 'COMPLETED') {
                    service.table.init({
                        rows_url: 'query/api/jobs/' + service.job.id +'/rows/',
                        columns_url: 'query/api/jobs/' + service.job.id +'/columns/',
                        files_url: 'files/api/files/',
                        references_url: 'serve/api/references/',
                        params: {
                            job: service.job.id
                        }
                    });
                } else {
                    service.table.clear();
                }
            }
        } else if (tab == 'plot') {
            if (!service.plot.ready) {
                if (service.job && service.job.phase == 'COMPLETED') {
                    service.plot.init({
                        rows_url: 'query/api/jobs/' + service.job.id +'/rows/',
                        columns: service.job.columns
                    });
                } else {
                    service.plot.clear();
                }
            }
        } else if (tab == 'download') {
            service.downloads.init({
                job: service.job
            });
        }
    }

    return service;

}]);
<|endoftext|>


<commit_before><commit_msg>Code review feedback: helper to standardize key names<commit_after><|endoftext|>


<commit_before>"use strict"

var assert = require('assert');

var add = function(str) {
  var delimiter = customDelimiter(str);
  var numbers = this.tokenize(str, delimiter);
  return numbers.reduce(function(x,y) {
    var n = y|0;
    if (n < 0) {
      throw new Error('Negative numbers not supported');
    }
    return x + n;
  }, 0);
};

var tokenize = function(str, delimiter) {
  var s = delimiter.line.replace('\n', delimiter.separator);
  return s.split(delimiter.separator);
};

var StringCalculator = function() {
  this.add = add;
  this.tokenize = tokenize;
  return this;
};

var customDelimiter = function(str) {
  if (str.indexOf('//') === 0) {
    return {
      line: str.substr(str.indexOf('\n')+1),
      separator: str[2]
    };
  } else {
    return {
      line: str,
      separator: ','
    };
  }
};

describe('string calculator', function() {
  describe('simple add', function() {

    var calculator = new StringCalculator();

    it('should return 0 for empty string', function() {
      assert.equal(0, calculator.add(''));
    });

    it('should return a single digit', function() {
      assert.equal(99, calculator.add('99'));
    });

    it('should add numbers separaetd by commas', function() {
      assert.equal(101, calculator.add('77,24'));
    });

    it('should add a variable number of commas', function() {
      assert.equal(10, calculator.add('1,2,3,4'));
    });

    it('should also consider \\n a separate', function() {
      assert.equal(10, calculator.add('6\n4'));
    });

    it('should identify custom delimiters', function() {
      var delim = customDelimiter('//q\n1q2');
      assert.equal('q', delim.separator);
      assert.equal('1q2', delim.line);
    });

    it('should return default separator', function() {
      var delim = customDelimiter('1,2');
      assert.equal(',', delim.separator);
      assert.equal('1,2', delim.line);
    });

    it('should add using the custom delimiter', function() {
      assert.equal(3, calculator.add('//q\n1q2'));
    });

    it('should not support negative numbers', function() {
      assert.throws(function() {
          calculator.add('-1');
        });
    });

    it('should include all numbers in the message', function() {
      assert.throws(function() {
          calculator.add('-1,-2');
        },
        function(e) {
          assert(e.message.indexOf('-1') !== -1);
          assert(e.message.indexOf('-2') !== -2);
        }
      );
    });
  });
});
<commit_msg>failing test<commit_after>"use strict"

var assert = require('assert');

var add = function(str) {
  var delimiter = customDelimiter(str);
  var numbers = this.tokenize(str, delimiter);
  return numbers.reduce(function(x,y) {
    var n = y|0;
    if (n < 0) {
      throw new Error('Negative numbers not supported');
    }
    return x + n;
  }, 0);
};

var tokenize = function(str, delimiter) {
  var s = delimiter.line.replace('\n', delimiter.separator);
  return s.split(delimiter.separator);
};

var StringCalculator = function() {
  this.add = add;
  this.tokenize = tokenize;
  return this;
};

var customDelimiter = function(str) {
  if (str.indexOf('//') === 0) {
    return {
      line: str.substr(str.indexOf('\n')+1),
      separator: str[2]
    };
  } else {
    return {
      line: str,
      separator: ','
    };
  }
};

describe('string calculator', function() {
  describe('simple add', function() {

    var calculator = new StringCalculator();

    it('should return 0 for empty string', function() {
      assert.equal(0, calculator.add(''));
    });

    it('should return a single digit', function() {
      assert.equal(99, calculator.add('99'));
    });

    it('should add numbers separaetd by commas', function() {
      assert.equal(101, calculator.add('77,24'));
    });

    it('should add a variable number of commas', function() {
      assert.equal(10, calculator.add('1,2,3,4'));
    });

    it('should also consider \\n a separate', function() {
      assert.equal(10, calculator.add('6\n4'));
    });

    it('should identify custom delimiters', function() {
      var delim = customDelimiter('//q\n1q2');
      assert.equal('q', delim.separator);
      assert.equal('1q2', delim.line);
    });

    it('should return default separator', function() {
      var delim = customDelimiter('1,2');
      assert.equal(',', delim.separator);
      assert.equal('1,2', delim.line);
    });

    it('should add using the custom delimiter', function() {
      assert.equal(3, calculator.add('//q\n1q2'));
    });

    it('should not support negative numbers', function() {
      assert.throws(function() {
          calculator.add('-1');
        });
    });

    it('should include all numbers in the message', function() {
      assert.throws(function() {
          calculator.add('-1,-2');
        },
        function(e) {
          assert(e.message.indexOf('-1') !== -1, 'num not found');
          assert(e.message.indexOf('-2') !== -2);
        }
      );
    });
  });
});
<|endoftext|>


<commit_before>"use strict";

var assert = require('assert');

var sumArrayOfStringsAsInts = function(n) {
    var sum = 0;

    var errors = [];

    for (var i=0;i<n.length;++i) {
      var num = n[i]|0;
      if (num < 0) {
        errors.push(num);
      }

      if (num < 1000) {
        sum += num;
      }
    }

    if (errors.length) { throw Error('negatives not allowed: ' + errors); }

    return sum;
};

var tokenize = function(s, delim) {
  s = s.replace(/\n/g, delim);
  return s.split(delim);
};

var StringCalculator = function() {
  StringCalculator.prototype.add = function(s) {
    if (s.length === 0) {
      return 0;
    }

    var result = getDelimiter(s);
    var tokens = tokenize(result.line,result.delimiter);
    return sumArrayOfStringsAsInts(tokens);
  };
  return this;
};

var hasCustomDelimiter = function(s) {
  return s.indexOf('//') === 0;
};

var getDelimiter = function(s) {
  if (hasCustomDelimiter(s)) {
    return {
      delimiter: s.substr(2, s.indexOf('\n') - 2),
      line: s.substr(s.indexOf('\n') + 1)
    };
  }

  return { delimiter: ',', line: s };
};

describe('string calculator', function() {
  it('exists', function() {
    assert(new StringCalculator());
  });

  it('has an add method', function() {
    assert(new StringCalculator().add);
  });

  describe('delimiters', function() {
    it('knows about custom delimters', function() {
      assert(hasCustomDelimiter('//q\n1q2q3'));
    });

    it('ignores normal delimters', function() {
      assert(!hasCustomDelimiter('1,2,3'));
    });

    it('returns delimiter', function() {
      var s = getDelimiter('//q\n1q2q3');
      assert('q', s.delimiter);
    });

    it('returns default delimiters', function() {
      var s = getDelimiter('1,2,3');
      assert(',', s.delimiter);
    });

    it('consumes some of the string', function() {
      assert.equal('1,2,3', getDelimiter('1,2,3').line);
      assert.equal('1q2q3', getDelimiter('//q\n1q2q3').line);
    });
  });

  describe('add method', function() {
    var stringCalculator = new StringCalculator();

    it('0 args', function() {
      assert.equal(0, stringCalculator.add(''));
    });

    it('1 arg', function() {
      assert.equal(55, stringCalculator.add('55'));
    });

    it('2 args', function() {
      assert.equal(74, stringCalculator.add('69,5'));
    });

    it('n args', function() {
      assert.equal(1+2+3+4+5, stringCalculator.add('1,2,3,4,5'));
    });

    it('handles new lines as separators', function() {
      assert.equal(1+2+3, stringCalculator.add('1\n2\n3'));
    });

    it('supports delimiters', function() {
      assert.equal(1+2+3, stringCalculator.add('//q\n1q2q3'));
    });

    it('dislikes negatives', function() {
      assert.throws(function() {
        stringCalculator.add('1,-2,3');
      });
    });

    it('lists all negatives', function() {
      assert.throws(function() { stringCalculator.add('-1,-2,-3'); }, function(e) { return e.message.indexOf('-1,-2,-3') !== -1; });
    });

    it('numbers > 1000 ignored', function() {
      assert.equal(0, stringCalculator.add('1001'));
    });
  });
});<commit_msg>fail test<commit_after>"use strict";

var assert = require('assert');

var sumArrayOfStringsAsInts = function(n) {
    var sum = 0;

    var errors = [];

    for (var i=0;i<n.length;++i) {
      var num = n[i]|0;
      if (num < 0) {
        errors.push(num);
      }

      if (num < 1000) {
        sum += num;
      }
    }

    if (errors.length) { throw Error('negatives not allowed: ' + errors); }

    return sum;
};

var tokenize = function(s, delim) {
  s = s.replace(/\n/g, delim);
  return s.split(delim);
};

var StringCalculator = function() {
  StringCalculator.prototype.add = function(s) {
    if (s.length === 0) {
      return 0;
    }

    var result = getDelimiter(s);
    var tokens = tokenize(result.line,result.delimiter);
    return sumArrayOfStringsAsInts(tokens);
  };
  return this;
};

var hasCustomDelimiter = function(s) {
  return s.indexOf('//') === 0;
};

var getDelimiter = function(s) {
  if (hasCustomDelimiter(s)) {
    return {
      delimiter: s.substr(2, s.indexOf('\n') - 2),
      line: s.substr(s.indexOf('\n') + 1)
    };
  }

  return { delimiter: ',', line: s };
};

describe('string calculator', function() {
  it('exists', function() {
    assert(new StringCalculator());
  });

  it('has an add method', function() {
    assert(new StringCalculator().add);
  });

  describe('delimiters', function() {
    it('knows about custom delimters', function() {
      assert(hasCustomDelimiter('//q\n1q2q3'));
    });

    it('ignores normal delimters', function() {
      assert(!hasCustomDelimiter('1,2,3'));
    });

    it('returns delimiter', function() {
      var s = getDelimiter('//q\n1q2q3');
      assert('q', s.delimiter);
    });

    it('returns default delimiters', function() {
      var s = getDelimiter('1,2,3');
      assert(',', s.delimiter);
    });

    it('consumes some of the string', function() {
      assert.equal('1,2,3', getDelimiter('1,2,3').line);
      assert.equal('1q2q3', getDelimiter('//q\n1q2q3').line);
    });
  });

  describe('add method', function() {
    var stringCalculator = new StringCalculator();

    it('0 args', function() {
      assert.equal(0, stringCalculator.add(''));
    });

    it('1 arg', function() {
      assert.equal(55, stringCalculator.add('55'));
    });

    it('2 args', function() {
      assert.equal(74, stringCalculator.add('69,5'));
    });

    it('n args', function() {
      assert.equal(1+2+3+4+5, stringCalculator.add('1,2,3,4,5'));
    });

    it('handles new lines as separators', function() {
      assert.equal(1+2+3, stringCalculator.add('1\n2\n3'));
    });

    it('supports delimiters', function() {
      assert.equal(1+2+3, stringCalculator.add('//q\n1q2q3'));
    });

    it('dislikes negatives', function() {
      assert.throws(function() {
        stringCalculator.add('1,-2,3');
      });
    });

    it('lists all negatives', function() {
      assert.throws(function() { stringCalculator.add('-1,-2,-3'); }, function(e) { return e.message.indexOf('-1,-2,-3') !== -1; });
    });

    it('numbers > 1000 ignored', function() {
      assert.equal(0, stringCalculator.add('1001'));
    });

    it('supports delims of any length', function() {
      assert.equal(6, stringCalculator.add('//[***]\n1***2***3'));
    });
  });
});<|endoftext|>


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

class TableColumn extends Component {
  static propTypes = {
    cellRenderer: PropTypes.func,
    dataKey: PropTypes.string,
    headerRenderer: PropTypes.func,
  };

  render() {
    return

;
  }
}

export default TableColumn;
<commit_msg>make TableColumn compliant<commit_after>import React, {Component, PropTypes} from 'react';
import classNames from 'classnames';

export default class TableColumn extends Component {
  static propTypes = {
    cellRenderer: PropTypes.func,
    className: PropTypes.string,
    dataKey: PropTypes.string,
    headerRenderer: PropTypes.func,
  };

  render() {
    let classes = classNames(
      'sd-table-column',
      this.props.className
    );
    return

;
  }
}
<|endoftext|>


<commit_before>cdb.windshaft.Dashboard = function(options) {
  this.cartoDBLayerGroup = options.cartoDBLayerGroup;
  this.widgets = options.widgets;
  this.client = options.client;
  this.statTag = options.statTag;
  this.configGenerator = options.configGenerator;

  this.instance = new cdb.windshaft.DashboardInstance();
  this.cartoDBLayerGroup.bindDashboardInstance(this.instance);

  // Bindings
  this.cartoDBLayerGroup.layers.bind('change', this.createInstance, this);
  this.instance.bind('change:layergroupid', this._updateWidgets, this);
};

cdb.windshaft.Dashboard.prototype._updateWidgets = function(dashboardInstance) {
  _.each(this.widgets, function(widget) {
    widget.set({
      dashboardBaseURL: dashboardInstance.getBaseURL(),
    });
  }.bind(this));
};

cdb.windshaft.Dashboard.prototype.createInstance = function() {
  var dashboardConfig = this.configGenerator.generate(this);
  var instance = this.client.instantiateMap(dashboardConfig);
  instance.bind('change:layergroupid', function() {
    this.instance.set(instance.toJSON());
  }.bind(this));

  return this.instance;
};

cdb.windshaft.Dashboard.prototype.getLayers = function() {
  return this.cartoDBLayerGroup.layers;
};

cdb.windshaft.Dashboard.prototype.getVisibleLayers = function() {
  return this.cartoDBLayerGroup.getVisibleLayers();
};

cdb.windshaft.Dashboard.prototype.getWidgets = function() {
  return this.widgets;
};

<commit_msg>Whitespace<commit_after>cdb.windshaft.Dashboard = function(options) {
  this.cartoDBLayerGroup = options.cartoDBLayerGroup;
  this.widgets = options.widgets;
  this.client = options.client;
  this.statTag = options.statTag;
  this.configGenerator = options.configGenerator;

  this.instance = new cdb.windshaft.DashboardInstance();
  this.cartoDBLayerGroup.bindDashboardInstance(this.instance);

  // Bindings
  this.cartoDBLayerGroup.layers.bind('change', this.createInstance, this);
  this.instance.bind('change:layergroupid', this._updateWidgets, this);
};

cdb.windshaft.Dashboard.prototype._updateWidgets = function(dashboardInstance) {
  _.each(this.widgets, function(widget) {
    widget.set({
      dashboardBaseURL: dashboardInstance.getBaseURL(),
    });
  }.bind(this));
};

cdb.windshaft.Dashboard.prototype.createInstance = function() {
  var dashboardConfig = this.configGenerator.generate(this);
  var instance = this.client.instantiateMap(dashboardConfig);
  instance.bind('change:layergroupid', function() {
    this.instance.set(instance.toJSON());
  }.bind(this));

  return this.instance;
};

cdb.windshaft.Dashboard.prototype.getLayers = function() {
  return this.cartoDBLayerGroup.layers;
};

cdb.windshaft.Dashboard.prototype.getVisibleLayers = function() {
  return this.cartoDBLayerGroup.getVisibleLayers();
};

cdb.windshaft.Dashboard.prototype.getWidgets = function() {
  return this.widgets;
};
<|endoftext|>