oculus 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +17 -0
  2. data/.travis.yml +7 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +22 -0
  5. data/README.md +34 -0
  6. data/Rakefile +18 -0
  7. data/TODO.md +12 -0
  8. data/bin/oculus +32 -0
  9. data/features/query.feature +26 -0
  10. data/features/step_definitions/query_steps.rb +35 -0
  11. data/features/support/env.rb +22 -0
  12. data/lib/oculus/connection/mysql2.rb +22 -0
  13. data/lib/oculus/connection.rb +7 -0
  14. data/lib/oculus/presenters/query_presenter.rb +40 -0
  15. data/lib/oculus/presenters.rb +1 -0
  16. data/lib/oculus/query.rb +61 -0
  17. data/lib/oculus/server/public/css/bootstrap.min.css +689 -0
  18. data/lib/oculus/server/public/css/codemirror.css +112 -0
  19. data/lib/oculus/server/public/css/reset.css +47 -0
  20. data/lib/oculus/server/public/css/style.css +107 -0
  21. data/lib/oculus/server/public/img/glyphicons-halflings-white.png +0 -0
  22. data/lib/oculus/server/public/img/glyphicons-halflings.png +0 -0
  23. data/lib/oculus/server/public/js/application.js +160 -0
  24. data/lib/oculus/server/public/js/bootstrap.min.js +6 -0
  25. data/lib/oculus/server/public/js/codemirror.min.js +1 -0
  26. data/lib/oculus/server/public/js/jquery.min.js +4 -0
  27. data/lib/oculus/server/public/js/spin.min.js +2 -0
  28. data/lib/oculus/server/views/history.erb +35 -0
  29. data/lib/oculus/server/views/index.erb +97 -0
  30. data/lib/oculus/server/views/layout.erb +37 -0
  31. data/lib/oculus/server/views/show.erb +59 -0
  32. data/lib/oculus/server.rb +88 -0
  33. data/lib/oculus/storage/file_store.rb +129 -0
  34. data/lib/oculus/storage.rb +7 -0
  35. data/lib/oculus/version.rb +3 -0
  36. data/lib/oculus.rb +29 -0
  37. data/oculus.gemspec +26 -0
  38. data/spec/connection_spec.rb +61 -0
  39. data/spec/file_store_spec.rb +111 -0
  40. data/spec/query_presenter_spec.rb +54 -0
  41. data/spec/query_spec.rb +107 -0
  42. metadata +173 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ before_script:
