shipit-engine 0.8.9 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/shipit_bs.js.coffee +2 -0
  3. data/app/assets/javascripts/task.js.coffee +14 -5
  4. data/app/assets/javascripts/task/search_bar.js.coffee +52 -0
  5. data/app/assets/javascripts/task/stream.js.coffee +9 -2
  6. data/app/assets/javascripts/task/tty.js.coffee +75 -28
  7. data/app/assets/stylesheets/_base/_forms.scss +5 -0
  8. data/app/assets/stylesheets/_pages/_commits.scss +1 -1
  9. data/app/assets/stylesheets/_pages/_deploy.scss +56 -24
  10. data/app/assets/stylesheets/_pages/_settings.scss +5 -0
  11. data/app/assets/stylesheets/_structure/_layout.scss +1 -0
  12. data/app/assets/stylesheets/_structure/_main.scss +4 -0
  13. data/app/assets/stylesheets/shipit.scss +2 -0
  14. data/app/assets/stylesheets/shipit_bs.scss +22 -0
  15. data/app/controllers/shipit/deploys_controller.rb +5 -1
  16. data/app/controllers/shipit/shipit_controller.rb +10 -3
  17. data/app/controllers/shipit/stacks_controller.rb +12 -3
  18. data/app/controllers/shipit/tasks_controller.rb +4 -0
  19. data/app/helpers/shipit/shipit_helper.rb +18 -0
  20. data/app/helpers/shipit/stacks_helper.rb +1 -1
  21. data/app/jobs/shipit/cache_deploy_spec_job.rb +2 -0
  22. data/app/jobs/shipit/fetch_deployed_revision_job.rb +1 -0
  23. data/app/jobs/shipit/git_mirror_update_job.rb +2 -0
  24. data/app/jobs/shipit/perform_task_job.rb +1 -0
  25. data/app/models/shipit/commit.rb +2 -2
  26. data/app/models/shipit/deploy.rb +1 -1
  27. data/app/models/shipit/deploy_spec/bundler_discovery.rb +1 -1
  28. data/app/models/shipit/duration.rb +28 -0
  29. data/app/models/shipit/stack.rb +33 -11
  30. data/app/models/shipit/task.rb +26 -3
  31. data/app/serializers/shipit/task_serializer.rb +14 -1
  32. data/app/views/bootstrap/shipit/missing_settings.html.erb +97 -0
  33. data/app/views/bootstrap/shipit/stacks/new.html.erb +44 -0
  34. data/app/views/layouts/shipit.html.erb +1 -1
  35. data/app/views/layouts/shipit_bootstrap.html.erb +44 -0
  36. data/app/views/shipit/commits/_commit.html.erb +3 -2
  37. data/app/views/shipit/deploys/_deploy.html.erb +11 -2
  38. data/app/views/shipit/deploys/show.html.erb +1 -1
  39. data/app/views/shipit/stacks/new.html.erb +12 -12
  40. data/app/views/shipit/stacks/settings.html.erb +5 -0
  41. data/app/views/shipit/stacks/show.html.erb +1 -1
  42. data/app/views/shipit/tasks/_task.html.erb +10 -2
  43. data/app/views/shipit/tasks/_task_output.html.erb +11 -1
  44. data/app/views/shipit/tasks/show.html.erb +1 -1
  45. data/config/routes.rb +1 -0
  46. data/db/migrate/20160324155046_add_started_at_and_ended_at_on_tasks.rb +25 -0
  47. data/lib/shipit.rb +13 -0
  48. data/lib/shipit/command.rb +13 -9
  49. data/lib/shipit/engine.rb +8 -0
  50. data/lib/shipit/template_renderer_extension.rb +16 -0
  51. data/lib/shipit/version.rb +1 -1
  52. data/test/controllers/deploys_controller_test.rb +11 -0
  53. data/test/controllers/stacks_controller_test.rb +5 -0
  54. data/test/controllers/tasks_controller_test.rb +6 -0
  55. data/test/dummy/config/secrets.example.yml +4 -0
  56. data/test/dummy/config/secrets.yml +2 -0
  57. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/bar.txt +2 -0
  58. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/dkfdsf +0 -0
  59. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/dskjfsd +0 -0
  60. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/dslkjfjsdf +0 -0
  61. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/plopfizz +0 -0
  62. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/sd +0 -0
  63. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/sdkfjsdf +1 -0
  64. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/sdlfjsdfdsfj +0 -0
  65. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/sdlkfjsdlkfjsdlkfjdsfsdfksdfjsldkfjsdlkfjsdf +0 -0
  66. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/shipit.yml +32 -0
  67. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/toto.txt +2 -0
  68. data/test/dummy/data/stacks/byroot/junk/production/git/bar.txt +1 -0
  69. data/test/dummy/data/stacks/byroot/junk/production/git/shipit.yml +6 -1
  70. data/test/dummy/data/stacks/byroot/test/production/git/README.md +1 -0
  71. data/test/dummy/db/development.sqlite3 +0 -0
  72. data/test/dummy/db/schema.rb +3 -1
  73. data/test/dummy/db/seeds.rb +6 -0
  74. data/test/dummy/db/test.sqlite3 +0 -0
  75. data/test/dummy/db/test.sqlite3-journal +0 -0
  76. data/test/fixtures/shipit/tasks.yml +11 -0
  77. data/test/models/commits_test.rb +1 -1
  78. data/test/models/deploys_test.rb +40 -0
  79. data/test/models/duration_test.rb +13 -0
  80. data/test/models/stacks_test.rb +3 -4
  81. data/test/unit/command_test.rb +14 -0
  82. data/vendor/assets/javascripts/clusterize.js +327 -0
  83. data/vendor/assets/javascripts/mousetrap-global-bind.js +43 -0
  84. data/vendor/assets/javascripts/mousetrap.js +1021 -0
  85. data/vendor/assets/javascripts/string_includes.js +14 -0
  86. data/vendor/assets/stylesheets/clusterize.css +27 -0
  87. metadata +100 -3
  88. data/app/assets/javascripts/task/sticky_element.js.coffee +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e8a76aee01307d253747b6e49c6c7be3bb4c3cf2
