redis_monitor 0.2.1 → 0.3

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.travis.yml +1 -1
  4. data/README.md +7 -9
  5. data/Rakefile +4 -0
  6. data/lib/engine/app/controllers/content_controller.rb +1 -1
  7. data/lib/engine/app/controllers/notifications_controller.rb +19 -0
  8. data/lib/engine/app/controllers/tasks_controller.rb +47 -0
  9. data/lib/engine/app/helpers/application_helper.rb +13 -3
  10. data/lib/engine/app/lib/commands/retrieve_key.rb +5 -0
  11. data/lib/engine/app/lib/commands/{search_key.rb → search_keys.rb} +1 -1
  12. data/lib/engine/app/lib/jobs/background_task_job.rb +42 -0
  13. data/lib/engine/app/models/notification.rb +7 -0
  14. data/lib/engine/app/models/task.rb +34 -0
  15. data/lib/engine/app/models/watch_key_task.rb +22 -0
  16. data/lib/engine/app/models/watch_query_task.rb +24 -0
  17. data/lib/engine/app/views/content/_search_form.haml +2 -2
  18. data/lib/engine/app/views/layouts/application.haml +1 -0
  19. data/lib/engine/app/views/notifications/_notification.haml +9 -0
  20. data/lib/engine/app/views/notifications/index.haml +14 -0
  21. data/lib/engine/app/views/tasks/_form.haml +32 -0
  22. data/lib/engine/app/views/tasks/_task.haml +14 -0
  23. data/lib/engine/app/views/tasks/edit.haml +3 -0
  24. data/lib/engine/app/views/tasks/index.haml +21 -0
  25. data/lib/engine/app/views/tasks/new.haml +3 -0
  26. data/lib/engine/bin/delayed_job +5 -0
  27. data/lib/engine/config/database.yml +2 -2
  28. data/lib/engine/config/initializers/delayed_job.rb +3 -0
  29. data/lib/engine/config/routes.rb +3 -0
  30. data/lib/engine/db/migrate/20140112162911_create_tasks.rb +15 -0
  31. data/lib/engine/db/migrate/20140118155310_create_delayed_jobs.rb +22 -0
  32. data/lib/engine/db/migrate/20140118183403_create_notifications.rb +10 -0
  33. data/lib/engine/db/schema.rb +51 -0
  34. data/lib/engine/public/javascripts/jquery_ujs.js +394 -0
  35. data/lib/engine/public/stylesheets/custom.css +4 -0
  36. data/lib/engine/spec/controllers/content_controller_spec.rb +1 -1
  37. data/lib/engine/spec/controllers/notifications_controller_spec.rb +39 -0
  38. data/lib/engine/spec/controllers/tasks_controller_spec.rb +109 -0
  39. data/lib/engine/spec/lib/commands/retrieve_key_spec.rb +15 -0
  40. data/lib/engine/spec/lib/commands/search_key_spec.rb +2 -2
  41. data/lib/engine/spec/lib/jobs/background_task_job_spec.rb +55 -0
  42. data/lib/engine/spec/models/notification_spec.rb +9 -0
  43. data/lib/engine/spec/models/task_spec.rb +23 -0
  44. data/lib/engine/spec/models/watch_key_task_spec.rb +13 -0
  45. data/lib/engine/spec/models/watch_query_task_spec.rb +13 -0
  46. data/lib/redis_monitor.rb +7 -1
  47. data/lib/version.rb +1 -1
  48. data/redis_monitor.gemspec +2 -0
  49. metadata +61 -10
  50. data/lib/engine/test/controllers/.keep +0 -0
  51. data/lib/engine/test/fixtures/.keep +0 -0
  52. data/lib/engine/test/helpers/.keep +0 -0
  53. data/lib/engine/test/integration/.keep +0 -0
  54. data/lib/engine/test/mailers/.keep +0 -0
  55. data/lib/engine/test/models/.keep +0 -0
  56. data/lib/engine/test/test_helper.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ff13da4a5a6d6d3b4965964e669d6790b8aa88eb
4
- data.tar.gz: db3f048bdb23af208771e50116923c962b4b520f
3
+ metadata.gz: b306ec30c262af4c5130f654efee0356f2652ff8
4
+ data.tar.gz: 7ee38ccac2e96471493eeb7349876395786bb43f
5
5
  SHA512:
