Merge branch 'master' of github.com:travis-ci/travis-web

This commit is contained in:
Sven Fuchs 2012-12-05 11:23:28 +01:00
commit e62dc32d2a
16 changed files with 693 additions and 58 deletions

View File

@ -34,7 +34,7 @@ input assets.scripts do
safe_concat assets.vendor_order, 'vendor.js'
end
match '{spec,spec/unit}/*.js' do
match '{spec,spec/unit,spec/unit/views}/*.js' do
concat 'spec/specs.js'
end

View File

@ -1,32 +1,41 @@
require 'travis/model'
@Travis.Artifact = Travis.Model.extend
version: 1 # used to refresh log on requeue
body: DS.attr('string')
init: ->
@_super.apply this, arguments
@set 'queue', Ember.A([])
@set 'parts', Ember.ArrayProxy.create(content: [])
@addObserver 'body', @fetchWorker
@fetchWorker()
clear: ->
@set('body', '')
@incrementProperty('version')
append: (body) ->
if @get('isLoaded')
if @get('isInitialized')
@get('parts').pushObject body
@set('body', @get('body') + body)
else
@get('queue').pushObject(body)
recordDidLoad: (->
if @get('isLoaded')
if (body = @get 'body') && @get('parts.length') == 0
@get('parts').pushObject body
@set 'isInitialized', true
queue = @get('queue')
if queue.get('length') > 0
@append queue.toArray().join('')
).observes('isLoaded')
fetchWorker: ->
if body = @get('body')
if !@get('workerName') && (body = @get('body'))
line = body.split("\n")[0]
if line && (match = line.match /Using worker: (.*)/)
if worker = match[1]

View File

@ -94,6 +94,9 @@ require 'travis/model'
updateTimes: ->
@notifyPropertyChange 'lastBuildDuration'
regenerateKey: (options) ->
Travis.ajax.ajax '/repos/' + @get('id') + '/key', 'post', options
@Travis.Repo.reopenClass
recent: ->
@find()

View File

