wackamole 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. data/History.txt +5 -1
  2. data/README.rdoc +12 -3
  3. data/bin/wackamole +2 -2
  4. data/data/fixtures.rb +132 -0
  5. data/features/env.rb +29 -0
  6. data/features/mission.feature +10 -0
  7. data/features/step_definitions/mission_steps.rb +5 -0
  8. data/lib/app.rb +23 -7
  9. data/lib/controllers/features.rb +3 -2
  10. data/lib/controllers/logs.rb +1 -0
  11. data/lib/controllers/mission.rb +14 -4
  12. data/lib/controllers/session.rb +32 -0
  13. data/lib/controllers/users.rb +3 -2
  14. data/lib/helpers/flash_helper.rb +4 -1
  15. data/lib/helpers/logs_helper.rb +2 -1
  16. data/lib/helpers/session_helper.rb +29 -0
  17. data/lib/wackamole.rb +1 -1
  18. data/lib/wackamole/models/control.rb +15 -1
  19. data/lib/wackamole/models/feature.rb +11 -2
  20. data/lib/wackamole/models/log.rb +1 -0
  21. data/lib/wackamole/models/mission.rb +9 -7
  22. data/lib/wackamole/models/mole_info.rb +8 -4
  23. data/lib/wackamole/models/search_filter.rb +48 -15
  24. data/lib/wackamole/models/user.rb +12 -1
  25. data/public/stylesheets/wackamole.css +60 -3
  26. data/spec/config/bogus_test.yml +1 -1
  27. data/spec/config/test.yml +1 -1
  28. data/spec/spec_helper.rb +3 -0
  29. data/spec/ui/log_spec.rb +68 -0
  30. data/spec/ui/mission_spec.rb +64 -0
  31. data/spec/ui/session_spec.rb +39 -0
  32. data/spec/ui_utils/mission_util.rb +41 -0
  33. data/spec/{models → wackamole/models}/control_spec.rb +21 -21
  34. data/spec/{models → wackamole/models}/feature_spec.rb +7 -7
  35. data/spec/{models → wackamole/models}/log_spec.rb +5 -5
  36. data/spec/{models → wackamole/models}/mission_spec.rb +11 -7
  37. data/spec/wackamole/models/moled_info_spec.rb +36 -0
  38. data/spec/{models → wackamole/models}/search_filter_spec.rb +12 -16
  39. data/spec/{models → wackamole/models}/user_spec.rb +5 -5
  40. data/spec/{wackamole_spec.rb → wackamole/wackamole_spec.rb} +2 -2
  41. data/tasks/fixtures.rake +3 -1
  42. data/tasks/setup.rb +2 -1
  43. data/tasks/spec.rake +17 -3
  44. data/views/dashboard/_report.erb +3 -3
  45. data/views/layout.erb +44 -11
  46. data/views/logs/show.erb +3 -0
  47. data/views/mission/_report.erb +2 -2
  48. data/views/session/login.erb +28 -0
  49. data/views/shared/_search.erb +1 -1
  50. data/views/users/index.js.erb +1 -1
  51. metadata +22 -10
  52. data/spec/models/moled_info_spec.rb +0 -30
@@ -1,7 +1,7 @@
1
1
  module Wackamole
2
2
 
3
3
  # :stopdoc:
4
- VERSION = '0.0.8' unless defined? Wackamole::VERSION
4
+ VERSION = '0.0.9' unless defined? Wackamole::VERSION
5
5
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR unless defined? Wackamole::LIBPATH
6
6
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR unless defined? Wackamole::PATH
7
7
  # :startdoc:
@@ -8,7 +8,7 @@ module Wackamole
8
8
  # Initialize app by reading off mongo configuration parameters if necessary
9
9
  def self.init_config( config_file, env )
10
10
  begin
11
- config = YAML.load_file( config_file )
11
+ config = YAML.load_file( config_file )
12
12
  @config = config[env]
13
13
  raise "Invalid environment `#{env}" unless @config