4
- data.tar.gz: 511372e610c0dfcdad6038607733841ee2752d0b
3
+ metadata.gz: 22eb132c6bbb6e9af2fdfca03481022aa47ccbeb
4
+ data.tar.gz: 6415095a2772724f3b2ad364411ad3d5956039f4
5
5
  SHA512:
6
- metadata.gz: ed8a88d84aa2f2d6e845fadad57c9aa39ebb78986f60ccd351a502bc6f5753c9a17e99bb7e3baf2c5b9e9c6fe716d3358c65bf704c7dbf4bffed69ae5c8595f3
7
- data.tar.gz: d9bf4e7c3550d38705f5482971f84177eb64cddc42248e42c5197009d6fa12bbdc0914360c722fa7298b4d596e7e8a0c44f00d3af8ba2c2f4cbb0c119e222ae9
6
+ metadata.gz: 2d5f6b17d2f1b9f4a52b4ed208524dd13f4ddddd8d178e0235eaeecd318c872ee29b480b87f5f905bfc9990274e82e1addca61feefa98aa373b09f03d245176a
7
+ data.tar.gz: d17ad59449f3a8a801689d991ff4092efe67c6c88ea83f51de1c70257fdf3c919ea0181d1d206099fea76550824602c65b85432cdf88e879c770d99954a5a709
@@ -0,0 +1,2 @@
1
+ #= require jquery
2
+ #= require bootstrap-sprockets
@@ -1,9 +1,20 @@
1
+ #= require string_includes
2
+ #= require mousetrap
3
+ #= require mousetrap-global-bind
4
+ #= require lodash
5
+ #= require clusterize
1
6
  #= require_tree ./task
2
7
  #= require_self
3
8
 
4
9
  @OutputStream = new Stream
5
10
 
6
11
  jQuery ->
12
+ $code = $('code')
13
+ initialOutput = $code.attr('data-output')
14
+ $code.removeAttr('data-output')
15
+
16
+ search = new SearchBar($('.search-bar'))
17
+
7
18
  OutputStream.addEventListener 'status', (status, response) ->
8
19
  $('[data-status]').attr('data-status', status)
9
20
 
@@ -11,16 +22,14 @@ jQuery ->
11
22
  window.location = response.rollback_url
12
23
 
13
24
  tty = new TTY($('body'))
25
+ search.addEventListener('query', tty.filterOutput)
26
+ search.immediateBroadcastQueryChange()
14
27
  OutputStream.addEventListener('chunk', tty.appendChunk)
15
28
 
16
29
  if task = $('[data-task]').data('task')
17
30
  Notifications.init(OutputStream, task)
18
31
 
19
- $code = $('code')
20
32
  OutputStream.init
21
33
  status: $code.closest('[data-status]').data('status')
22
34
  url: $code.data('next-chunks-url')