@ -1,10 +1,18 @@
{{view.logSubscriber}}
{{#if view.job.log.isLoaded}}
<pre id="log" class="ansi"><a href="#" id="tail" {{action toggleTailing target="view"}}>
<span class="status"></span>
<label>Follow logs</label>
</a>{{{formatLog log.body repo="repository" item="parentView.currentItem"}}}</pre>
{{! this #with + #if is needed because I want to rerender 'pre' when log changes to properly clean it up,
this should probably be refactored to use container view}}
{{#with view.job.log}}
{{#if id}}
{{#view view.PreView logBinding="view.context.log" logUrlBinding="view.logUrl"}}
<pre id="log" class="ansi"><a href="#" id="tail" {{action toggleTailing target="view"}}>
<span class="status"></span>
<label>Follow logs</label>
</a></pre>
{{/view}}
{{/if}}
{{/with}}
{{#if sponsor.name}}
<p class="sponsor">

View File

@ -14,6 +14,11 @@
<a href="#" {{action requeueJob target="view"}}>Rebuild</a>
</li>
{{/if}}
{{#if view.canRegenerateKey}}
<li>
<a href="#" name="regenerate-key" class="open-popup" {{action regenerateKeyPopup target="view"}}>Regenerate Key</a>
</li>
{{/if}}
</ul>
</div>
@ -44,3 +49,31 @@
<input type="text" class="rdoc" {{bindAttr value="view.rdocStatusImage"}}></input>
</p>
</div>
<div id="regenerate-key" class="popup">
<a href="#" class="close" {{action popupClose target="view"}}></a>
<p>
Do you really want to regenerate ssl keys for this repository? Please note that
any data, which is encrypted, such as notification services credentials or secure
environment variables will not be accessible during the builds until you re-encrypt
it with the new key.
</p>
<p>
<a class="sync_now button" {{action regenerateKey target="view"}}>Yes! Do it!</a>
<span class="or">or</span>
<a href="#" class="cancel" {{action popupClose target="view"}}>Cancel</a>
</p>
</div>
<div id="regeneration-success" class="popup">
<a href="#" class="close" {{action popupClose target="view"}}></a>
<p>
Key for this repository has been regenerated. If you used previous key
for encryption, you will need encrypt your data again with the new key.
</p>
<p>
You can read more about encryption keys <a href="http://about.travis-ci.org/docs/user/encryption-keys/">in Travis documentation</a>
</p>
</div>

View File

@ -4,7 +4,8 @@ require 'ext/ember/namespace'
View: Em.View.extend
popup: (event) ->
@popupCloseAll()
$("##{event.target.name}").toggleClass('display')
name = if event.target then event.target.name else event
$("##{name}").toggleClass('display')
popupClose: (event) ->
$(event.target).closest('.popup').removeClass('display')
popupCloseAll: ->

View File

@ -50,6 +50,10 @@
templateName: 'jobs/log'
logBinding: 'job.log'
didInsertElement: ->
@_super.apply this, arguments
@tryScrollingToHashLineNumber()
scrollTo: (hash) ->
# and this is even more weird, when changing hash in URL in firefox
# to other value, for example #L10, it actually scrolls just #main
@ -81,11 +85,6 @@
checker()
didInsertElement: ->
@_super.apply this, arguments
@tryScrollingToHashLineNumber()
click: (event) ->
target = $(event.target)
@ -115,3 +114,93 @@
job.subscribe()
null
).property('job', 'job.state')
logUrl: (->
repo = @get('job.repo')
item = @get('parentView.currentItem')
if repo && item
event = if item.constructor == Travis.Build
'showBuild'
else
'showJob'
Travis.app.get('router').urlForEvent(event, repo, item)
).property('job.repo', 'parentView.currentItem')
PreView: Em.View.extend
init: ->
@_super.apply this, arguments
@set 'logManager', Travis.Log.create(target: this)
didInsertElement: ->
@_super.apply this, arguments
Ember.run.next this, ->
if @get 'log.isInitialized'
@logDidChange()
willDestroy: ->
@get('logManager').destroy()
@get('log.parts').removeArrayObserver this,
didChange: 'logContentsDidChange'
willChange: 'logContentsWillChange'
version: (->
@rerender()
@set 'logManager', Travis.Log.create(target: this)
).observes('log.version')
logDidChange: (->
if @get('log.isInitialized') && @state == 'inDOM'
@attachLogObservers()
).observes('log', 'log.isInitialized')
attachLogObservers: ->
return if @get('logPartsObserversAttached') == Ember.guidFor(@get('log'))
@set 'logPartsObserversAttached', Ember.guidFor(@get('log'))
Ember.run.next this, ->
@get('logManager').append @get('log.parts')
@get('log.parts').addArrayObserver this,
didChange: 'logContentsDidChange'
willChange: 'logContentsWillChange'
logContentsDidChange: (lines, index, removedCount, addedCount) ->
addedLines = lines.slice(index, index + addedCount)
@get('logManager').append addedLines
logContentsWillChange: (-> )
appendLog: (payloads) ->
url = @get('logUrl')
for payload in payloads
line = payload.content
number = payload.number
unless payload.append
pathWithNumber = "#{url}#L#{number}"
line = '<p><a href="%@" id="L%@" class="log-line-number" name="L%@">%@</a>%@</p>'.fmt(pathWithNumber, number, number, number, line)
if payload.fold && !payload.foldContinuation
line = "<div class='fold #{payload.fold} show-first-line'>#{line}</div>"
if payload.replace
this.$("#log #L#{number}").parent().replaceWith line
else if payload.append
this.$("#log #L#{number}").parent().append line
else if payload.foldContinuation
this.$("#log .fold.#{payload.fold}:last").append line
else
this.$('#log').append(line)
if payload.openFold
this.$("#log .fold.#{payload.openFold}:last").
removeClass('show-first-line').
addClass('open')
if payload.foldEnd
this.$("#log .fold.#{payload.fold}:last").removeClass('show-first-line')

View File

@ -96,6 +96,12 @@
@popup(event)
event.stopPropagation()
regenerateKeyPopup: (event) ->
@set('active', true)
@closeMenu()
@popup(event)
event.stopPropagation()
requeueBuild: ->
@closeMenu()
@get('build').requeue()
@ -104,6 +110,16 @@
@closeMenu()
@get('job').requeue()
regenerateKey: ->
@popupCloseAll()
self = this
@get('repo').regenerateKey
success: ->
self.popup('regeneration-success')
error: ->
Travis.app.router.flashController.loadFlashes([{ error: 'Travis encountered an error while trying to regenerate the key, please try again.'}])
canRequeueBuild: (->
@get('isBuildTab') && @get('build.isFinished') && @get('hasPermissions')
).property('isBuildTab', 'build.isFinished', 'hasPermissions')
@ -112,6 +128,10 @@
@get('isJobTab') && @get('job.isFinished') && @get('hasPermissions')
).property('isJobTab', 'job.isFinished', 'hasPermissions')
canRegenerateKey: (->
@get('hasPermissions')
).property('hasPermissions')
isBuildTab: (->
# ['current', 'build', 'job'].indexOf(@get('tab')) > -1
@get('tab') in ['current', 'build']

View File

@ -1,41 +1,125 @@
@Travis.Log =
FOLDS:
schema: /(<p.*?\/a>\$ (?:bundle exec )?rake( db:create)? db:schema:load[\s\S]*?<p.*?\/a>-- assume_migrated_upto_version[\s\S]*?<\/p>\n<p.*?\/a>.*<\/p>)/g
migrate: /(<p.*?\/a>\$ (?:bundle exec )?rake( db:create)? db:migrate[\s\S]*== +\w+: migrated \(.*\) =+)/g
bundle: /(<p.*?\/a>\$ bundle install.*<\/p>\n(<p.*?\/a>(Updating|Using|Installing|Fetching|remote:|Receiving|Resolving).*?<\/p>\n|<p.*?\/a><\/p>\n)*)/g
exec: /(<p.*?\/a>[\/\w]*.rvm\/rubies\/[\S]*?\/(ruby|rbx|jruby) .*?<\/p>)/g
# TODO: revisit those patterns
FOLDS = [
Em.Object.create(name: 'schema', startPattern: /^\$ (?:bundle exec )?rake( db:create)? db:schema:load/, endPattern: /^\$/)
Em.Object.create(name: 'migrate', startPattern: /^\$ (?:bundle exec )?rake( db:create)? db:migrate/, endPattern: /^\$/)
Em.Object.create(name: 'bundle', startPattern: /^\$ bundle install/, endPattern: /^\$/)
]
filter: (log, path) ->
log = @escape(log)
log = @deansi(log)
log = log.replace(/\r/g, '')
log = @number(log, path)
log = @fold(log)
log = log.replace(/\n/g, '')
log
@Travis.Log = Em.Object.extend
init: ->
@set 'folds', []
@set 'line', 1
@initial = true
stripPaths: (log) ->
log.replace /\/home\/vagrant\/builds(\/[^\/\n]+){2}\//g, ''
for fold in FOLDS
@addFold fold
append: (lines) ->
log = @join lines
log = @escape log
log = @deansi log
lines = @split log
target = @get 'target'
index = 0
currentFold = @currentFold
@set 'lineNumber', 1 unless @get 'lineNumber'
result = []
for line in lines
if line == '\r'
@set 'replace', true
else if line == '\n'
@set 'newline', true
index += 1
else
if currentFold && ( @isFoldEnding(currentFold, line) )
# end of the fold, send fold to target
if result.length > 0
result.slice(-1)[0].foldEnd = true
target.appendLog result
@currentFold = currentFold = null
@set 'foldContinuation', false
result = []
if !currentFold && ( currentFold = @foldByStart(line) )
# beginning new fold, send current lines to target
if result.length > 0
target.appendLog result
result = []
start = index
payload = { content: line }
if currentFold
payload.fold = currentFold.get('name')
if @get 'foldContinuation'
payload.foldContinuation = true
payload.number = @get('lineNumber') + index
if @get 'replace'
@set 'replace', false
payload.replace = true
else if @get 'newline'
@set 'newline', false
else if !@initial
payload.append = true
@initial = false
if payload.foldContinuation && payload.content.match(/Done. Build script exited with:/)
# script ended, but fold is still closed, which most probably means
# error, end the fold and open it.
# TODO: we need log marks to make it easier
payload.foldContinuation = null
payload.openFold = payload.fold
payload.fold = null
result.pushObject payload
if currentFold
@set 'foldContinuation', true
if result.length > 0
if currentFold
@currentFold = currentFold
target.appendLog result
nextLineNumber = @get('lineNumber') + index
@set 'lineNumber', nextLineNumber
join: (lines) ->
if typeof lines == 'string'
lines
else
lines.toArray().join ''
split: (log) ->
log = log.replace /\r\n/g, '\n'
lines = log.split(/(\n)/)
if lines.slice(-1)[0] == ''
lines.popObject()
result = []
for line in lines
result.pushObjects line.split(/(\r)/)
result
escape: (log) ->
Handlebars.Utils.escapeExpression log
escapeRuby: (log) ->
log.replace /#<(\w+.*?)>/, '#&lt;$1&gt;'
number: (log, path) ->
path = "#{path}/"
result = ''
$.each log.trim().split('\n'), (ix, line) ->
number = ix + 1
pathWithNumber = "#{path}#L#{number}"
result += '<p><a href="%@" id="L%@" class="log-line-number" name="L%@">%@</a>%@</p>\n'.fmt(pathWithNumber, number, number, number, line)
result.trim()
deansi: (log) ->
log = log.replace(/\r\r/g, '\r')
.replace(/\033\[K\r/g, '\r')
.replace(/^.*\r(?!$)/gm, '')
.replace(/\[2K/g, '')
.replace(/\033\(B/g, '')
.replace(/\033\[\d+G/, '')
@ -52,17 +136,11 @@
text += (if classes.length then ('<span class=\'' + classes.join(' ') + '\'>' + part.text + '</span>') else part.text)
text.replace /\033/g, ''
fold: (log) ->
log = @unfold(log)
$.each Travis.Log.FOLDS, (name, pattern) ->
log = log.replace(pattern, ->
'<div class=\'fold ' + name + '\'>' + arguments[1].trim() + '</div>'
)
log
addFold: (fold) ->
@get('folds').pushObject fold
unfold: (log) ->
log.replace /<div class='fold[^']*'>([\s\S]*?)<\/div>/g, '$1\n'
location: ->
window.location.hash
foldByStart: (line) ->
@get('folds').find (fold) -> line.match(fold.get('startPattern'))
isFoldEnding: (fold, line) ->
line.match(fold.get('endPattern'))

View File

@ -0,0 +1,36 @@
store = null
record = null
describe 'Travis.Artifact', ->
beforeEach ->
store = Travis.Store.create()
afterEach ->
store.destroy()
describe 'with part of the body loaded', ->
beforeEach =>
store.load Travis.Artifact, 1, { id: 1, body: 'first\nsecond\n' }
record = store.find(Travis.Artifact, 1)
it 'packs the existing part of the body to parts', ->
expect( record.get('parts').toArray() ).toEqual( ['first\nsecond\n'] )
it 'adds new chunks of log to parts', ->
record.append('third\n')
expect( record.get('parts').toArray() ).toEqual( ['first\nsecond\n', 'third\n'] )
it 'properly handles array observers', ->
called = 0
observer = {
arrayDidChange: -> called += 1
arrayWillChange: -> called += 1
}
record.get('parts').addArrayObserver observer,
willChange: 'arrayWillChange'
didChange: 'arrayDidChange'
record.append('something')
expect(called).toEqual 2

View File

@ -0,0 +1,232 @@
log = null
target = null
describe 'Travis.Log', ->
beforeEach ->
target = Em.Object.create
calls: []
appendLog: (payloads) ->
lines = payloads.map (p) ->
line = p.content
delete p.content
line
@get('calls').pushObject
options: payloads
lines: lines
log = Travis.Log.create(target: target)
it 'works with log passed as a string', ->
log.append '1\n2'
expect( target.get('calls.firstObject.lines') ).toEqual ['1', '2']
it 'splits lines', ->
log.append ['1\r\n2\n\n', '3']
expect( target.get('calls.length') ).toEqual 1
expect( target.get('calls.firstObject.lines') ).toEqual ['1', '2', '', '3']
it 'escapes html characters', ->
log.append '<>'
expect( target.get('calls.firstObject.lines') ).toEqual ['&lt;&gt;']
it 'normalizes ansi mess', ->
log.append ['foo\r\r', 'bar']
expect( target.get('calls.firstObject.lines') ).toEqual [ 'foo', 'bar' ]
it 'calls target with folds separation', ->
fold = Em.Object.create name: 'foo', startPattern: /^\$ foo/, endPattern: /^\$/
log.addFold fold
fold = Em.Object.create name: 'qux', startPattern: /^\$ qux/, endPattern: /^\$/
log.addFold fold
log.append [
'1\n', '2\n'
'$ foo --foo\n', '1\n'
'$ bar\n'
'$ baz\n'
'$ qux\n', '1\n', '2\n'
'$ end\n'
]
# expect( target.get('calls.length') ).toEqual 5
lines = target.get('calls').map (call) -> call.lines
options = target.get('calls').map (call) -> call.options
expect( lines[0] ).toEqual ['1', '2']
expect( options[0]).toEqual [ { number: 1 }, { number: 2 } ]
expect( lines[1] ).toEqual ['$ foo --foo', '1']
expect( options[1]).toEqual [
{ number: 3, fold: 'foo' },
{ number: 4, fold: 'foo', foldContinuation: true, foldEnd: true }]
expect( lines[2] ).toEqual ['$ bar', '$ baz']
expect( options[2]).toEqual [{ number: 5 }, { number: 6 }]
expect( lines[3] ).toEqual ['$ qux', '1', '2']
expect( options[3]).toEqual [
{ number: 7, fold: 'qux' },
{ number: 8, fold: 'qux', foldContinuation: true },
{ number: 9, fold: 'qux', foldContinuation: true, foldEnd: true }]
expect( lines[4] ).toEqual ['$ end', '']
expect( options[4]).toEqual [{ number: 10 }, { number: 11 }]
it 'works properly when log is started with fold', ->
fold = Em.Object.create name: 'foo', startPattern: /^\$ foo/, endPattern: /^\$/
log.addFold fold
log.append [
'$ foo --foo\n', '1\n'
'$ bar\n'
]
expect( target.get('calls.length') ).toEqual 2
lines = target.get('calls').map (call) -> call.lines
options = target.get('calls').map (call) -> call.options
expect( lines[0] ).toEqual ['$ foo --foo', '1']
expect( options[0]).toEqual [
{ number: 1, fold: 'foo' },
{ number: 2, fold: 'foo', foldContinuation: true, foldEnd: true }]
expect( lines[1] ).toEqual ['$ bar', '']
expect( options[1]).toEqual [{ number: 3 }, { number: 4 }]
it 'works properly for 2 consecutive folds', ->
fold = Em.Object.create name: 'foo', startPattern: /^\$ foo/, endPattern: /^\$/
log.addFold fold
log.append [
'$ foo --foo\n', '1\n'
'$ foo --bar\n', '2\n'
'$ bar\n'
]
expect( target.get('calls.length') ).toEqual 3
lines = target.get('calls').map (call) -> call.lines
options = target.get('calls').map (call) -> call.options
expect( lines[0] ).toEqual ['$ foo --foo', '1']
expect( options[0]).toEqual [
{ number: 1, fold: 'foo' },
{ number: 2, fold: 'foo', foldContinuation: true, foldEnd: true }]
expect( lines[1] ).toEqual ['$ foo --bar', '2']
expect( options[1]).toEqual [
{ number: 3, fold: 'foo' },
{ number: 4, fold: 'foo', foldContinuation: true, foldEnd: true }]
expect( lines[2] ).toEqual ['$ bar', '']
expect( options[2]).toEqual [{ number: 5 }, { number: 6 }]
it 'works fine with not finalized fold', ->
fold = Em.Object.create name: 'foo', startPattern: /^\$ foo/, endPattern: /^\$/
log.addFold fold
log.append [
'$ foo --foo\n', '1\n'
]
expect( target.get('calls.length') ).toEqual 1
lines = target.get('calls').map (call) -> call.lines
options = target.get('calls').map (call) -> call.options
expect( lines[0] ).toEqual ['$ foo --foo', '1', '']
expect( options[0]).toEqual [
{ fold: 'foo', number: 1 },
{ fold: 'foo', number: 2, foldContinuation: true },
{ fold: 'foo', number: 3, foldContinuation: true }]
it 'allows to continue fold', ->
fold = Em.Object.create name: 'foo', startPattern: /^\$ foo/, endPattern: /^\$/
log.addFold fold
log.append [
'$ foo --foo\n', '1\n'
]
log.append '2\n'
log.append [
'3\n'
'$ bar\n'
]
expect( target.get('calls.length') ).toEqual 4
lines = target.get('calls').map (call) -> call.lines
options = target.get('calls').map (call) -> call.options
expect( lines[0] ).toEqual ['$ foo --foo', '1', '']
expect( options[0]).toEqual [
{ fold: 'foo', number: 1 },
{ fold: 'foo', number: 2, foldContinuation: true },
{ fold: 'foo', number: 3, foldContinuation: true }]
expect( lines[1] ).toEqual ['2', '']
expect( options[1]).toEqual [
{ fold: 'foo', number: 3, foldContinuation: true, append: true }
{ fold: 'foo', number: 4, foldContinuation: true }
]
expect( lines[2] ).toEqual ['3']
expect( options[2]).toEqual [
{ fold: 'foo', number: 4, foldContinuation: true, append: true, foldEnd: true }
]
expect( lines[3] ).toEqual ['$ bar', '']
expect( options[3]).toEqual [{ number: 5 }, { number: 6 }]
it 'notifies that the line should be appended', ->
log.append '$ foo\n.'
log.append '...'
log.append '..\n$ bar\n'
expect( target.get('calls.length') ).toEqual 3
lines = target.get('calls').map (call) -> call.lines
options = target.get('calls').map (call) -> call.options
expect( lines[0] ).toEqual ['$ foo', '.']
expect( options[0]).toEqual [{ number: 1 }, { number: 2 }]
expect( lines[1] ).toEqual ['...']
expect( options[1]).toEqual [{ append: true, number: 2 }]
expect( lines[2] ).toEqual ['..', '$ bar', '']
expect( options[2]).toEqual [{ append: true, number: 2 }, { number: 3 }, { number: 4 }]
it 'notifies that the line should be replaced', ->
log.append '$ foo\n'
log.append '\rDownloading 50%'
log.append '\rDownloading 100%\r\n'
log.append '$ bar\n'
expect( target.get('calls.length') ).toEqual 4
lines = target.get('calls').map (call) -> call.lines
options = target.get('calls').map (call) -> call.options
expect( lines[0] ).toEqual ['$ foo', '']
expect( options[0]).toEqual [{ number: 1 }, { number: 2 }]
expect( lines[1] ).toEqual ['', 'Downloading 50%']
expect( options[1]).toEqual [{ number: 2, append: true }, { number: 2, replace: true }]
expect( lines[2] ).toEqual ['', 'Downloading 100%', '']
expect( options[2]).toEqual [{ number: 2, append: true }, { number: 2, replace: true }, { number: 3 }]
expect( lines[3] ).toEqual ['$ bar', '']
expect( options[3]).toEqual [{ number: 3, append: true }, { number: 4 }]
it 'notifies that the line should be replaced even if carriage return is in the middle', ->

View File

@ -0,0 +1,74 @@
view = null
store = null
record = null
describe 'Travis.LogView', ->
beforeEach ->
store = Travis.Store.create()
afterEach ->
store.destroy()
view.remove()
view.destroy()
it 'works fine with existing log, which is appended', ->
store.load Travis.Artifact, 1, { id: 1, body: '$ start' }
log = Travis.Artifact.find(1)
Ember.run ->
view = Travis.LogView.create(context: null)
view.append()
expect( $('#log').length ).toEqual 1
console.log $('#log')
job = Ember.Object.create log: log, subscribe: (-> )
Ember.run ->
view.set 'context', job
log.set 'isLoaded', true
expect( view.$('#log p').length ).toEqual 1
expect( view.$('#log p').text().trim() ).toEqual '1$ start'
Ember.run ->
log.append('$ end')
expect( view.$('#log p').length ).toEqual 2
expect( view.$('#log p').text().trim() ).toEqual '1$ start2$ end'
it 'works fine with log already attahed to view', ->
store.load Travis.Artifact, 1, { id: 1, body: '$ start' }
log = Travis.Artifact.find(1)
job = Ember.Object.create log: log, subscribe: (-> )
Ember.run ->
view = Travis.LogView.create()
view.set('context', job)
view.append()
Ember.run ->
log.append('end')
expect( view.$('#log p').length ).toEqual 2
expect( view.$('#log p').text().trim() ).toEqual '1$ start2end'
it 'folds items', ->
store.load Travis.Artifact, 1, { id: 1, body: '$ start' }
log = Travis.Artifact.find(1)
job = Ember.Object.create log: log, subscribe: (-> )
Ember.run ->
view = Travis.LogView.create()
view.set('context', job)
view.append()
Ember.run ->
log.append '$ bundle install\n1\n2'
Ember.run ->
log.append '3\n4\n$ something'
expect( view.$('#log > p').length ).toEqual 2
expect( view.$('#log .fold.bundle').length ).toEqual 1
expect( view.$('#log .fold.bundle > p').length ).toEqual 5

View File

@ -7,7 +7,8 @@ ansiparse = function (str) {
matchingText = '',
ansiState = [],
result = [],
state = {};
state = {},
eraseChar;
//
// General workflow for this thing is:
@ -20,6 +21,29 @@ ansiparse = function (str) {
// In further steps we hope it's all going to be fine. It usually is.
//
//
// Erases a char from the output
//
eraseChar = function () {
var index, text;
if (matchingText.length) {
matchingText = matchingText.substr(0, matchingText.length - 1);
}
else if (result.length) {
index = result.length - 1;
text = result[index].text;
if (text.length === 1) {
//
// A result bit was fully deleted, pop it out to simplify the final output
//
result.pop();
}
else {
result[index].text = text.substr(0, text.length - 1);
}
}
};
for (var i = 0; i < str.length; i++) {
if (matchingControl != null) {
if (matchingControl == '\033' && str[i] == '\[') {
@ -111,7 +135,9 @@ ansiparse = function (str) {
if (str[i] == '\033') {
matchingControl = str[i];
}
else if (str[i] == '\u0008') {
eraseChar();
}
else {
matchingText += str[i];
@ -158,4 +184,3 @@ if (typeof module == "object" && typeof window == "undefined") {
module.exports = ansiparse;
}

View File

@ -58,4 +58,27 @@
padding: 4px
@include border-radius(3px)
#regenerate-key
.cancel
text-decoration: underline
p
text-align: center
.or
display: inline-block
margin: 20px 10px 0 10px
p:last-of-type
margin-bottom: 5px
a.button
font-size: 13px
#status-images, #regenerate-key
input
border: 1px solid $color-border-light
width: 505px
padding: 4px
@include border-radius(3px)
#regenerate-key, #regeneration-success
display: none
width: 400px
margin: -95px 0 0 -300px

View File

@ -39,6 +39,10 @@ pre#log
&.open
height: auto
background-image: inline-image('ui/log.fold.open.2.png')
&.show-first-line:not(.open)
height: 36px
p:not(:first-child):not(:last-child)
display: none
#log.loading
padding: 25px 0 0 10px

View File

@ -17,7 +17,7 @@
position: absolute
right: 0
top: -3px
width: 130px
width: 135px
background-color: $color-bg-menu
border: 1px solid $color-border-light
@include border-bottom-radius(4px)