6
+ - "export DISPLAY=:99.0"
7
+ - "sh -e /etc/init.d/xvfb start"
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Paul Rosania
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # Oculus
2
+
3
+ ![The Oculus of the Pantheon](http://upload.wikimedia.org/wikipedia/commons/1/17/Oculus_of_the_Pantheon.jpg)
4
+
5
+ [![Build Status](https://secure.travis-ci.org/paulrosania/oculus.png?branch=master)](http://travis-ci.org/paulrosania/oculus)
6
+ [![Dependency Status](https://gemnasium.com/paulrosania/oculus.png)](https://gemnasium.com/paulrosania/oculus)
7
+
8
+ Oculus is a web-based logging SQL client. It keeps a history of your queries
9
+ and the results they returned, so your research is always at hand, easy to share
10
+ and easy to repeat or reproduce in the future.
11
+
12
+ **Oculus will not prevent you from doing stupid things! I recommend using a
13
+ readonly MySQL account.**
14
+
15
+ ## Installation
16
+
17
+ $ gem install oculus
18
+
19
+ ## Usage
20
+
21
+ Oculus is a Sinatra app. Run it from the command line, or mount `Oculus::Server`
22
+ as middleware in your Rack application.
23
+
24
+ For details on command line options, run:
25
+
26
+ oculus --help
27
+
28
+ ## Contributing
29
+
30
+ 1. Fork it
31
+ 2. Make your changes
32
+ 3. Send me a pull request
33
+
34
+ If you're making a big change, please open an Issue first, so we can discuss.
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core/rake_task'
5
+ require 'cucumber/rake/task'
6
+
7
+ desc 'Run RSpec tests'
8
+ RSpec::Core::RakeTask.new(:spec) do |task|
9
+ task.rspec_opts = %w[--color --format documentation]
10
+ task.pattern = 'spec/*_spec.rb'
11
+ end
12
+
13
+ desc 'Run Cucumber features'
14
+ Cucumber::Rake::Task.new(:cucumber) do |task|
15
+ task.cucumber_opts = 'features --format pretty'
16
+ end
17
+
18
+ task :default => [:spec, :cucumber]
data/TODO.md ADDED
@@ -0,0 +1,12 @@
1
+ # Todo
2
+
3
+ ## Upcoming (pre-1.0)
4
+
5
+ * Input validation
6
+ * Data download
7
+
8
+ ## Eventually (1.1 or later)
9
+
10
+ * Restartable queries
11
+ * Reports/query groups
12
+ * Processlist monitor
data/bin/oculus ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ begin
5
+ require 'vegas'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'vegas'
9
+ end
10
+ require 'oculus/server'
11
+
12
+
13
+ Vegas::Runner.new(Oculus::Server, 'oculus') do |runner, opts, app|
14
+ opts.on("-h", "--host HOST", "Database server hostname") do |host|
15
+ Oculus.connection_options[:host] = host
16
+ end
17
+ opts.on("-P", "--port PORT", "Database server port") do |port|
18
+ Oculus.connection_options[:port] = port.to_i
19
+ end
20
+ opts.on("-u", "--username USER", "Database username") do |username|
21
+ Oculus.connection_options[:username] = username
22
+ end
23
+ opts.on("-p", "--password PASSWORD", "Database password") do |password|
24
+ Oculus.connection_options[:password] = password
25
+ end
26
+ opts.on("-D", "--database DATABASE", "Database to use") do |db|
27
+ Oculus.connection_options[:database] = db
28
+ end
29
+ opts.on("-d", "--data DIRECTORY", "Data cache path (default: tmp/data)") do |path|
30
+ Oculus.cache_path = path
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ Feature: Users can query the database
2
+
3
+ @javascript
4
+ Scenario: Running a new query
5
+ When I execute "SELECT * FROM oculus_users"
6
+ Then I should see 3 rows of results
7
+
8
+ Scenario: Loading a cached query
9
+ Given a query is cached with results:
10
+ | id | users |
11
+ | 1 | Paul |
12
+ | 2 | Amy |
13
+ | 3 | Peter |
14
+ When I load the cached query
15
+ Then I should see 3 rows of results
16
+
17
+ @javascript
18
+ Scenario: Deleting a query
19
+ Given a query is cached with results:
20
+ | id | users |
21
+ | 1 | Paul |
22
+ | 2 | Amy |
23
+ | 3 | Peter |
24
+ And I am on the history page
25
+ When I click delete
26
+ Then I should not see any queries
@@ -0,0 +1,35 @@
1
+ Given /^a query is cached with results:$/ do |results|
2
+ Oculus::Query.create(:name => "all users", :query => "SELECT * FROM oculus_users", :results => results.raw)
3
+ end
4
+
5
+ Given /^I am on the history page$/ do
6
+ visit '/history'
7
+ end
8
+
9
+ When /^I execute "([^"]*)"$/ do |query|
10
+ visit '/'
11
+ find('.CodeMirror :first-child :first-child').native.send_keys(query)
12
+ click_button 'Run'
13
+ end
14
+
15
+ When /^I load the cached query$/ do
16
+ visit '/history'
17
+ click_link 'all users'
18
+ end
19
+
20
+ When /^I click delete$/ do
21
+ find('.delete').click
22
+ end
23
+
24
+ Then /^I should see (\d+) rows of results$/ do |result_count|
25
+ page.has_css?(".results", :visible => true)
26
+ within('.results') do
27
+ all('tr').length.should == result_count.to_i
28
+ end
29
+ end
30
+
31
+ Then /^I should not see any queries$/ do
32
+ within('#history') do
33
+ all('li').length.should == 0
34
+ end
35
+ end
@@ -0,0 +1,22 @@
1
+ $: << File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
2
+
3
+ require 'oculus'
4
+ require 'oculus/server'
5
+ require 'capybara/cucumber'
6
+
7
+ Capybara.app = Oculus::Server
8
+
9
+ Oculus.cache_path = 'tmp/test_cache'
10
+ Oculus.connection_options = {
11
+ :host => 'localhost',
12
+ :username => 'root',
13
+ :database => 'test'
14
+ }
15
+
16
+ Before do
17
+ FileUtils.mkdir_p('tmp/test_cache')
18
+ end
19
+
20
+ After do
21
+ FileUtils.rm_r('tmp/test_cache')
22
+ end
@@ -0,0 +1,22 @@
1
+ require 'mysql2'
2
+
3
+ module Oculus
4
+ module Connection
5
+ class Mysql2
6
+ def initialize(options = {})
7
+ @connection = ::Mysql2::Client.new(options)
8
+ end
9
+
10
+ def execute(sql)
11
+ results = @connection.query(sql)
12
+ [results.fields] + results.map(&:values) if results
13
+ rescue ::Mysql2::Error => e
14
+ raise Connection::Error.new(e.message)
15
+ end
16
+
17
+ def thread_id
18
+ @connection.thread_id
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ require 'oculus/connection/mysql2'
2
+
3
+ module Oculus
4
+ module Connection
5
+ class Error < StandardError; end
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ require 'delegate'
2
+
3
+ module Oculus
4
+ module Presenters
5
+ class QueryPresenter < SimpleDelegator
6
+ def formatted_date
7
+ date.strftime("%Y-%m-%d %H:%M") if date
8
+ end
9
+
10
+ def status
11
+ if complete?
12
+ if error
13
+ "error"
14
+ else
15
+ "done"
16
+ end
17
+ else
18
+ "loading"
19
+ end
20
+ end
21
+
22
+ def description
23
+ if name && name != ""
24
+ name
25
+ else
26
+ query = self.query
27
+ if query && query.length > 100
28
+ "#{query[0..97]}..."
29
+ else
30
+ query
31
+ end
32
+ end
33
+ end
34
+
35
+ def named?
36
+ !!name
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1 @@
1
+ require 'oculus/presenters/query_presenter'
@@ -0,0 +1,61 @@
1
+ module Oculus
2
+ class Query
3
+ attr_accessor :id
4
+ attr_accessor :name
5
+ attr_accessor :author
6
+ attr_accessor :query
7
+ attr_accessor :results
8
+ attr_accessor :error
9
+ attr_accessor :date
10
+ attr_accessor :thread_id
11
+
12
+ def initialize(attributes = {})
13
+ attributes.each do |attr, value|
14
+ send("#{attr}=", value)
15
+ end
16
+ end
17
+
18
+ def attributes
19
+ attrs = {
20
+ :name => name,
21
+ :author => author,
22
+ :query => query,
23
+ :date => date,
24
+ :thread_id => thread_id
25
+ }
26
+ attrs[:error] = error if error
27
+ attrs
28
+ end
29
+
30
+ def execute(connection)
31
+ self.results = connection.execute(query)
32
+ rescue Connection::Error => e
33
+ self.error = e.message
34
+ end
35
+
36
+ def save
37
+ @date = Time.now
38
+ Oculus.data_store.save_query(self)
39
+ end
40
+
41
+ def complete?
42
+ !!error || (!results.nil? && !results.empty?)
43
+ end
44
+
45
+ def succeeded?
46
+ complete? && !error
47
+ end
48
+
49
+ class << self
50
+ def create(attributes)
51
+ query = new(attributes)
52
+ query.save
53
+ query
54
+ end
55
+
56
+ def find(id)
57
+ Oculus.data_store.load_query(id)
58
+ end
59
+ end
60
+ end
61
+ end