23
- text: tty.popInitialOutput()
24
-
25
- StickyElement.init('.deploy-banner')
26
- StickyElement.init('.sidebar')
35
+ text: initialOutput
@@ -0,0 +1,52 @@
1
+ class @SearchBar
2
+ DEBOUNCE = 300
3
+
4
+ constructor: (@$bar) ->
5
+ @eventListeners = {}
6
+ @query = window.location.hash.replace(/^#/, '')
7
+ @$input = @$bar.find('.search-input')
8
+ @$input.on('blur', @closeIfEmpty)
9
+ @$input.on('input', @updateQuery)
10
+ @broadcastQueryChange = _.debounce(@immediateBroadcastQueryChange, DEBOUNCE)
11
+ Mousetrap.bindGlobal(['command+f', 'ctrl+f'], @open)
12
+
13
+ if @query
14
+ @open()
15
+ @setQuery(@query)
16
+
17
+ addEventListener: (type, handler) ->
18
+ @listeners(type).push(handler)
19
+
20
+ listeners: (type) ->
21
+ @eventListeners[type] ||= []
22
+
23
+ setQuery: (query) ->
24
+ @$input.val(query)
25
+ @updateQuery()
26
+
27
+ updateQuery: =>
28
+ oldQuery = @query
29
+ @query = @$input.val()
30
+ @broadcastQueryChange() unless @query == oldQuery
31
+
32
+ immediateBroadcastQueryChange: =>
33
+ @updateHash()
34
+ for handler in @listeners('query')
35
+ handler(@query)
36
+
37
+ updateHash: ->
38
+ window.location.hash = "##{@query}"
39
+
40
+ open: (event) =>
41
+ event?.preventDefault()
42
+ @$bar.removeClass('hidden')
43
+ @focus()
44
+
45
+ focus: ->
46
+ @$input.focus()[0].select()
47
+
48
+ closeIfEmpty: (event) =>
49
+ @close() unless @query.length
50
+
51
+ close: ->
52
+ @$bar.addClass('hidden')
@@ -8,10 +8,15 @@ class Chunk
8
8
  @_text ||= AnsiStream.strip(@raw)
9
9
 
10
10
  rawLines: ->
11
- @_rawLines ||= @raw.split(/\r?\n/)
11
+ @_rawLines ||= @splitLines(@raw)
12
12
 
13
13
  lines: ->
14
- @_lines ||= @text().split(/\r?\n/)
14
+ @_lines ||= @splitLines(@text())
15
+
16
+ splitLines: (text) ->
17
+ lines = text.split(/\r?\n/)
18
+ lines.pop() unless lines[lines.length - 1]
19
+ lines
15
20
 
16
21
  class @Stream
17
22
  INTERVAL = 1000
@@ -49,6 +54,8 @@ class @Stream
49
54
  console?.log("Plugin error: #{error}")
50
55
 
51
56
  broadcastOutput: (raw, args...) ->
57
+ return unless raw
58
+
52
59
  chunk = new Chunk(raw)
53
60
  for handler in @listeners('chunk')
54
61
  try
@@ -1,3 +1,47 @@
1
+ class OutputLines
2
+ constructor: (@screen, @render) ->
3
+ @query = ''
4
+ @highlightRegexp = null
5
+ @raw = []
6
+ @renderingCache = {}
7
+ @stripCache = {}
8
+
9
+ setFilter: (query) ->
10
+ if @query = query
11
+ @screen.options.no_data_text = 'No matches'
12
+ else
13
+ @screen.options.no_data_text = 'Loading...'
14
+ @highlightRegexp = @buildHighlightRegexp(@query)
15
+ @reset()
16
+
17
+ reset: ->
18
+ @screen.update(@renderLines(@filter(@raw)))
19
+
20
+ filter: (lines) ->
21
+ return lines unless @query
22
+ line for line in lines when @strip(line).includes(@query)
23
+
24
+ strip: (line) ->
25
+ @stripCache[line] ||= AnsiStream.strip(line)
26
+
27
+ append: (lines) ->
28
+ @raw = @raw.concat(lines)
29
+ @screen.append(@renderLines(@filter(lines)))
30
+
31
+ renderLines: (lines) ->
32
+ for line in lines
33
+ @highlight(@renderingCache[line] ||= @render(line))
34
+
35
+ buildHighlightRegexp: (query) ->
36
+ pattern = query.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/(\s+)/g, '(<[^>]+>)*$1(<[^>]+>)*')
37
+ new RegExp("(#{pattern})", 'g')
38
+
39
+ highlight: (renderedLine) ->
40
+ return renderedLine unless @query
41
+
42
+ renderedLine.replace(@highlightRegexp, '<mark>$1</mark>').replace(/(<mark>[^<>]*)((<[^>]+>)+)([^<>]*<\/mark>)/, '$1</mark>$2<mark>$4');
43
+
44
+
1
45
  class @TTY
