Merge branch 'master' of github.com:travis-ci/travis-web
This commit is contained in:
commit
e62dc32d2a
|
@ -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
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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: ->
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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+.*?)>/, '#<$1>'
|
||||
|
||||
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'))
|
||||
|
|
36
assets/scripts/spec/unit/artifact_spec.coffee
Normal file
36
assets/scripts/spec/unit/artifact_spec.coffee
Normal 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
|
232
assets/scripts/spec/unit/log_spec.coffee
Normal file
232
assets/scripts/spec/unit/log_spec.coffee
Normal 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 ['<>']
|
||||
|
||||
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', ->
|
||||
|
74
assets/scripts/spec/unit/views/log_view_spec.coffee
Normal file
74
assets/scripts/spec/unit/views/log_view_spec.coffee
Normal 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
|
31
assets/scripts/vendor/ansiparse.js
vendored
31
assets/scripts/vendor/ansiparse.js
vendored
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user