Skip to content

Commit

Permalink
over 9000
Browse files Browse the repository at this point in the history
  • Loading branch information
tothandras committed Aug 17, 2014
1 parent 5e1cdd4 commit b5093d2
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 107 deletions.
170 changes: 85 additions & 85 deletions www/waterfall_view/src/module/main.module.coffee
Expand Up @@ -192,7 +192,7 @@ class Waterfall extends Controller
# Returns the result string of a builder, build or step
###
result: (b) ->
if not b.complete and b.started_at > 0
if not b.complete and b.started_at >= 0
result = 'pending'
else
switch b.results
Expand Down Expand Up @@ -371,97 +371,97 @@ class Waterfall extends Controller
.attr('y', -3)
.text((build) -> build.buildid)

###
# Event actions
###
@mouseOver = (build) ->
e = self.d3.select(@)
mouse = self.d3.mouse(@)
self.addTicks(build)
self.drawYAxis()

# Move build and builder to front
p = self.d3.select(@parentNode)
@parentNode.appendChild(@)
p.each -> @parentNode.appendChild(@)

# Show tooltip on the left or on the right
r = build.builderid < self.builders.length / 2

# Create tooltip
height = 40
points = ->
if r then "20,0 0,#{height / 2} 20,#{height} 170,#{height} 170,0"
else "150,0 170,#{height / 2} 150,#{height} 0,#{height} 0,0"
tooltip = e.append('g')
.attr('class', 'svg-tooltip')
.attr('transform', "translate(#{mouse[0]}, #{mouse[1]})")
.append('g')
.attr('class', 'tooltip-content')
.attr('transform', "translate(#{if r then 5 else -175}, #{- height / 2})")

tooltip.append('polygon')
.attr('points', points())

# Load steps
build.all('steps').bind(self.$scope).then (buildsteps) ->

# Resize the tooltip
height = buildsteps.length * 15 + 7
tooltip.transition().duration(100)
.attr('transform', "translate(#{if r then 5 else -175}, #{- height / 2})")
.select('polygon')
.attr('points', points())

duration = (step) ->
d = new Date((step.complete_at - step.started_at) * 1000)
if d > 0 then "(#{d / 1000}s)" else ''
tooltip.selectAll('.buildstep')
.data(buildsteps)
.enter().append('g')
.attr('class', 'buildstep')
# Add text
.append('text')
.attr('y', (step, i) -> 15 * (i + 1))
.attr('x', if r then 30 else 10)
.attr('class', color)
.classed('fill', true)
.transition().delay(100)
# Text format
.text((step, i) -> "#{i + 1}. #{step.name} #{duration(step)}")

@mouseMove = (build) ->
e = self.d3.select(@)

# Move the tooltip to the mouse position
mouse = self.d3.mouse(@)
e.select('.svg-tooltip')
.attr('transform', "translate(#{mouse[0]}, #{mouse[1]})")

@mouseOut = (build) ->
e = self.d3.select(@)
self.removeTicks()
self.drawYAxis()

# Remove tooltip
e.selectAll('.svg-tooltip').remove()

@click = (build) ->
# Open modal on click
modal = self.$modal.open
templateUrl: 'waterfall_view/views/modal.html'
controller: 'waterfallModalController as modal'
windowClass: 'modal-small'
resolve:
selectedBuild: -> build

# Add event listeners
builds
.on('mouseover', @mouseOver)
.on('mousemove', @mouseMove)
.on('mouseout', @mouseOut)
.on('click', @click)

###
# Event actions
###
mouseOver: (build) ->
e = self.d3.select(@)
mouse = self.d3.mouse(@)
self.addTicks(build)
self.drawYAxis()

# Move build and builder to front
p = self.d3.select(@parentNode)
@parentNode.appendChild(@)
p.each -> @parentNode.appendChild(@)

# Show tooltip on the left or on the right
r = build.builderid < self.builders.length / 2