6
- metadata.gz: 0008c8e625d7c73d2fe4ad6f554f4ea648557c6d308b4538e870000df576ad8cf052cd78d4931d1f2f62983622356851a2d47fd3444b79e964366250127942cf
7
- data.tar.gz: b63cd187913745965bc002e4928f9b72df5a6490ed7febe98615290ed9646ed6bd13e57c76e11bdb7bb73de50fe6f593e51fbad437b2f9503cc296e36e3b81d3
6
+ metadata.gz: 95da81fb9d481224903eb4f6a25aa49c28b2f283e3aa0747be13dc9bb73bdffb2c75eb3a4a68e6e70464ea0e969525756ad9c2c2bf24a7d73d3384d1e23a150b
7
+ data.tar.gz: fc1a93aef1fb52ab9e8ae2a3c8eeb465f4ba61a52811b702e18f0394e2415ce0a29b967c871c3093428435c31b40640b6c0cf57107eb3a513fea18e2a0533a18
data/.gitignore CHANGED
@@ -17,4 +17,6 @@ test/version_tmp
17
17
  tmp
18
18
  .rvmrc
19
19
  .idea
20
- *.rdb
20
+ *.rdb
21
+ .ruby-version
22
+ .ruby-gemset
data/.travis.yml CHANGED
@@ -4,4 +4,4 @@ rvm:
4
4
  - 2.0.0
5
5
  - jruby-19mode
6
6
 
7
- script: rake spec
7
+ script: rake db:migrate;rake spec
data/README.md CHANGED
@@ -3,15 +3,7 @@
3
3
 
4
4
  ## Installation
5
5
 
6
- Add this line to your application's Gemfile:
7
-
8
- gem 'redis_monitor'
9
-
10
- And then execute:
11
-
12
- $ bundle
13
-
14
- Or install it yourself as:
6
+ Execute:
15
7
 
16
8
  $ gem install redis_monitor
17
9
 
@@ -29,6 +21,12 @@ For more information about the parameters:
29
21
 
30
22
  $ redis_monitor --help
31
23
 
24
+ You can define tasks in 'Task' tab and they will be running in background. There is two type of task availables at the moment:
25
+ - Watch key, it will watch key introduced in 'trigger' field and it will create notifications if the value change.
26
+
27
+ - Watch query, it will watch the query introduced in 'trigger' field and in the result of the query changes it will create a notification.
28
+ Be careful with this one, don't watch queries too generic because it can affect to the performance of your database.
29
+
32
30
  ## Contributing
33
31
 
34
32
  1. Fork it
data/Rakefile CHANGED
@@ -1,6 +1,10 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
+ require File.expand_path('../lib/engine/config/application', __FILE__)
4
+
5
+ Engine::Application.load_tasks
3
6
 
4
7
  RSpec::Core::RakeTask.new(:spec) do |config|
5
8
  config.rspec_opts = '--default-path lib/engine/spec'
6
9
  end
10
+
@@ -8,7 +8,7 @@ class ContentController < ApplicationController
8
8
  end
9
9
 
10
10
  def search
11
- results = SearchKey.new(backend, params[:key]).result
11
+ results = SearchKeys.new(backend, params[:key]).result
12
12
  @results = results.paginate(:page => params[:page], :per_page => 20)
13
13
  end
14
14
 
@@ -0,0 +1,19 @@
1
+ class NotificationsController < ApplicationController
2
+ skip_before_action :verify_authenticity_token
3
+ before_action :load_section
4
+
5
+ def index
6
+ @notifications = Notification.all
7
+ end
8
+
9
+ def destroy
10
+ notification = Notification.find(params[:id])
11
+ notification.destroy
12
+ redirect_to action: :index
13
+ end
14
+
15
+ private
16
+ def load_section
17
+ @section = 'notifications'
18
+ end
19
+ end
@@ -0,0 +1,47 @@
1
+ class TasksController < ApplicationController
2
+ skip_before_action :verify_authenticity_token
3
+ before_action :load_section
4
+ after_action :reset_queue, only: [:create, :update, :destroy]
5
+
6
+ def index
7
+ @tasks = Task.all
8
+ end
9
+
10
+ def new
11
+ @task = Task.new
12
+ end
13
+
14
+ def edit
15
+ @task = Task.find(params[:id])
16
+ end
17
+
18
+ def create
19
+ @task = Task.create(task_params)
20
+ redirect_to action: :index
21
+ end
22
+
23
+ def update
24
+ @task = Task.find(params[:id])
25
+ @task.update_attributes(task_params)
26
+ redirect_to action: :index
27
+ end
28
+
29
+ def destroy
30
+ @task = Task.find(params[:id])
31
+ @task.destroy
32
+ redirect_to action: :index
33
+ end
34
+
35
+ private
36
+ def load_section
37
+ @section = 'tasks'
38
+ end
39
+
40
+ def task_params
41
+ params.require(:task).permit(:name, :type, :trigger, :database, :every, :status)
42
+ end
43
+
44
+ def reset_queue
45
+ @task.reset_queue
46
+ end
47
+ end
@@ -12,10 +12,20 @@ module ApplicationHelper
12
12
  def content_menu(selected_section)
13
13
  capture_haml do