2
46
  FORMATTERS = []
3
47
  STICKY_SCROLL_TOLERENCE = 200
@@ -9,13 +53,19 @@ class @TTY
9
53
  FORMATTERS.unshift(formatter)
10
54
 
11
55
  constructor: ($body) ->
56
+ @outputLines = []
12
57
  @$code = $body.find('code')
13
- @scrolling = new Scrolling(@$code)
58
+ @$container = @$code.closest('.clusterize-scroll')
59
+ scroller = new Clusterize(
60
+ no_data_text: 'Loading...'
61
+ tag: 'div'
62
+ contentElem: @$code[0]
63
+ scrollElem: @$container[0]
64
+ )
65
+ @output = new OutputLines(scroller, (line) => @createLine(@formatChunks(line)))
14
66
 
15
- popInitialOutput: ->
16
- output = @$code.text()
17
- @$code.empty()
18
- output
67
+ filterOutput: (query) =>
68
+ @output.setFilter(query)
19
69
 
20
70
  formatChunks: (chunk) ->
21
71
  for formatter in FORMATTERS
@@ -23,32 +73,29 @@ class @TTY
23
73
  chunk
24
74
 
25
75
  appendChunk: (chunk) =>
26
- @scrolling.preserve =>
27
- @$code.append(@formatChunks(chunk.raw))
28
-
29
- class Scrolling
30
- TOLERENCE = 200
76
+ lines = chunk.rawLines()
77
+ return unless lines.length
31
78
 
32
- constructor: (@$code) ->
33
- @$window = $(window)
34
- @initialScroll = true
79
+ @preserveScroll =>
80
+ @output.append(lines)
35
81
 
36
- preserve: (callback) ->
37
- wasScrolledToBottom = @isScrolledToBottom()
38
- callback()
39
- if wasScrolledToBottom
40
- @$window.scrollTop(@codeBottomPosition() - @$window.height() + 50)
82
+ createLine: (fragment) ->
83
+ div = document.createElement('div')
84
+ div.appendChild(fragment)
85
+ div.className = 'output-line'
86
+ div.outerHTML
41
87
 
42
88
  isScrolledToBottom: ->
43
- if @initialScroll
44
- @initialScroll = (window.pageYOffset == 0)
45
- true
46
- else
47
- codeBottom = @codeBottomPosition()
48
- codeBottom + TOLERENCE > @viewportBottomPosition() >= codeBottom - TOLERENCE
89
+ (@getMaxScroll() - @$container.scrollTop()) < 1
90
+
91
+ scrollToBottom: ->
92
+ @$container.scrollTop(@getMaxScroll())
93
+
94
+ getMaxScroll: ->
95
+ @$code.parent().outerHeight(true) - @$container.outerHeight(true)
49
96
 
50
- viewportBottomPosition: ->
51
- window.pageYOffset + @$window.height()
97
+ preserveScroll: (callback) ->
98
+ wasScrolledToBottom = @isScrolledToBottom()
99
+ callback()
100
+ @scrollToBottom() if wasScrolledToBottom
52
101
 
53
- codeBottomPosition: ->
54
- @$code.position().top + @$code.height()
@@ -27,3 +27,8 @@ textarea {
27
27
  .field-wrapper {
28
28
  margin-bottom: 1.5rem;
29
29
  }
30
+
31
+ .field_with_errors {
32
+ border: 1px solid $red;
33
+ display: inline-block;
34
+ }
@@ -12,7 +12,7 @@
12
12
  }
13
13
 
14
14
  .commit, .task {
15
- padding: 1rem 0;
15
+ padding: .75rem 0;
16
16
  display: flex;
17
17
  flex-direction: column;
18
18
 
@@ -54,17 +54,27 @@
54
54
  }
55
55
  }
56
56
 