14
14
  raise "Unable to find host in - #{@config.inspect}" unless @config.has_key?('host')
@@ -81,6 +81,7 @@ module Wackamole
81
81
  def self.mole_db?( db_name )
82
82
  return false unless db_name =~ molex
83
83
  db = connection.db( db_name )
84
+ db.authenticate( config['username'], config['password'] )
84
85
  cltns = db.collection_names
85
86
  return ((%w[users features logs] & cltns).size == 3)
86
87
  end
@@ -98,6 +99,19 @@ module Wackamole
98
99
  @config
99
100
  end
100
101
 
102
+ #
103
+ def self.single_app?
104
+ config['db_name']
105
+ end
106
+
107
+ #
108
+ def self.app_info
109
+ if config['db_name']
110
+ return extract_app_info( config['db_name'] )
111
+ end
112
+ nil
113
+ end
114
+
101
115
  # -----------------------------------------------------------------------
102
116
  # Connects to mongo instance if necessary...
103
117
  def self.connection( log=false )
@@ -21,12 +21,21 @@ module Wackamole
21
21
  def self.paginate_tops( conds, page=1, page_size=default_page_size )
22
22
  tops = logs_cltn.group( [:fid], conds, { :count => 0 }, 'function(obj,prev) { prev.count += 1}', true )
23
23
 
24
+ all_features = features_cltn.find( {}, :fields => [:_id] )
25
+ feature_ids = all_features.map{ |f| f['_id'] }
26
+ total_features = []
27
+ tops.each do |row|
28
+ total_features << row
29
+ feature_ids.delete( row['fid'] )
30
+ end
31
+ feature_ids.each { |f| total_features << {'fid' => f, 'count' => 0} }
32
+
24
33
  features = []
25
- tops.sort{ |a,b| b['count'] <=> a['count'] }.each do |row|
34
+ total_features.sort{ |a,b| b['count'] <=> a['count'] }.each do |row|
26
35
  features << { :fid => row['fid'], :total => row['count'].to_i }
27
36
  end
28
37
 
29
- WillPaginate::Collection.create( page, page_size, features.size ) do |pager|
38
+ WillPaginate::Collection.create( page, page_size, features.size ) do |pager|
30
39
  offset = (page-1)*page_size
31
40
  result = features[offset...(offset+page_size)]
32
41
  result.each do |u|
@@ -14,6 +14,7 @@ module Wackamole
14
14
  # ---------------------------------------------------------------------------
15
15
  # Fetch all logs matching the given condition
16
16
  def self.paginate( conds, page=1, page_size=default_page_size )
17
+ puts conds.inspect
17
18
  matching = logs_cltn.find( conds )
18
19
  WillPaginate::Collection.create( page, page_size, matching.count ) do |pager|
