oculus 0.5.0

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 (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