57
+ .task-output-container {
58
+ height: calc(100vh - 9rem - 4rem - 1px); // .header and .deploy-banner. -1px is to floor the result
59
+ }
60
+
61
+ .output-line {
62
+ height: 1.5rem;
63
+ }
64
+
57
65
  .deploy-banner {
66
+ height: 4rem;
58
67
  background-color: #f0f4f7;
59
68
  display: flex;
60
69
  justify-content: center;
61
70
  align-items: center;
62
71
  position: relative;
63
72
  flex-wrap: wrap;
73
+ overflow-x: hidden;
64
74
 
65
75
  .deploy-banner-section {
66
76
  display: inline-block;
67
- padding: .95rem 1.5rem 1.125rem;
77
+ padding: .75rem 1.5rem;
68
78
  }
69
79
 
70
80
  .stack-link {
@@ -75,26 +85,44 @@
75
85
  flex: none;
76
86
  }
77
87
 
78
- &.sticky {
79
- position: fixed;
80
- top: 0;
88
+ .deploy-banner-status {
89
+ height: 2px;
90
+ position: absolute;
91
+ bottom: 0;
81
92
  left: 0;
82
- right: 0;
93
+ }
83
94
 
84
- .stack-link {
85
- display: inline-block;
95
+ &[data-status="failure"],
96
+ &[data-status="error"] {
97
+ .deploy-banner-status {
98
+ background-color: $bright-red;
99
+ width: 100%;
100
+ }
101
+ }
102
+
103
+ &[data-status="aborted"],
104
+ &[data-status="flapping"] {
105
+ .deploy-banner-status {
106
+ background-color: $orange;
107
+ width: 100%;
86
108
  }
87
109
  }
88
110
 
89
- &[data-status="running"]:before,
90
- &[data-status="aborting"]:before {
91
- content: "";
111
+ &[data-status="success"] .deploy-banner-status {
112
+ background-color: $green;
113
+ width: 100%;
114
+ }
115
+
116
+ &[data-status="running"] .deploy-banner-status {
92
117
  background-color: $blue;
93
- bottom: 0;
94
- left: 0;
95
118
  width: 0%;
96
- height: 2px;
97
- position: absolute;
119
+ z-index: 9999;
120
+ -webkit-animation: loading-slide 1.2s linear infinite;
121
+ }
122
+
123
+ &[data-status="aborting"] .deploy-banner-status {
124
+ background-color: $orange;
125
+ width: 0%;
98
126
  z-index: 9999;
99
127
  -webkit-animation: loading-slide 1.2s linear infinite;
100
128
  }
@@ -112,6 +140,19 @@
112
140
  }
113
141
  }
114
142
 
143
+ .search-bar {
144
+ background-color: #f0f4f7;
145
+ border: 1px solid #f0f4f7;
146
+ position: absolute;
147
+ right: 0px;
148
+ padding: .125rem;
149
+ border-bottom-left-radius: .25rem;
150
+
151
+ input[type="search"] {
152
+ width: 300px;
153
+ }
154
+ }
155
+
115
156
  @include keyframes(loading-slide) {
116
157
  0% { width: 0%; left: 0%; }
117
158
  30% { left: 0%; }
@@ -124,27 +165,18 @@
124
165
 
125
166
  .sidebar.enabled + .deploy-main {
126
167
  margin-left: 300px;
127
-
128
- .deploy-banner.sticky {
129
- box-sizing: border-box;
130
- margin-left: 300px;
131
- }
132
168
  }
133
169
 
134
170
  .sidebar {
135
171
  background-color: $slate;
136
172
  color: white;
137
173
  overflow: hidden;
138
- height: 100%;
174
+ height: calc(100vh - 9rem - 1px); // .header. -1px is to floor the result
139
175
  position: absolute;
140
176
  width: 0px;
141
177
  &.enabled {
142
178
  width: 300px;
143
179
  }
144
- &.sticky {
145
- top: 0;
146
- position: fixed;
147
- }
148
180
  }
149
181
 
150
182
  .sidebar-plugins {
@@ -8,4 +8,9 @@
8
8
  & + & {
9
9
  border-top: 1px solid #e5e5e5;
10
10
  }
11
+
12
+ .form-hint {
13
+ font-size: smaller;
14
+ font-style: italic;
15
+ }
11
16
  }
@@ -14,6 +14,7 @@
14
14
  // -----------------------------------------------------------------------------
15
15
 
16
16
  .header {
17
+ height: 9rem;
17
18
  border-bottom: 1px solid #e5e5e5;
18
19
  background-color: #fff;
19
20
  color: #999;
@@ -1,6 +1,9 @@
1
1
  .main {
2
2
  @include flex(1);
3
3
  padding-bottom: 3rem;
4
+ &.no-footer {
5
+ padding-bottom: 0;
6
+ }
4
7
  }
5
8
 
6
9
  section {
@@ -47,6 +50,7 @@
47
50
 
48
51
  pre {
49
52
  background-color: $terminal-black;
53
+ min-height: 100%;
50
54
  overflow-x: auto;
51
55
  color: #fff;
52
56
  padding: 1.5rem;