14
14
  haml_tag :ul, class: 'nav navbar-nav' do
15
- section(name: 'info', title: 'Info', url: '/info', selected_section: selected_section)
16
- section(name: 'content', title: 'Content', url: '/content', selected_section: selected_section)
17
- section(name: 'performance', title: 'Performance', url: '/performance', selected_section: selected_section)
15
+ section(name: 'info', title: 'Info', url: info_index_path, selected_section: selected_section)
16
+ section(name: 'content', title: 'Content', url: content_index_path, selected_section: selected_section)
17
+ section(name: 'tasks', title: 'Tasks', url: tasks_path, selected_section: selected_section)
18
+ section(name: 'notifications', title: 'Notifications', url: notifications_path, selected_section: selected_section)
19
+ section(name: 'performance', title: 'Performance', url: performance_index_path, selected_section: selected_section)
18
20
  end
19
21
  end
20
22
  end
23
+
24
+ def remove_class
25
+ 'btn btn-danger'
26
+ end
27
+
28
+ def edit_class
29
+ 'btn btn-primary'
30
+ end
21
31
  end
@@ -0,0 +1,5 @@
1
+ class RetrieveKey < Struct.new(:backend, :key)
2
+ def result
3
+ backend.get(key)
4
+ end
5
+ end
@@ -1,4 +1,4 @@
1
- class SearchKey < Struct.new(:backend, :key)
1
+ class SearchKeys < Struct.new(:backend, :key)
2
2
  def result
3
3
  backend.keys(key).map{|found| {key: found, value: backend.get(found)} }
4
4
  end
@@ -0,0 +1,42 @@
1
+ class BackgroundTaskJob < Struct.new(:task)
2
+ def queue_name
3
+ "task_#{task.id}"
4
+ end
5
+
6
+ def run_at
7
+ task.every.seconds.from_now
8
+ end
9
+
10
+ def clear_queue
11
+ Delayed::Job.where(queue: queue_name).destroy_all
12
+ end
13
+
14
+ def add_to_queue
15
+ if task.active?
16
+ Delayed::Job.enqueue(self, queue: queue_name, run_at: run_at)
17
+ end
18
+ end
19
+
20
+ def reset_queue
21
+ clear_queue
22
+ add_to_queue
23
+ end
24
+
25
+ def perform
26
+ task.perform
27
+ end
28
+
29
+ def refresh_data
30
+ self.task = Task.where(id: task.id).first
31
+ end
32
+
33
+ def after(job)
34
+ refresh_data
35
+ add_to_queue if task
36
+ end
37
+
38
+ def self.reset_all_queues
39
+ Delayed::Job.where("queue like 'task_%'").destroy_all
40
+ Task.all.each{|task| new(task).add_to_queue}
41
+ end
42
+ end
@@ -0,0 +1,7 @@
1
+ class Notification < ActiveRecord::Base
2
+ default_scope ->{order('created_at DESC')}
3
+
4
+ def when
5
+ created_at.localtime.strftime('%Y/%m/%d %H:%M')
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ class Task < ActiveRecord::Base
2
+ ACTIVE = 'active'.freeze
3
+ INACTIVE = 'inactive'.freeze
4
+ DEFAULT_INTERVAL = 60
5
+ DEFAULT_DATABASE = 0
6
+
7
+ store :data
8
+ after_initialize :init
9
+
10
+ def init
11
+ self.every ||= DEFAULT_INTERVAL
12
+ self.database ||= DEFAULT_DATABASE
13
+ end
14
+
15
+ def self.selectable_actions
16
+ [['Watch key', WatchKeyTask], ['Watch query', WatchQueryTask]]
17
+ end
18
+
19
+ def active?
20
+ status == ACTIVE
21
+ end
22
+
23
+ def to_partial_path
24
+ 'tasks/task'
25
+ end
26
+
27
+ def reset_queue
28
+ BackgroundTaskJob.new(self).reset_queue
29
+ end
30
+
31
+ def backend
32
+ BackendConnection.build(current_database: self.database)
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ class WatchKeyTask < Task
2
+ def type_name
3
+ 'Watch key'
4
+ end
5
+
6
+ def perform
7
+ result = RetrieveKey.new(backend, trigger).result
8
+ if data[:initialized]
9
+ notify(data[:old_value], result) if data[:old_value] != result
10
+ else
11
+ data[:initialized] = true
12
+ end
13
+
14
+ data[:old_value] = result
15
+ save
16
+ end
17
+
18
+ def notify(old_value, new_value)
19
+ msg = "Value of key '#{trigger}' has changed from #{old_value.inspect} to #{new_value.inspect}"
20
+ Notification.create(reporter: name, message: msg)
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+
3
+ class WatchQueryTask < Task
4
+ def type_name
5
+ 'Watch query'
6
+ end
7
+
8
+ def perform
9
+ result = SearchKeys.new(backend, trigger).result.map(&:with_indifferent_access)
10
+ if data[:initialized]
11
+ notify(data[:old_value], result) if data[:old_value] != result
12
+ else
13
+ data[:initialized] = true
14
+ end
15
+
16
+ data[:old_value] = result
17
+ save
18
+ end
19
+
20
+ def notify(old_value, new_value)
21
+ msg = "Value of key '#{trigger}' has changed from #{old_value.inspect} to #{new_value.inspect}"
22
+ Notification.create!(reporter: name, message: msg)
23
+ end
24
+ end
@@ -3,11 +3,11 @@
3
3
  = choose_database_select(@databases)