# Create tooltip
height = 40
points = ->
if r then "20,0 0,#{height / 2} 20,#{height} 170,#{height} 170,0"
else "150,0 170,#{height / 2} 150,#{height} 0,#{height} 0,0"
tooltip = e.append('g')
.attr('class', 'svg-tooltip')
.attr('transform', "translate(#{mouse[0]}, #{mouse[1]})")
.append('g')
.attr('class', 'tooltip-content')
.attr('transform', "translate(#{if r then 5 else -175}, #{- height / 2})")

tooltip.append('polygon')
.attr('points', points())

# Load steps
build.all('steps').bind(self.$scope).then (buildsteps) ->

# Resize the tooltip
height = buildsteps.length * 15 + 7
tooltip.transition().duration(100)
.attr('transform', "translate(#{if r then 5 else -175}, #{- height / 2})")
.select('polygon')
.attr('points', points())

duration = (step) ->
d = new Date((step.complete_at - step.started_at) * 1000)
if d > 0 then "(#{d / 1000}s)" else ''
tooltip.selectAll('.buildstep')
.data(buildsteps)
.enter().append('g')
.attr('class', 'buildstep')
# Add text
.append('text')
.attr('y', (step, i) -> 15 * (i + 1))
.attr('x', if r then 30 else 10)
.attr('class', color)
.classed('fill', true)
.transition().delay(100)
# Text format
.text((step, i) -> "#{i + 1}. #{step.name} #{duration(step)}")

mouseMove: (build) ->
e = self.d3.select(@)

# Move the tooltip to the mouse position
mouse = self.d3.mouse(@)
e.select('.svg-tooltip')
.attr('transform', "translate(#{mouse[0]}, #{mouse[1]})")

mouseOut: (build) ->
e = self.d3.select(@)
self.removeTicks()
self.drawYAxis()

# Remove tooltip
e.selectAll('.svg-tooltip').remove()

click: (build) ->
# Open modal on click
modal = self.$modal.open
templateUrl: 'waterfall_view/views/modal.html'
controller: 'waterfallModalController as modal'
windowClass: 'modal-small'
resolve:
selectedBuild: -> build

###
# Render the waterfall view
###
Expand Down
62 changes: 49 additions & 13 deletions www/waterfall_view/src/module/main.module.spec.coffee
Expand Up @@ -6,7 +6,7 @@ beforeEach ->
null

describe 'Waterfall view controller', ->
$rootScope = $state = elem = w = $document = $window = $modal = config = null
$rootScope = $state = elem = w = $document = $window = $modal = config = $timeout = null

injected = ($injector) ->
$rootScope = $injector.get('$rootScope')
Expand All @@ -17,16 +17,25 @@ describe 'Waterfall view controller', ->
$document = $injector.get('$document')
$window = $injector.get('$window')
$modal = $injector.get('$modal')
$timeout = $injector.get('$timeout')
config = $injector.get('config')
elem = angular.element('<div></div>')
elem.append($compile('<ui-view></ui-view>')(scope))
$document.find('body').append(elem)

$state.transitionTo('waterfall')
$rootScope.$digest()
w = $document.find('.waterfall').scope().w

scope = $document.find('.waterfall').scope()
w = $document.find('.waterfall').controller()
spyOn(w, 'mouseOver').and.callThrough()
spyOn(w, 'mouseOut').and.callThrough()
spyOn(w, 'mouseMove').and.callThrough()
spyOn(w, 'click').and.callThrough()
spyOn(w, 'loadMore').and.callThrough()
# We don't want the setHeight to call loadMore
spyOn(w, 'setHeight').and.callFake ->
# Data is loaded
$timeout.flush()

beforeEach(inject(injected))