19
20
  pager.replace( logs_cltn.find( conds,
@@ -15,16 +15,18 @@ module Wackamole
15
15
 
16
16
  # -----------------------------------------------------------------------
17
17
  # generates mole logs conditons
18
- def self.gen_conds( now, single_day )
18
+ def self.gen_conds( now, single_day )
19
19
  conds = {}
20
20
  if now
21
21
  if single_day
22
- now = Time.local( now.year, now.month, now.day, 0, 0, 0 ).utc
22
+ conds = SearchFilter.time_conds( now, 1 )
23
+ else
24
+ now = now.clone.utc
25
+ date_id = now.to_date_id.to_s
26
+ time_id = now.to_time_id
27
+ conds[:did] = { '$gte' => date_id }
28
+ conds[:tid] = { '$gte' => time_id }
23
29
  end
24
- date_id = now.to_date_id.to_s
25
- time_id = now.to_time_id
26
- conds[:did] = date_id
27
- conds[:tid] = {'$gte' => time_id}
28
30
  end
29
31
  conds
30
32
  end
@@ -53,7 +55,7 @@ module Wackamole
53
55
  row[t] = logs.count
54
56
  end
55
57
  end
56
- counts
58
+ counts
57
59
  end
58
60
 
59
61
  # =========================================================================
@@ -19,10 +19,14 @@ module Wackamole
19
19
  info[:fault_load] = 0
20
20
 
21
21
  # Fetch day logs
22
- day_logs = logs_cltn.find( { :did => now.utc.to_date_id.to_s },
22
+ utc_time = now.clone.utc
23
+ puts utc_time
24
+ conds = SearchFilter.time_conds( now, 0 )
25
+ day_logs = logs_cltn.find( conds,
23
26
  :fields => [:typ, :fid, :tid, :did, :uid],
24
27
  :sort => [ [:tid => Mongo::ASCENDING] ] )
25
-
28
+ puts conds.inspect
29
+ puts day_logs.count
26
30
  # Count all logs per hourly time period
27
31
  users = Set.new
28
32
  features = Set.new
@@ -36,10 +40,10 @@ module Wackamole
36
40
  log_utc = Time.utc( date_tokens[0], date_tokens[1], date_tokens[2], time_tokens[0], time_tokens[1], time_tokens[2] )
37
41
  local = log_utc.clone.localtime
38
42
  hour = local.hour
39
-
43
+
40
44
  next if hour > local_time.hour
41
45
 
42
- if log_utc.hour == now.hour
46
+ if log_utc.hour == utc_time.hour
43
47
  users << log['uid']
44
48
  features << log['fid']
45
49
  info[:fault_load] += 1 if log['typ'] == Rackamole.fault
@@ -34,6 +34,19 @@ module Wackamole
34
34
  @time_frames ||= ['today', '2 days', '1 week', '2 weeks', '1 month', '3 months', '6 months', '1 year' ]
35
35
  end
36
36
 
37
+ def self.time_frames_in_days
38
+ @time_frame_in_days ||= {
39
+ 'today' => 0,
40
+ '2 days' => 1,
41
+ '1 week' => 7,
42
+ '2 weeks' => 14,
43
+ '1 month' => 30,
44
+ '3 months' => 90,
45
+ '6 months' => 180,
46
+ '1 year' => 360
47
+ }
48
+ end
49
+
37
50
  # ---------------------------------------------------------------------------
38
51
  # Available hours
39
52
  def self.hourlies
@@ -45,7 +58,7 @@ module Wackamole
45
58
  def self.mole_types
46
59
  @types ||= %w[All Feature Perf Fault]
47
60
  end
48
-
61
+
49
62
  # ---------------------------------------------------------------------------
50
63
  # Set filter type
51
64
  def mole_type( type )
@@ -118,10 +131,41 @@ module Wackamole
118
131
  end
119
132
  end
120
133
 
134
+ # -------------------------------------------------------------------------
135
+ # compute query time factors
136
+ def self.time_conds( local_now, days, current_hour=0 )
137
+ conds = {}
138
+
139
+ if current_hour == 0 and days == 0
140
+ from_utc = Time.local( local_now.year, local_now.month, local_now.day, 0, 0, 1 ).utc
141
+ to_utc = Time.local( local_now.year, local_now.month, local_now.day, 23, 59, 59 ).utc
142
+
143
+ from_date_id = from_utc.to_date_id.to_s
144
+ to_date_id = to_utc.to_date_id.to_s
145
+
146
+ if from_date_id != to_date_id
147
+ from_time_id = from_utc.to_time_id.to_s
148
+ to_time_id = to_utc.to_time_id.to_s
149
+ conds['$where'] = "((this.did == '#{from_date_id}' && this.tid >= '#{from_time_id}') || ( this.did == '#{to_date_id}' && this.tid <= '#{to_time_id}') )"
150
+ else
151
+ conds[:did] = to_date_id
152
+ end
153
+ else
154
+ date = Chronic.parse( "#{days == 1 ? "now" : "#{days} days ago"}" )
155
+ current = "%4d/%02d/%02d %02d:%02d:%02d" % [date.year, date.month, date.day, current_hour, 0, 1]
156
+ time = Chronic.parse( current ).utc
157
+ conds[:did] = { '$gte' => time.to_date_id.to_s }
158
+ conds[:tid] = /^#{"%02d"%time.hour}.+/ unless current_hour == 0
159
+ end
160
+ conds
161
+ end
162
+
121
163
  # ---------------------------------------------------------------------------
122
164
  # Spews filter conditions
123
165
  def to_conds
124
- conds = {}
166
+ current_hour = self.hour
167
+ current_hour = 0 if self.hour == SearchFilter.hourlies.first
168
+ conds = SearchFilter.time_conds( Time.now, SearchFilter.time_frames_in_days[self.time_frame], current_hour )
125
169
 
126
170
  # filter mole_types
127
171
  if type != 'All'
@@ -136,19 +180,8 @@ module Wackamole
136
180
  unless feature_id.to_s == "-1"
137
181
  conds[:fid] = Mongo::ObjectID.from_string( feature_id )
138
182
  end
139
-
140
- # filter by date
141
- time = Chronic.parse( time_frame + ( time_frame == SearchFilter.time_frames.first ? "" : " ago" ) )
142
- conds[:did] = { '$gte' => time.to_date_id.to_s }
143
-
144
- unless self.hour == 'all'
145
- now = Time.now
146
- current = "%4d/%02d/%02d %02d:%02d:%02d" % [now.year, now.month, now.day, self.hour, 0, 0]
147
- time = Chronic.parse( current ).utc
148
- conds[:tid] = /^#{"%02d"%time.hour}.+/
149
- end
150
-
151
- unless search_terms.empty?
183
+
184
+ if search_terms and !search_terms.empty?
152
185
  tokens = search_terms.split( ":" ).collect{ |c| c.strip }
153
186
  key = tokens.shift
154
187
  if key
@@ -17,12 +17,23 @@ module Wackamole
17
17
  tops.sort{ |a,b| b['count'] <=> a['count'] }.each do |row|
18
18
  users << { :uid => row['uid'], :total => row['count'].to_i, :details => [] }
19
19
  end
20
+
21
+ # BOZO !! Not too wise to fetch all users. allow for now and reconsider
22
+ all_users = users_cltn.find( {}, :fields => [:_id] )
23
+ user_ids = all_users.map{ |f| f['_id'] }
24
+ total_users = []
25
+ tops.each do |row|
26
+ total_users << row
27
+ user_ids.delete( row['uid'] )
28
+ end
29
+ user_ids.each { |u| total_users << {'uid' => u, 'count' => 0} }
20
30
 
21
31
  WillPaginate::Collection.create( page, page_size, users.size ) do |pager|
22
32
  offset = (page-1)*page_size
23
33
  result = users[offset...(offset+page_size)]
24
34
  result.each do |u|
25
- user = users_cltn.find_one( u[:uid] ) #, :fields => [:una] )
35
+ # BOZO !! should group that call to retrieve all matching users
36
+ user = users_cltn.find_one( u[:uid], :fields => [:una] )
26
37
  raise "Unable to find user with id `#{u[:uid].inspect}" unless user
27
38
  u[:name] = user['una']
28
39
  end
@@ -29,7 +29,7 @@ div#overall {
29
29
  div#logo {
30
30
  float: left;
31
31
  width: 45%;
32
- margin-bottom: 10px;
32
+ margin-bottom: 5px;
33
33
  }
34
34
 
35
35
  div#main {
@@ -291,10 +291,10 @@ a.fault {
291
291
  div.flash {
292
292
  display: none;
293
293
  width: 95%;
294
+ text-align: center;
294
295
  padding: 10px;
295
296
  font-size: 1.5em;
296
297
  color: #fff;
297
- border: 1px #92b948 solid;
298
298
  -moz-border-radius-bottomleft: 20px;
299
299
  -moz-border-radius-topleft: 0px;
300
300
  -moz-border-radius-bottomright: 0px;
@@ -595,7 +595,7 @@ table#mission thead tr {
595
595
  font-size: 1.2em;
596
596
  color: #808080;
597
597
  }
598
- table#mission tr.div {
598
+ table#mission tr.app_info {
599
599
  border-bottom: 1px #c1c1c1 solid;
600
600
  }
601
601
 
@@ -718,4 +718,61 @@ div.ie_8 {
718
718
  }
719
719
  div.unknown {
720
720
  background-position: -154px 0;
721
+ }
722
+
723
+ /* ------------------------------------------------------------------------- */
724
+ /* Login */
725
+
726
+ div#dialog {
727
+ font-size: 1.5em;
728
+ padding-top: 30px;
729
+ }
730
+
731
+ div#inner_box {
732
+ width: 400px;
733
+ background: #eee;
734
+ height: 190px;
735
+ -moz-border-radius: 7px;
736
+ -webkit-border-radius: 7px;
737
+ -moz-box-shadow: 0 0 90px 0px #000;
738
+ -webkit-box-shadow: 0 0 90px #000;
739
+ margin-top: 40px;
740
+ padding: 30px;
741
+ }
742
+
743
+ form#login div {
744
+ margin-bottom: 10px;
745
+ }
746
+
747
+ form#login label {
748
+ color: #1d1d1d;
749
+ display: block;
750
+ width: 260px;
751
+ text-align: left;
752
+ }
753
+
754
+ form#login input {
755
+ width: 250px;
756
+ font-size: 1.0em;
757
+ font-weight: bold;
758
+ }
759
+
760
+ form#login input[type='text'] {
761
+ padding: 5px;
762
+ color: #1d1d1d;
763
+ background: transparent url(../images/fade.png);
764
+ }
765
+
766
+ form#login input[type='password'] {
767
+ padding: 5px;
768
+ background: transparent url(../images/fade.png);
769
+ }
770
+
771
+ form#login input[type='submit'] {
772
+ width: 100px;
773
+ }
774
+
775
+ div#copyright {
776
+ margin-top:4px;
777
+ color:#4974FF;
721
778
  }