4
4
  %hr
5
5
 
6
- %form{action: '/content/search', method: 'get', style: 'margin-top: 5px'}
6
+ %form{action: search_content_index_path, method: 'get', style: 'margin-top: 5px'}
7
7
  .form-group
8
8
  %label{for: 'input_key'}
9
9
  Introduce search terms (wildcards allowed)
10
10
  %input.form-control{type: 'text', id: 'input_key', name: 'key', placeholder: 'Search terms', value: search_term}
11
11
  .form-group
12
- %button.btn.btn-default{type: 'submit'}
12
+ %button{type: 'submit', class: remove_class}
13
13
  Search
@@ -6,6 +6,7 @@
6
6
  = stylesheet_link_tag 'custom', media: 'all'
7
7
 
8
8
  = javascript_include_tag 'jquery-2.0.3.min'
9
+ = javascript_include_tag 'jquery_ujs'
9
10
  = javascript_include_tag 'bootstrap.min'
10
11
  = javascript_include_tag 'bootstrap-select.min'
11
12
  = javascript_include_tag 'app'
@@ -0,0 +1,9 @@
1
+ %tr
2
+ %td
3
+ = notification.reporter
4
+ %td
5
+ = notification.message
6
+ %td
7
+ = notification.when
8
+ %td
9
+ = link_to 'Delete', notification_path(notification), class: remove_class, :confirm => 'Are you sure?', :method => :delete
@@ -0,0 +1,14 @@
1
+ %table.table.table-striped
2
+ %thead
3
+ %tr
4
+ %th
5
+ Reporter
6
+ %th
7
+ Message
8
+ %th
9
+ When
10
+ %th
11
+ Actions
12
+
13
+ %tbody
14
+ = render @notifications
@@ -0,0 +1,32 @@
1
+ = form_for @task.becomes(Task) do |f|
2
+ .form-group
3
+ = f.label :name
4
+ = f.text_field :name, class: 'form-control'
5
+
6
+ .form-group
7
+ = f.label :type, 'Action'
8
+ = f.select(:type, options_for_select(Task.selectable_actions, @task.type), {}, {class: 'form-control'})
9
+
10
+ .form-group
11
+ = f.label :trigger
12
+ = f.text_field :trigger, class: 'form-control'
13
+
14
+ .form-group
15
+ = f.label :database, 'Database'
16
+ = f.number_field :database, class: 'form-control'
17
+
18
+ .form-group
19
+ = f.label :every, 'Every (seconds)'
20
+ = f.number_field :every, class: 'form-control'
21
+
22
+ .form_group
23
+ .checkbox
24
+ = f.label :status, {style: 'font-weight:bold'} do
25
+ = f.check_box :status, {}, Task::ACTIVE, Task::INACTIVE
26
+ = 'Active'
27
+
28
+ .form-group
29
+ %button.btn.btn-default{type: 'submit', style: 'margin-right: 10px'}
30
+ Save
31
+ %a{href: tasks_path}
32
+ Cancel
@@ -0,0 +1,14 @@
1
+ %tr
2
+ %td
3
+ = task.name
4
+ %td
5
+ = task.type_name
6
+ %td
7
+ = task.trigger
8
+ %td
9
+ = task.every
10
+ %td
11
+ = task.active? ? 'Yes' : 'No'
12
+ %td
13
+ = link_to 'Edit', edit_task_path(task), class: edit_class
14
+ = link_to 'Delete', task_path(task), class: remove_class, :confirm => 'Are you sure?', :method => :delete
@@ -0,0 +1,3 @@
1
+ %h2
2
+ Edit task
3
+ = render 'form'
@@ -0,0 +1,21 @@
1
+ %table.table.table-striped
2
+ %thead
3
+ %tr
4
+ %th
5
+ Name
6
+ %th
7
+ Action
8
+ %th
9
+ Trigger
10
+ %th
11
+ Every (seconds)
12
+ %th
13
+ Active
14
+ %th
15
+ Actions
16
+
17
+ %tbody
18
+ = render @tasks
19
+
20
+ %a.btn.btn-default{href: new_task_path}
21
+ New task