Expand Down Expand Up @@ -59,30 +68,57 @@ describe 'Waterfall view controller', ->
n.__onclick()
expect($modal.open).toHaveBeenCalled()
# Test mouseover
expect(w.mouseOver).not.toHaveBeenCalled()
expect(e.select('.svg-tooltip').empty()).toBe(true)
n.__onmouseover({})
expect(w.mouseOver).toHaveBeenCalled()
expect(e.select('.svg-tooltip').empty()).toBe(false)
# Test mousemove
event = document.createEvent('MouseEvents')
event.initMouseEvent('mousemove', true, true, window,
0, 0, 0, 100, 850, false, false, false, false, 0, null)
expect(e.select('.svg-tooltip').attr('transform')).toContain('NaN')
n.__onmousemove(event)
expect(e.select('.svg-tooltip').attr('transform')).not.toContain('NaN')
expect(w.mouseMove).not.toHaveBeenCalled()
n.__onmousemove({})
expect(w.mouseMove).toHaveBeenCalled()
# Test mouseout
expect(w.mouseOut).not.toHaveBeenCalled()
expect(e.select('.svg-tooltip').empty()).toBe(false)
n.__onmouseout({})
expect(w.mouseOut).toHaveBeenCalled()
expect(e.select('.svg-tooltip').empty()).toBe(true)

it 'should rerender the waterfall on resize', ->
spyOn(w, 'render')
spyOn(w, 'render').and.callThrough()
expect(w.render).not.toHaveBeenCalled()
angular.element($window).triggerHandler('resize')
expect(w.render).toHaveBeenCalled()

it 'should rerender the waterfall on data change', ->
spyOn(w, 'render')
spyOn(w, 'render').and.callThrough()
expect(w.render).not.toHaveBeenCalled()
#w.loadMore()
$rootScope.$digest()
w.loadMore()
$timeout.flush()
expect(w.render).toHaveBeenCalled()

it 'should lazy load data on scroll', ->
spyOn(w, 'getHeight').and.returnValue(900)
e = d3.select('.inner-content')
n = e.node()
expect(w.loadMore).not.toHaveBeenCalled()
angular.element(n).triggerHandler('scroll')
expect(w.loadMore).toHaveBeenCalled()

it 'should have string representations of result codes', ->
testBuild =
complete: false
started_at: 0
expect(w.result(testBuild)).toBe('pending')
testBuild.complete = true
expect(w.result(testBuild)).toBe('unknown')
results =
0: 'success'
1: 'warnings'
2: 'failure'
3: 'skipped'
4: 'exception'
5: 'cancelled'
for i in [0..5]
testBuild.results = i
expect(w.result(testBuild)).toBe(results[i])
24 changes: 15 additions & 9 deletions www/waterfall_view/test/buildbot/buildbot.service.coffee
Expand Up @@ -46,8 +46,6 @@ builds = [
complete_at: 0
complete: false
]
for build in builds
build.all = -> bind: -> then: ->

buildrequests = [
builderid: 1
Expand All @@ -65,15 +63,16 @@ buildrequests = [

# Mocked buildbot service
class Buildbot extends Service('common')
constructor: ($q) ->
constructor: ($q, $timeout) ->
@some = (string, options) ->
deferred = $q.defer()
switch string
when 'builds' then deferred.resolve builds[0..options.limit-1]
when 'builders' then deferred.resolve builders[0..options.limit-1]
when 'buildrequests' then deferred.resolve buildrequests[0..options.limit-1]
else
deferred.resolve []
resolve = ->
switch string
when 'builds' then deferred.resolve builds[0..options.limit-1]
when 'builders' then deferred.resolve builders[0..options.limit-1]
when 'buildrequests' then deferred.resolve buildrequests[0..options.limit-1]
else deferred.resolve []
$timeout(resolve, 100)
bind: (scope) ->
deferred.promise.then (b) ->
scope[string] = b
Expand All @@ -92,3 +91,10 @@ class Buildbot extends Service('common')
deferred.promise
getList: ->
deferred.promise

for build in builds
build.all = (string) ->
deferred = $q.defer()
# TODO sample data (eg. steps)
deferred.resolve []
bind: -> deferred.promise

0 comments on commit b5093d2

Please sign in to comment.