@@ -1,3 +1,3 @@
1
1
  test:
2
2
  bozo: localhost
3
- blee: 27777
3
+ blee: 27099
@@ -1,3 +1,3 @@
1
1
  test:
2
2
  host: localhost
3
- port: 27777
3
+ port: 27099
@@ -1,4 +1,7 @@
1
1
  require 'rubygems'
2
+ require 'sinatra'
3
+ require 'forwardable'
4
+ require 'rack/test'
2
5
  require 'mongo'
3
6
  require 'rackamole'
4
7
  gem 'agnostic-will_paginate'
@@ -0,0 +1,68 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. spec_helper])
2
+ require 'capybara'
3
+ require 'capybara/dsl'
4
+ require File.join(File.dirname(__FILE__), %w[.. ui_utils mission_util])
5
+
6
+ include Capybara
7
+ include MissionUtil
8
+
9
+ describe 'Mission' do
10
+ before( :all ) do
11
+ Capybara.default_driver = :selenium
12
+ @url = "http://localhost:4567/"
13
+ end
14
+
15
+ before :each do
16
+ visit( @url )
17
+ login( @url, 'admin', 'admin', @url + 'mission' )
18
+ rows = all( :css, "table#mission tr.app_info" )
19
+ show_logs( rows[0], 'app1', 'test', 0 )
20
+ current_url.should == @url + "logs/1"
21
+ end
22
+
23
+ after :each do
24
+ log_out
25
+ end
26
+
27
+ it "should filter log's type correctly" do
28
+ expected = { 'Perf' => 4, 'Fault' => 2 }
29
+ expected.each_pair do |k,v|
30
+ within( "//form[@id='filter_form']" ) do
31
+ locate( :css, "select#time_frame").select( 'today' )
32
+ locate( :css, "select#hourly_frame").select( 'all' )
33
+ locate( :css, "select#type").select( k )
34
+ click_button( 'Filter' )
35
+ end
36
+ sleep( 0.2 )
37
+ find( :css, 'div.page_entries' ).text.should =~ /#{v}/
38
+ end
39
+ end
40
+
41
+ it "should filter log's time range correctly" do
42
+ expected = { 'today' => 12, '2 days' => 15 }
43
+ expected.each_pair do |k,v|
44
+ within( "//form[@id='filter_form']" ) do
45
+ locate( :css, "select#hourly_frame").select( 'all' )
46
+ locate( :css, "select#type").select( 'All' )
47
+ locate( :css, "select#time_frame").select( k )
48
+ click_button( 'Filter' )
49
+ end
50
+ sleep( 0.2 )
51
+ find( :css, 'div.page_entries' ).text.should =~ /#{v}/
52
+ end
53
+ end
54
+
55
+ it "should filter log's hour correctly" do
56
+ expected = { '17' => 5, '18' => 1 }
57
+ expected.each_pair do |k,v|
58
+ within( "//form[@id='filter_form']" ) do
59
+ locate( :css, "select#type").select( 'All' )
60
+ locate( :css, "select#time_frame").select( 'today' )
61
+ locate( :css, "select#hourly_frame").select( k )
62
+ click_button( 'Filter' )
63
+ end
64
+ sleep( 0.2 )
65
+ find( :css, 'div.page_entries' ).text.should =~ /#{v}/
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,64 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. spec_helper])
2
+ require 'capybara'
3
+ require 'capybara/dsl'
4
+ require File.join(File.dirname(__FILE__), %w[.. ui_utils mission_util])
5
+
6
+ include Capybara
7
+ include MissionUtil
8
+
9
+ describe 'Mission' do
10
+ before( :all ) do
11
+ Capybara.default_driver = :selenium
12
+ @url = "http://localhost:4567/"
13
+ end
14
+
15
+ before :each do
16
+ visit( @url )
17
+ login( @url, 'admin', 'admin', @url + 'mission' )
18
+ end
19
+
20
+ after :each do
21
+ log_out
22
+ end
23
+
24
+ it "should gather mission info correctly" do
25
+ rows = all( :css, "table#mission tr.app_info" )
26
+ rows.should have(2).items
27
+
28
+ check_app( rows[0], 'app1', 'test', [7,5,3], [6,4,2], [0,0,0] )
29
+ check_app( rows[1], 'app2', 'test', [7,5,3], [6,4,2], [0,0,0] )
30
+ end
31
+
32
+ it "should have the right number of links" do
33
+ rows = all( :css, "table#mission tr.app_info" )
34
+ rows.first.all( :css, "a" ).should have(4).items
35
+ rows.last.all( :css, "a" ).should have(4).items
36
+ end
37
+
38
+ it "should navigate to an application dasboard correctly" do
39
+ %w(app1).each do |app|
40
+ find_link( app ).click
41
+ current_url.should == "http://localhost:4567/dashboard/#{app}/test"
42
+ # visit( @url )
43
+ end
44
+ end
45
+
46
+ it "should navigate to logs features correctly" do
47
+ [0,1].each do |i|
48
+ rows = all( :css, "table#mission tr.app_info" )
49
+ cells = rows[i].all( :css, 'td' )
50
+ app = cells[0].text
51
+ env = cells[1].text
52
+ vals = %w(Feature Perf Fault)
53
+ logs = [6,4,2]
54
+ [0,1,2].each do |type|
55
+ rows = all( :css, "table#mission tr.app_info" )
56
+ show_logs( rows[i], app, env, type )
57
+ current_url.should == @url + "logs/1"
58
+ find( :css, 'select#type' ).value.should == vals[type]
59
+ find( :css, 'div.page_entries' ).text.should =~ /#{logs[type]}/
60
+ nav_mission
61
+ end
62
+ end
63
+ end
64
+ end