etsy-deployinator 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ <div id="maintenance">
2
+ <h1> Deployinator is in maintenance mode.</h1>
3
+ <img src="/images/maintenance.gif"></img>
4
+ <h1> We'll be back shortly. But please see {{ maintenance_contact }} for more details. </h1>
5
+ </div>
@@ -0,0 +1,180 @@
1
+ <!-- content -->
2
+ <section id="main" class="info stats">
3
+ <!-- heading -->
4
+ <div class="heading clearfix">
5
+ <h2>Deployments Per Day (US/Eastern)</h2>
6
+ </div>
7
+ <div class="stats-main">
8
+ <button id="scatterOrLine">Line</button>
9
+ <label><input type="checkbox" id="combined"> Combined</label>
10
+ <div id="placeholder" style="width:940px;height:300px;"></div>
11
+ <div id="overview" style="width:940px;height:120px;"></div>
12
+ <script>var data = {}</script>
13
+
14
+ <p id="choices"></p>
15
+
16
+ <div class="holder">
17
+
18
+ {{# per_day}}
19
+ <div class="stats">
20
+ <table class="stats">
21
+ <tr>
22
+ <th>Day</th>
23
+ <th>Count</th>
24
+ </tr>
25
+ {{# data}}
26
+ <tr>
27
+ <td>{{date}}</td>
28
+ <td class="count">{{count}}</td>
29
+ </tr>
30
+ {{/ data}}
31
+ </table>
32
+ <h3>{{stack}}</h3>
33
+ <script type="text/javascript" charset="utf-8">
34
+ data["{{stack}}"] = {data: {{json}}, label: "{{stack}}"}
35
+ </script>
36
+ </div>
37
+ {{/ per_day}}
38
+
39
+ </div>
40
+ </div>
41
+
42
+ </section>
43
+
44
+ <script type="text/javascript" charset="utf-8">
45
+ var x1, x2;
46
+
47
+ function getData(ignoreX) {
48
+ var d = [];
49
+ var c = { data: [], label: "combined" };
50
+
51
+ $("#choices").find("input:checked").each(function() {
52
+ if ($(this).attr("id") == "combined") { return; }
53
+
54
+ var key = $(this).attr("name");
55
+
56
+ if (key && data[key]) {
57
+ // combine all the data points if requested
58
+ if ($("#combined")[0].checked) {
59
+ for (var i=0; i<data[key].data.length; i++) {
60
+ var found = false;
61
+ var m = data[key].data[i];
62
+ for (var j=0; j<c.data.length; j++) {
63
+ var n = c.data[j];
64
+ if (n[0] == m[0]) {
65
+ n[1] += m[1];
66
+ found = true;
67
+ break;
68
+ }
69
+ }
70
+
71
+ if (! found) { c.data[i] = [m[0], m[1]]; }
72
+ }
73
+ }
74
+
75
+ d.push(data[key]);
76
+ }
77
+ });
78
+
79
+ if ($("#combined")[0].checked) { d = [c]; }
80
+
81
+ var newD = [];
82
+ for (var i=0; i<d.length; i++) {
83
+ var dAr = [];
84
+ for (var j=0; j<d[i].data.length; j++) {
85
+ n = d[i].data[j];
86
+ dAr.push(n);
87
+ }
88
+ newD[i] = { label: d[i].label, data: dAr };
89
+ }
90
+
91
+ if ($("#combined")[0].checked) { outputTable(c); }
92
+ return newD;
93
+ }
94
+
95
+ function outputTable(c) {
96
+ $("#holderCombined").remove();
97
+
98
+ var table = $("<table id='holderCombined'>")
99
+ .html("<tr><th>Date</th><th>Combined</th></tr>")
100
+ .appendTo(".stats-main");
101
+
102
+ for (var i=0; i<c.data.length; i++) {
103
+ var m = c.data[i];
104
+ var dO = new Date(m[0]);
105
+ var dateS = (dO.getYear() + 1900) + "-" + dO.getMonth() + "-" + dO.getDate();
106
+ $("<tr><td>" + dateS + "</td><td>" + m[1] + "</td></tr>")
107
+ .appendTo(table);
108
+ }
109
+ }
110
+
111
+ function options() {
112
+ return {
113
+ xaxis: { mode: "time", timeformat: "%y/%m/%d", minTickSize: [1, "day"] },
114
+ legend: { position: "nw" },
115
+ points: { show: ($("#scatterOrLine").html() == "Scatter") },
116
+ lines: { show: ($("#scatterOrLine").html() == "Line") },
117
+ selection: { mode: "x" }
118
+ }
119
+ };
120
+
121
+ function sliceD(d, msg) {
122
+ return d.slice(d.slice(d.indexOf(x1), d.indexOf(x2)));
123
+ }
124
+
125
+ function drawGraphs() {
126
+ var d = getData();
127
+
128
+ var plot = $.plot($("#placeholder"), sliceD(d, "main"), options());
129
+
130
+ var overview = $.plot($("#overview"), d, {
131
+ legend: { show: false },
132
+ selection: { mode: "x" },
133
+ lines: { show: true },
134
+ xaxis: { mode: "time", timeformat: "%y/%m/%d" }
135
+ });
136
+
137
+ $("#placeholder").unbind("plotselected");
138
+ $("#placeholder").bind("plotselected", function (event, cRanges) {
139
+ ranges = cRanges;
140
+ x1 = Math.floor(ranges.xaxis.from);
141
+ x2 = Math.floor(ranges.xaxis.to);
142
+
143
+ // do the zooming
144
+ plot = $.plot($("#placeholder"), sliceD(d, "zoom"),
145
+ $.extend(true, {}, options(), {
146
+ xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to },
147
+ yaxis: { min: ranges.yaxis.from, max: ranges.yaxis.to }
148
+ }));
149
+
150
+ // don't fire event on the overview to prevent eternal loop
151
+ overview.setSelection(ranges, true);
152
+ });
153
+
154
+ $("#overview").unbind("plotselected");
155
+ $("#overview").bind("plotselected", function (event, ranges) {
156
+ plot.setSelection(ranges);
157
+
158
+ x1 = Math.floor(ranges.xaxis.from);
159
+ x2 = Math.floor(ranges.xaxis.to);
160
+ });
161
+
162
+ if (x1 && x2) {
163
+ ranges = {xaxis: {from: x1, to: x2}};
164
+
165
+ overview.setSelection(ranges);
166
+ plot.setSelection(ranges);
167
+ }
168
+ }
169
+
170
+ $("#scatterOrLine").live("click", function () {
171
+ $(this).html(($(this).html() == "Line") ? "Scatter" : "Line");
172
+
173
+ drawGraphs();
174
+ });
175
+
176
+ $("#combined").live("click", function () {
177
+ drawGraphs();
178
+ });
179
+ </script>
180
+ <script src="/js/stats_load.js"></script>
@@ -1,3 +1,3 @@
1
1
  module Deployinator
2
- VERSION = "1.0.2"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -17,6 +17,42 @@ module Deployinator::Views
17
17
  @params[:show_counts] == "true"
18
18
  end
19
19
 
20
+ def next_page_params
21
+ params = [
22
+ {
23
+ :name => "page",
24
+ :value => next_page
25
+ }
26
+ ]
27
+
28
+ unless @params[:stack].nil?
29
+ params << {
30
+ :name => "stack",
31
+ :value => @params[:stack]
32
+ }
33
+ end
34
+
35
+ return params
36
+ end
37
+
38
+ def prev_page_params
39
+ params = [
40
+ {
41
+ :name => "page",
42
+ :value => prev_page
43
+ }
44
+ ]
45
+
46
+ unless @params[:stack].nil?
47
+ params << {
48
+ :name => "stack",
49
+ :value => @params[:stack]
50
+ }
51
+ end
52
+
53
+ return params
54
+ end
55
+
20
56
  def prev_page
21
57
  return unless @params && @params[:page]
22
58
  page = @params[:page].to_i
@@ -0,0 +1,15 @@
1
+ module Deployinator::Views
2
+ class Maintenance < Layout
3
+ self.template_file = "#{File.dirname(__FILE__)}/../templates/maintenance.mustache"
4
+ def additional_header_html
5
+ <<-EOS
6
+ #{super}
7
+ <link rel="stylesheet" href="/css/maintenance.css" type="text/css" media="screen">
8
+ EOS
9
+ end
10
+
11
+ def maintenance_contact
12
+ Deployinator.maintenance_contact
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,96 @@
1
+ require 'json'
2
+ require 'time'
3
+
4
+ module Deployinator::Views
5
+ class Stats < Layout
6
+
7
+ self.template_file = "#{File.dirname(__FILE__)}/../templates/stats.mustache"
8
+
9
+ @@ignored_stacks = Deployinator.stats_ignored_stacks || []
10
+
11
+ def deploys
12
+ @deploys ||= begin
13
+ log_to_hash({
14
+ :no_global => false,
15
+ :stack => Deployinator.stats_included_stacks,
16
+ :env => "production|search|prod",
17
+ :extragrep => Deployinator.stats_extra_grep,
18
+ :no_limit => true,
19
+ :limit => 10000
20
+ })
21
+ end
22
+ # note that the stack param will help but will send bring back extra lines that matched
23
+ end
24
+
25
+ def timings
26
+ deploys
27
+ end
28
+
29
+ def inject_renamed_stacks(renamed_stacks)
30
+ return if nil == renamed_stacks
31
+
32
+ renamed_stacks.each do |ops|
33
+ previous_stack = ops[:previous_stack]
34
+ new_stack_name = ops[:new_name]
35
+
36
+ renamed_stack_data = log_to_hash(previous_stack)
37
+
38
+ renamed_stack_data.each do |data|
39
+ data[:stack] = new_stack_name
40
+ deploys.push(data)
41
+ end
42
+ end
43
+ end
44
+
45
+ def per_day
46
+ inject_renamed_stacks(Deployinator.stats_renamed_stacks)
47
+
48
+ original_zone = ENV["TZ"]
49
+ ENV["TZ"] = "US/Eastern"
50
+
51
+ early_day = Time.now.strftime("%Y-%m-%d")
52
+ stack_days = deploys.inject({}) do |h, deploy|
53
+ if deploy[:time] && deploy[:stack]
54
+ if @@ignored_stacks.include?(deploy[:stack])
55
+ # puts "SKIPPING " + deploy[:stack]
56
+ # something breaks if you just next here so don't
57
+ else
58
+ day = Date.parse(deploy[:time].localtime.strftime("%Y-%m-%d"))
59
+ early_day = day if day.to_s < early_day.to_s
60
+ h[deploy[:stack]] ||= {}
61
+ h[deploy[:stack]][day] ||= 0
62
+ h[deploy[:stack]][day] += 1
63
+ end
64
+ end
65
+ h
66
+ end
67
+
68
+ # fill in zero days
69
+ day_seconds = 24 * 60 * 60
70
+ (0..((Time.now - Time.parse(early_day.to_s)) / day_seconds).to_i).each do |days_ago|
71
+ stack_days.keys.each do |stack|
72
+
73
+ next if @@ignored_stacks.include?(stack)
74
+ day = Date.parse((Time.now - (day_seconds * days_ago)).strftime("%Y-%m-%d"))
75
+ stack_days[stack][day] ||= 0
76
+ end
77
+ end
78
+
79
+ n = stack_days.keys.map do |stack|
80
+ next if @@ignored_stacks.include?(stack)
81
+
82
+ data = []
83
+ json = []
84
+ stack_days[stack].sort.reverse.each do |d,c|
85
+ data << {:date => d, :count => c}
86
+ json << [d.strftime("%s").to_i * 1000, c]
87
+ end
88
+ {:stack => stack, :data => data, :json => json.to_json}
89
+ end
90
+
91
+ ENV["TZ"] = original_zone
92
+
93
+ n
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,72 @@
1
+ require 'deployinator'
2
+ require 'deployinator/helpers'
3
+ require 'deployinator/helpers/concurrency'
4
+ require 'test/unit'
5
+ require 'mocha/setup'
6
+
7
+ Celluloid.logger = nil
8
+ class ConcurrencyTest < Test::Unit::TestCase
9
+ # Celluloid recommends doing this before each test
10
+ # https://github.com/celluloid/celluloid/wiki/Gotchas#testing
11
+ include Deployinator::Helpers::ConcurrencyHelpers
12
+ def setup
13
+ Celluloid.boot
14
+ @@futures = {}
15
+ end
16
+
17
+ def teardown
18
+ Celluloid.shutdown
19
+ end
20
+
21
+ def test_spawn_off_concurrent_thread
22
+ run_parallel(:test) do
23
+ "Running inside thread"
24
+ end
25
+ assert_equal(get_value(:test), "Running inside thread")
26
+ end
27
+
28
+ def test_future_name_type
29
+ assert_raise NoMethodError do
30
+ run_parallel({:test => 'going crazy'}) do
31
+ "Running inside thread"
32
+ end
33
+ end
34
+ end
35
+
36
+ def test_fibers_with_same_reference
37
+ run_parallel(:test) do
38
+ "Running inside thread"
39
+ end
40
+ assert_raise Deployinator::Helpers::ConcurrencyHelpers::DuplicateReferenceError do
41
+ run_parallel(:test) do
42
+ "Running inside thread"
43
+ end
44
+ end
45
+ end
46
+
47
+ def test_get_non_symbol_value
48
+ run_parallel(:test) do
49
+ "Running inside thread"
50
+ end
51
+ assert_raise NoMethodError do
52
+ get_value({:test => 'going crazy - jpaul'})
53
+ end
54
+ end
55
+
56
+ def test_reference_taken
57
+ assert_equal(reference_taken?(:test), false)
58
+ @@futures[:test] = 'a value'
59
+ assert_equal(reference_taken?(:test), true)
60
+ end
61
+
62
+ def test_multiple_futures_return
63
+ run_parallel(:test1) do
64
+ "future1"
65
+ end
66
+ run_parallel(:test2) do
67
+ "future2"
68
+ end
69
+ assert_equal(get_values(:test1, :test2), {:test1 => "future1", :test2 => "future2"})
70
+ end
71
+
72
+ end
@@ -0,0 +1,70 @@
1
+ require 'deployinator'
2
+ require 'deployinator/helpers'
3
+ require 'deployinator/helpers/git'
4
+
5
+ include Deployinator::Helpers::GitHelpers
6
+
7
+ class HelpersTest < Test::Unit::TestCase
8
+
9
+ def test_git_url_https
10
+ GitHelpers.expects(:which_github_host).returns("www.testmagic.com")
11
+ GitHelpers.expects(:git_info_for_stack).returns({:stack => {:repository => 'drills', :user => 'construction'}})
12
+ GitHelpers.expects(:git_info_for_stack).returns({:stack => {:repository => 'drills', :user => 'construction'}})
13
+ assert_equal('https://www.testmagic.com/construction/drills.git', GitHelpers.git_url(:stack, 'https', false))
14
+ end
15
+
16
+ def test_git_url_default
17
+ GitHelpers.expects(:which_github_host).returns("www.testmagic.com")
18
+ GitHelpers.expects(:git_info_for_stack).returns({:stack => {:repository => 'drills', :user => 'construction'}})
19
+ GitHelpers.expects(:git_info_for_stack).returns({:stack => {:repository => 'drills', :user => 'construction'}})
20
+ assert_equal('git://www.testmagic.com/construction/drills', GitHelpers.git_url(:stack))
21
+ end
22
+
23
+ def test_git_url_read_write
24
+ GitHelpers.expects(:which_github_host).returns("www.testmagic.com")
25
+ GitHelpers.expects(:git_info_for_stack).returns({:stack => {:repository => 'drills', :user => 'construction'}})
26
+ GitHelpers.expects(:git_info_for_stack).returns({:stack => {:repository => 'drills', :user => 'construction'}})
27
+ assert_equal('git@www.testmagic.com:construction/drills', GitHelpers.git_url(:stack, "git", true))
28
+ end
29
+
30
+ def test_git_freshen_or_clone_https_passthrough
31
+ GitHelpers.expects(:git_checkout_path).returns("/dev/null")
32
+ GitHelpers.expects(:is_git_repo).with("/dev/null", "extra-ssh").returns(:missing)
33
+ GitHelpers.expects(:log_and_stream).returns(nil)
34
+ GitHelpers.expects(:git_url).with(:stack, "https", false).returns("https://www.testmagic.com/construction/drills.git")
35
+ GitHelpers.expects(:git_clone).with(:stack, "https://www.testmagic.com/construction/drills.git", "extra-ssh", "/dev/null", "merge99")
36
+ GitHelpers.git_freshen_or_clone(:stack, "extra-ssh", "/dev/null", "merge99", false, "https")
37
+ end
38
+
39
+ def test_git_freshen_or_clone_git_update
40
+ GitHelpers.expects(:git_checkout_path).returns("/dev/null")
41
+ GitHelpers.expects(:is_git_repo).with("/dev/null", "extra-ssh").returns(:true)
42
+ GitHelpers.expects(:log_and_stream).returns(nil)
43
+ GitHelpers.expects(:git_freshen_clone).with(:stack, "extra-ssh", "/dev/null", "merge99", false)
44
+ GitHelpers.git_freshen_or_clone(:stack, "extra-ssh", "/dev/null", "merge99", false, "https")
45
+ end
46
+
47
+ def test_git_freshen_or_clone_git_update_force
48
+ GitHelpers.expects(:git_checkout_path).returns("/dev/null")
49
+ GitHelpers.expects(:is_git_repo).with("/dev/null", "extra-ssh").returns(:true)
50
+ GitHelpers.expects(:log_and_stream).returns(nil)
51
+ GitHelpers.expects(:git_freshen_clone).with(:stack, "extra-ssh", "/dev/null", "merge99", true)
52
+ GitHelpers.git_freshen_or_clone(:stack, "extra-ssh", "/dev/null", "merge99", false, "https", true)
53
+ end
54
+ def test_git_freshen_or_clone_git_bad_repo
55
+ GitHelpers.expects(:git_checkout_path).returns("/dev/null")
56
+ GitHelpers.expects(:is_git_repo).with("/dev/null", "extra-ssh").returns(:false)
57
+ GitHelpers.expects(:log_and_stream).returns(nil)
58
+ GitHelpers.git_freshen_or_clone(:stack, "extra-ssh", "/dev/null", "merge99", false, "https")
59
+ end
60
+
61
+ def test_git_head_rev_should_cache_results
62
+ FileUtils.rm_f('/tmp/rev_head_cache_some_stack')
63
+
64
+ head_rev_sha = 'ba83f60523008e48950f77bd0d3a773f9cb2805c'
65
+ GitHelpers.expects(:get_git_head_rev).with('some_stack', 'master').returns(head_rev_sha).once
66
+ assert_equal(head_rev_sha, GitHelpers.git_head_rev('some_stack'))
67
+ # Calling it a second time should just use the cached result on disk
68
+ assert_equal(head_rev_sha, GitHelpers.git_head_rev('some_stack'))
69
+ end
70
+ end