oculus 0.5.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -0
- data/Rakefile +25 -0
- data/TODO.md +0 -1
- data/features/step_definitions/query_steps.rb +5 -1
- data/features/support/env.rb +2 -1
- data/lib/oculus/presenters/query_presenter.rb +28 -2
- data/lib/oculus/query.rb +36 -7
- data/lib/oculus/server/public/css/style.css +12 -0
- data/lib/oculus/server/public/js/application.js +16 -0
- data/lib/oculus/server/views/history.erb +1 -7
- data/lib/oculus/server/views/layout.erb +3 -0
- data/lib/oculus/server/views/show.erb +46 -18
- data/lib/oculus/server/views/starred.erb +29 -0
- data/lib/oculus/server.rb +23 -6
- data/lib/oculus/storage/file_store.rb +28 -5
- data/lib/oculus/version.rb +1 -1
- data/spec/connection_spec.rb +2 -21
- data/spec/file_store_spec.rb +45 -20
- data/spec/query_presenter_spec.rb +14 -3
- data/spec/query_spec.rb +60 -16
- metadata +17 -16
data/.travis.yml
CHANGED
data/Rakefile
CHANGED
@@ -3,6 +3,7 @@ require "bundler/gem_tasks"
|
|
3
3
|
|
4
4
|
require 'rspec/core/rake_task'
|
5
5
|
require 'cucumber/rake/task'
|
6
|
+
require 'mysql2'
|
6
7
|
|
7
8
|
desc 'Run RSpec tests'
|
8
9
|
RSpec::Core::RakeTask.new(:spec) do |task|
|
@@ -15,4 +16,28 @@ Cucumber::Rake::Task.new(:cucumber) do |task|
|
|
15
16
|
task.cucumber_opts = 'features --format pretty'
|
16
17
|
end
|
17
18
|
|
19
|
+
namespace :db do
|
20
|
+
namespace :test do
|
21
|
+
desc "Populate the test database"
|
22
|
+
task :populate do
|
23
|
+
client = Mysql2::Client.new(:host => "localhost", :username => "root")
|
24
|
+
client.query "CREATE DATABASE IF NOT EXISTS oculus_test"
|
25
|
+
client.query "USE oculus_test"
|
26
|
+
client.query %[
|
27
|
+
CREATE TABLE IF NOT EXISTS oculus_users (
|
28
|
+
id MEDIUMINT NOT NULL AUTO_INCREMENT,
|
29
|
+
name VARCHAR(255),
|
30
|
+
PRIMARY KEY (id)
|
31
|
+
);
|
32
|
+
]
|
33
|
+
|
34
|
+
client.query 'TRUNCATE oculus_users'
|
35
|
+
|
36
|
+
client.query %[
|
37
|
+
INSERT INTO oculus_users (name) VALUES ('Paul'), ('Amy'), ('Peter')
|
38
|
+
]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
18
43
|
task :default => [:spec, :cucumber]
|
data/TODO.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
Given /^a query is cached with results:$/ do |results|
|
2
|
-
Oculus::Query.create(:name
|
2
|
+
Oculus::Query.create(:name => "all users",
|
3
|
+
:query => "SELECT * FROM oculus_users",
|
4
|
+
:results => results.raw,
|
5
|
+
:started_at => Time.now,
|
6
|
+
:finished_at => Time.now)
|
3
7
|
end
|
4
8
|
|
5
9
|
Given /^I am on the history page$/ do
|
data/features/support/env.rb
CHANGED
@@ -5,12 +5,13 @@ require 'oculus/server'
|
|
5
5
|
require 'capybara/cucumber'
|
6
6
|
|
7
7
|
Capybara.app = Oculus::Server
|
8
|
+
Capybara.default_wait_time = 10
|
8
9
|
|
9
10
|
Oculus.cache_path = 'tmp/test_cache'
|
10
11
|
Oculus.connection_options = {
|
11
12
|
:host => 'localhost',
|
12
13
|
:username => 'root',
|
13
|
-
:database => '
|
14
|
+
:database => 'oculus_test'
|
14
15
|
}
|
15
16
|
|
16
17
|
Before do
|
@@ -3,8 +3,34 @@ require 'delegate'
|
|
3
3
|
module Oculus
|
4
4
|
module Presenters
|
5
5
|
class QueryPresenter < SimpleDelegator
|
6
|
-
def
|
7
|
-
|
6
|
+
def formatted_start_time
|
7
|
+
started_at.strftime("%Y-%m-%d %I:%M %p") if started_at
|
8
|
+
end
|
9
|
+
|
10
|
+
def formatted_finish_time
|
11
|
+
finished_at.strftime("%Y-%m-%d %I:%M %p") if finished_at
|
12
|
+
end
|
13
|
+
|
14
|
+
def elapsed_time
|
15
|
+
return "" unless started_at && finished_at
|
16
|
+
|
17
|
+
seconds = (finished_at - started_at).round
|
18
|
+
|
19
|
+
if seconds < 60
|
20
|
+
"#{seconds} seconds"
|
21
|
+
else
|
22
|
+
minutes = (seconds / 60).floor
|
23
|
+
seconds %= 60
|
24
|
+
|
25
|
+
if minutes < 60
|
26
|
+
"#{minutes} minutes #{seconds} seconds"
|
27
|
+
else
|
28
|
+
hours = (minutes / 60).floor
|
29
|
+
minutes %= 60
|
30
|
+
|
31
|
+
"#{hours} hours #{minutes} minutes"
|
32
|
+
end
|
33
|
+
end
|
8
34
|
end
|
9
35
|
|
10
36
|
def status
|
data/lib/oculus/query.rb
CHANGED
@@ -6,7 +6,9 @@ module Oculus
|
|
6
6
|
attr_accessor :query
|
7
7
|
attr_accessor :results
|
8
8
|
attr_accessor :error
|
9
|
-
attr_accessor :
|
9
|
+
attr_accessor :started_at
|
10
|
+
attr_accessor :finished_at
|
11
|
+
attr_accessor :starred
|
10
12
|
attr_accessor :thread_id
|
11
13
|
|
12
14
|
def initialize(attributes = {})
|
@@ -20,7 +22,9 @@ module Oculus
|
|
20
22
|
:name => name,
|
21
23
|
:author => author,
|
22
24
|
:query => query,
|
23
|
-
:
|
25
|
+
:started_at => started_at,
|
26
|
+
:finished_at => finished_at,
|
27
|
+
:starred => starred,
|
24
28
|
:thread_id => thread_id
|
25
29
|
}
|
26
30
|
attrs[:error] = error if error
|
@@ -28,24 +32,40 @@ module Oculus
|
|
28
32
|
end
|
29
33
|
|
30
34
|
def execute(connection)
|
31
|
-
self.
|
35
|
+
self.started_at = Time.now
|
36
|
+
self.thread_id = connection.thread_id
|
37
|
+
self.save
|
38
|
+
results = connection.execute(query)
|
32
39
|
rescue Connection::Error => e
|
33
|
-
|
40
|
+
error = e.message
|
41
|
+
ensure
|
42
|
+
reload
|
43
|
+
self.results = results if results
|
44
|
+
self.error = error if error
|
45
|
+
self.finished_at = Time.now
|
46
|
+
self.save
|
34
47
|
end
|
35
48
|
|
36
49
|
def save
|
37
|
-
@date = Time.now
|
38
50
|
Oculus.data_store.save_query(self)
|
39
51
|
end
|
40
52
|
|
41
53
|
def complete?
|
42
|
-
!!
|
54
|
+
!!finished_at
|
43
55
|
end
|
44
56
|
|
45
57
|
def succeeded?
|
46
58
|
complete? && !error
|
47
59
|
end
|
48
60
|
|
61
|
+
def to_csv
|
62
|
+
CSV.generate do |csv|
|
63
|
+
results.each do |row|
|
64
|
+
csv << row
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
49
69
|
class << self
|
50
70
|
def create(attributes)
|
51
71
|
query = new(attributes)
|
@@ -54,8 +74,17 @@ module Oculus
|
|
54
74
|
end
|
55
75
|
|
56
76
|
def find(id)
|
57
|
-
Oculus.data_store.load_query(id)
|
77
|
+
new(Oculus.data_store.load_query(id))
|
58
78
|
end
|
59
79
|
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def reload
|
84
|
+
Oculus.data_store.load_query(id).each do |attr, value|
|
85
|
+
send("#{attr}=", value)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
60
89
|
end
|
61
90
|
end
|
@@ -102,6 +102,18 @@ body {
|
|
102
102
|
margin-top: 1em;
|
103
103
|
}
|
104
104
|
|
105
|
+
button.star .starred-contents {
|
106
|
+
display: none;
|
107
|
+
}
|
108
|
+
|
109
|
+
button.starred .starred-contents {
|
110
|
+
display: inline;
|
111
|
+
}
|
112
|
+
|
113
|
+
button.starred .unstarred-contents {
|
114
|
+
display: none;
|
115
|
+
}
|
116
|
+
|
105
117
|
#query-properties-form {
|
106
118
|
display: none;
|
107
119
|
}
|
@@ -32,6 +32,22 @@ $(function() {
|
|
32
32
|
});
|
33
33
|
});
|
34
34
|
|
35
|
+
$('#query-actions .star').click(function() {
|
36
|
+
var btn = $(this),
|
37
|
+
newState = !btn.hasClass('starred');
|
38
|
+
|
39
|
+
$.ajax({
|
40
|
+
url: this.href,
|
41
|
+
type: 'PUT',
|
42
|
+
data: {
|
43
|
+
'starred': newState
|
44
|
+
},
|
45
|
+
success: function() {
|
46
|
+
btn.toggleClass('starred', newState);
|
47
|
+
}
|
48
|
+
});
|
49
|
+
});
|
50
|
+
|
35
51
|
$('#query-actions .edit').click(function() {
|
36
52
|
$('#query-properties-form').toggle();
|
37
53
|
});
|
@@ -13,7 +13,7 @@
|
|
13
13
|
<% @queries.each do |query| %>
|
14
14
|
<tr>
|
15
15
|
<td class="status"><span class="indicator indicator-<%= query.status %>">*</span></td>
|
16
|
-
<td class="date"><%= query.
|
16
|
+
<td class="date"><%= query.formatted_start_time %></td>
|
17
17
|
<td>
|
18
18
|
<a href="/queries/<%= query.id %>"><%= query.description %></a>
|
19
19
|
</td>
|
@@ -27,9 +27,3 @@
|
|
27
27
|
<% end %>
|
28
28
|
</tbody>
|
29
29
|
</table>
|
30
|
-
|
31
|
-
<script type="text/javascript">
|
32
|
-
CodeMirror.fromTextArea(document.getElementById('query-field'), {
|
33
|
-
tabSize: 2
|
34
|
-
});
|
35
|
-
</script>
|
@@ -20,6 +20,9 @@
|
|
20
20
|
<li<% if @current_tab == 'home' %> class="active"<% end %>>
|
21
21
|
<a href="/">Home</a>
|
22
22
|
</li>
|
23
|
+
<li<% if @current_tab == 'starred' %> class="active"<% end %>>
|
24
|
+
<a href="/starred">Starred</a>
|
25
|
+
</li>
|
23
26
|
<li<% if @current_tab == 'history' %> class="active"<% end %>>
|
24
27
|
<a href="/history">History</a>
|
25
28
|
</li>
|
@@ -1,17 +1,35 @@
|
|
1
1
|
<h1 class="description"><%= @query.description %></h1>
|
2
2
|
|
3
3
|
<div id="query-actions">
|
4
|
+
<button class="btn btn-success star<% if @query.starred %> starred<% end %>">
|
5
|
+
<span class="unstarred-contents">
|
6
|
+
<i class="icon-star-empty icon-white"></i>
|
7
|
+
Star
|
8
|
+
</span>
|
9
|
+
<span class="starred-contents">
|
10
|
+
<i class="icon-star icon-white"></i>
|
11
|
+
Starred
|
12
|
+
</span>
|
13
|
+
</button>
|
4
14
|
<button class="btn edit<% unless @query.named? %> active<% end %>" data-toggle="button">
|
5
15
|
<i class="icon-pencil"></i>
|
6
16
|
Edit
|
7
17
|
</button>
|
18
|
+
<a class="btn file<% unless @query.succeeded? && @headers %> disabled<% end %>" href="/queries/<%= @query.id %>/download">
|
19
|
+
<i class="icon-file"></i>
|
20
|
+
Download
|
21
|
+
</a>
|
8
22
|
</div>
|
9
23
|
|
10
24
|
<dl class="dl-horizontal query-properties">
|
11
25
|
<dt>Author</dt>
|
12
26
|
<dd class="author"><%= @query.author %> </dd>
|
13
|
-
<dt>
|
14
|
-
<dd><%= @query.
|
27
|
+
<dt>Started at</dt>
|
28
|
+
<dd><%= @query.formatted_start_time %></dd>
|
29
|
+
<% if @query.finished_at %>
|
30
|
+
<dt>Finished at</dt>
|
31
|
+
<dd><%= @query.formatted_finish_time %> (<%= @query.elapsed_time %>)</dd>
|
32
|
+
<% end %>
|
15
33
|
</dl>
|
16
34
|
|
17
35
|
<form class="well form-inline" id="query-properties-form">
|
@@ -23,24 +41,31 @@
|
|
23
41
|
<pre class="cm-s-default"><%= @query.query %></pre>
|
24
42
|
|
25
43
|
<% if @query.succeeded? %>
|
26
|
-
|
27
|
-
<
|
28
|
-
<
|
29
|
-
<% @headers.each do |label| %>
|
30
|
-
<th><%= label %></th>
|
31
|
-
<% end %>
|
32
|
-
</tr>
|
33
|
-
</thead>
|
34
|
-
<tbody class="results">
|
35
|
-
<% @results.each do |result| %>
|
44
|
+
<% if @headers %>
|
45
|
+
<table class="table table-condensed" id="results-table">
|
46
|
+
<thead>
|
36
47
|
<tr>
|
37
|
-
<%
|
38
|
-
<
|
48
|
+
<% @headers.each do |label| %>
|
49
|
+
<th><%= label %></th>
|
39
50
|
<% end %>
|
40
51
|
</tr>
|
41
|
-
|
42
|
-
|
43
|
-
|
52
|
+
</thead>
|
53
|
+
<tbody class="results">
|
54
|
+
<% @results.each do |result| %>
|
55
|
+
<tr>
|
56
|
+
<% result.each do |value| %>
|
57
|
+
<td><%= value %></td>
|
58
|
+
<% end %>
|
59
|
+
</tr>
|
60
|
+
<% end %>
|
61
|
+
</tbody>
|
62
|
+
</table>
|
63
|
+
<% else %>
|
64
|
+
<div class="alert alert-success">
|
65
|
+
<strong>Heads Up!</strong>
|
66
|
+
This query ran successfully, but returned no results.
|
67
|
+
</div>
|
68
|
+
<% end %>
|
44
69
|
<% elsif @query.error %>
|
45
70
|
<div class="alert alert-error">
|
46
71
|
<strong>Sorry!</strong>
|
@@ -48,7 +73,7 @@
|
|
48
73
|
</div>
|
49
74
|
<% else %>
|
50
75
|
<div class="alert alert-info">
|
51
|
-
<strong>
|
76
|
+
<strong>Heads Up!</strong>
|
52
77
|
This query is in progress, and has not returned any results yet.
|
53
78
|
</div>
|
54
79
|
<% end %>
|
@@ -56,4 +81,7 @@
|
|
56
81
|
<script type="text/javascript">
|
57
82
|
var queryNode = $('pre.cm-s-default');
|
58
83
|
CodeMirror.runMode(queryNode.text(), 'mysql', queryNode[0]);
|
84
|
+
$('a.disabled').click(function(e) {
|
85
|
+
e.preventDefault();
|
86
|
+
});
|
59
87
|
</script>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<% @current_tab = 'starred' %>
|
2
|
+
|
3
|
+
<h2>Starred Queries</h2>
|
4
|
+
<table class="table">
|
5
|
+
<thead>
|
6
|
+
<th></th>
|
7
|
+
<th>Timestamp</th>
|
8
|
+
<th>Query</th>
|
9
|
+
<th>Author</th>
|
10
|
+
<th></th>
|
11
|
+
</thead>
|
12
|
+
<tbody id="history">
|
13
|
+
<% @queries.each do |query| %>
|
14
|
+
<tr>
|
15
|
+
<td class="status"><span class="indicator indicator-<%= query.status %>">*</span></td>
|
16
|
+
<td class="date"><%= query.formatted_start_time %></td>
|
17
|
+
<td>
|
18
|
+
<a href="/queries/<%= query.id %>"><%= query.description %></a>
|
19
|
+
</td>
|
20
|
+
<td class="author">
|
21
|
+
<%= query.author %>
|
22
|
+
</td>
|
23
|
+
<td>
|
24
|
+
<a class="delete" href="/queries/<%= query.id %>"><i class="icon-remove"></i></a>
|
25
|
+
</td>
|
26
|
+
</tr>
|
27
|
+
<% end %>
|
28
|
+
</tbody>
|
29
|
+
</table>
|
data/lib/oculus/server.rb
CHANGED
@@ -21,6 +21,12 @@ module Oculus
|
|
21
21
|
erb :index
|
22
22
|
end
|
23
23
|
|
24
|
+
get '/starred' do
|
25
|
+
@queries = Oculus.data_store.starred_queries.map { |q| Oculus::Presenters::QueryPresenter.new(q) }
|
26
|
+
|
27
|
+
erb :starred
|
28
|
+
end
|
29
|
+
|
24
30
|
get '/history' do
|
25
31
|
@queries = Oculus.data_store.all_queries.map { |q| Oculus::Presenters::QueryPresenter.new(q) }
|
26
32
|
|
@@ -35,13 +41,12 @@ module Oculus
|
|
35
41
|
end
|
36
42
|
|
37
43
|
post '/queries' do
|
38
|
-
|
39
|
-
query = Oculus::Query.create(:query => params[:query],
|
40
|
-
:thread_id => connection.thread_id)
|
44
|
+
query = Oculus::Query.create(:query => params[:query])
|
41
45
|
|
42
46
|
pid = fork do
|
47
|
+
query = Oculus::Query.find(query.id)
|
48
|
+
connection = Oculus::Connection::Mysql2.new(Oculus.connection_options)
|
43
49
|
query.execute(connection)
|
44
|
-
query.save
|
45
50
|
end
|
46
51
|
|
47
52
|
Process.detach(pid)
|
@@ -67,14 +72,26 @@ module Oculus
|
|
67
72
|
erb :show
|
68
73
|
end
|
69
74
|
|
75
|
+
get '/queries/:id/download' do
|
76
|
+
query = Oculus::Query.find(params[:id])
|
77
|
+
timestamp = query.started_at.strftime('%Y%m%d%H%M')
|
78
|
+
|
79
|
+
attachment "#{timestamp}-query-#{query.id}-results.csv"
|
80
|
+
content_type "text/csv"
|
81
|
+
last_modified query.finished_at
|
82
|
+
|
83
|
+
query.to_csv
|
84
|
+
end
|
85
|
+
|
70
86
|
get '/queries/:id/status' do
|
71
87
|
Oculus::Presenters::QueryPresenter.new(Oculus::Query.find(params[:id])).status
|
72
88
|
end
|
73
89
|
|
74
90
|
put '/queries/:id' do
|
75
91
|
@query = Oculus::Query.find(params[:id])
|
76
|
-
@query.name
|
77
|
-
@query.author
|
92
|
+
@query.name = params[:name] if params[:name]
|
93
|
+
@query.author = params[:author] if params[:author]
|
94
|
+
@query.starred = params[:starred] == "true" if params[:starred]
|
78
95
|
@query.save
|
79
96
|
|
80
97
|
puts "true"
|
@@ -10,7 +10,13 @@ module Oculus
|
|
10
10
|
|
11
11
|
def all_queries
|
12
12
|
Dir["#{root}/*.query"].map do |path|
|
13
|
-
File.parse(path)
|
13
|
+
Query.new(File.parse(path))
|
14
|
+
end.sort { |a,b| b.id <=> a.id }
|
15
|
+
end
|
16
|
+
|
17
|
+
def starred_queries
|
18
|
+
Dir["#{root}/starred/*.query"].map do |path|
|
19
|
+
Query.new(File.parse(path))
|
14
20
|
end.sort { |a,b| b.id <=> a.id }
|
15
21
|
end
|
16
22
|
|
@@ -19,7 +25,16 @@ module Oculus
|
|
19
25
|
|
20
26
|
File.open(filename_for_id(query.id), 'w') do |file|
|
21
27
|
file.write_prelude(query.attributes)
|
22
|
-
file.write_results(query.results) if query.results
|
28
|
+
file.write_results(query.results) if query.results && query.results.length > 0
|
29
|
+
end
|
30
|
+
|
31
|
+
FileUtils.mkdir_p(File.join(root, "starred")) unless Dir.exist?(File.join(root, "starred"))
|
32
|
+
star_path = starred_filename_for_id(query.id)
|
33
|
+
|
34
|
+
if query.starred
|
35
|
+
File.symlink(File.expand_path(filename_for_id(query.id)), star_path) unless File.exist?(star_path)
|
36
|
+
elsif File.exist?(star_path)
|
37
|
+
File.unlink(star_path)
|
23
38
|
end
|
24
39
|
end
|
25
40
|
|
@@ -34,6 +49,9 @@ module Oculus
|
|
34
49
|
end
|
35
50
|
|
36
51
|
def delete_query(id)
|
52
|
+
star_path = starred_filename_for_id(id)
|
53
|
+
File.unlink(star_path) if File.exist?(star_path)
|
54
|
+
|
37
55
|
path = filename_for_id(id)
|
38
56
|
|
39
57
|
if File.exist?(path)
|
@@ -50,9 +68,9 @@ module Oculus
|
|
50
68
|
file = File.open(path)
|
51
69
|
attributes = file.attributes
|
52
70
|
attributes[:results] = file.results
|
53
|
-
|
54
|
-
|
55
|
-
|
71
|
+
attributes[:id] = File.basename(path).split('.').first.to_i
|
72
|
+
attributes[:starred] ||= false
|
73
|
+
attributes
|
56
74
|
end
|
57
75
|
|
58
76
|
def write_prelude(attributes)
|
@@ -93,6 +111,11 @@ module Oculus
|
|
93
111
|
end
|
94
112
|
end
|
95
113
|
|
114
|
+
def starred_filename_for_id(id)
|
115
|
+
raise ArgumentError unless id.is_a?(Integer) || id =~ /^[0-9]+/
|
116
|
+
File.join(root, "starred", "#{id}.query")
|
117
|
+
end
|
118
|
+
|
96
119
|
def filename_for_id(id)
|
97
120
|
raise ArgumentError unless id.is_a?(Integer) || id =~ /^[0-9]+/
|
98
121
|
File.join(root, "#{id}.query")
|
data/lib/oculus/version.rb
CHANGED
data/spec/connection_spec.rb
CHANGED
@@ -1,33 +1,14 @@
|
|
1
1
|
require 'oculus'
|
2
2
|
|
3
3
|
describe Oculus::Connection do
|
4
|
-
|
5
|
-
client = Mysql2::Client.new(:host => "localhost", :username => "root")
|
6
|
-
client.query "CREATE DATABASE IF NOT EXISTS test"
|
7
|
-
client.query "USE test"
|
8
|
-
client.query %[
|
9
|
-
CREATE TABLE IF NOT EXISTS oculus_users (
|
10
|
-
id MEDIUMINT NOT NULL AUTO_INCREMENT,
|
11
|
-
name VARCHAR(255),
|
12
|
-
PRIMARY KEY (id)
|
13
|
-
);
|
14
|
-
]
|
15
|
-
|
16
|
-
client.query 'TRUNCATE oculus_users'
|
17
|
-
|
18
|
-
client.query %[
|
19
|
-
INSERT INTO oculus_users (name) VALUES ('Paul'), ('Amy'), ('Peter')
|
20
|
-
]
|
21
|
-
end
|
22
|
-
|
23
|
-
subject { Oculus::Connection::Mysql2.new(:database => 'test') }
|
4
|
+
subject { Oculus::Connection::Mysql2.new(:host => 'localhost', :database => 'oculus_test', :username => 'root') }
|
24
5
|
|
25
6
|
it "fetches a result set" do
|
26
7
|
subject.execute("SELECT * FROM oculus_users").should == [['id', 'name'], [1, 'Paul'], [2, 'Amy'], [3, 'Peter']]
|
27
8
|
end
|
28
9
|
|
29
10
|
it "returns nil for queries that don't return result sets" do
|
30
|
-
query_connection = Mysql2::Client.new(:host => "localhost", :database => "
|
11
|
+
query_connection = Mysql2::Client.new(:host => "localhost", :database => "oculus_test", :username => "root")
|
31
12
|
thread_id = query_connection.thread_id
|
32
13
|
Thread.new {
|
33
14
|
query_connection.execute("SELECT * FROM oculus_users WHERE SLEEP(2)")
|
data/spec/file_store_spec.rb
CHANGED
@@ -38,33 +38,48 @@ describe Oculus::Storage::FileStore do
|
|
38
38
|
it "round-trips a query with no results to disk" do
|
39
39
|
query = Oculus::Query.new(:name => "Unfinished query", :author => "Me")
|
40
40
|
subject.save_query(query)
|
41
|
-
subject.load_query(query.id).
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
41
|
+
subject.load_query(query.id).should == {
|
42
|
+
:id => query.id,
|
43
|
+
:name => query.name,
|
44
|
+
:author => query.author,
|
45
|
+
:query => query.query,
|
46
|
+
:results => [],
|
47
|
+
:thread_id => query.thread_id,
|
48
|
+
:starred => false,
|
49
|
+
:started_at => query.started_at,
|
50
|
+
:finished_at => query.finished_at
|
51
|
+
}
|
47
52
|
end
|
48
53
|
|
49
54
|
it "round-trips a query with an error to disk" do
|
50
55
|
subject.save_query(broken_query)
|
51
|
-
subject.load_query(broken_query.id).
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
56
|
+
subject.load_query(broken_query.id).should == {
|
57
|
+
:id => broken_query.id,
|
58
|
+
:name => broken_query.name,
|
59
|
+
:error => broken_query.error,
|
60
|
+
:author => broken_query.author,
|
61
|
+
:query => broken_query.query,
|
62
|
+
:results => [],
|
63
|
+
:thread_id => broken_query.thread_id,
|
64
|
+
:starred => false,
|
65
|
+
:started_at => broken_query.started_at,
|
66
|
+
:finished_at => broken_query.finished_at
|
67
|
+
}
|
58
68
|
end
|
59
69
|
|
60
70
|
it "round-trips a query to disk" do
|
61
71
|
subject.save_query(query)
|
62
|
-
subject.load_query(query.id).
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
72
|
+
subject.load_query(query.id).should == {
|
73
|
+
:id => query.id,
|
74
|
+
:name => query.name,
|
75
|
+
:author => query.author,
|
76
|
+
:query => query.query,
|
77
|
+
:results => query.results,
|
78
|
+
:thread_id => query.thread_id,
|
79
|
+
:starred => false,
|
80
|
+
:started_at => query.started_at,
|
81
|
+
:finished_at => query.finished_at
|
82
|
+
}
|
68
83
|
end
|
69
84
|
|
70
85
|
it "doesn't overwrite an existing query id when saving" do
|
@@ -87,9 +102,19 @@ describe Oculus::Storage::FileStore do
|
|
87
102
|
subject.all_queries.map(&:results).should == [other_query.results, query.results]
|
88
103
|
end
|
89
104
|
|
105
|
+
it "fetches starred queries" do
|
106
|
+
query.starred = true
|
107
|
+
subject.save_query(query)
|
108
|
+
subject.save_query(other_query)
|
109
|
+
|
110
|
+
results = subject.starred_queries
|
111
|
+
results.map(&:results).should == [query.results]
|
112
|
+
results.first.starred.should be true
|
113
|
+
end
|
114
|
+
|
90
115
|
it "deletes queries" do
|
91
116
|
subject.save_query(query)
|
92
|
-
subject.load_query(query.id)
|
117
|
+
subject.load_query(query.id)[:name].should == query.name
|
93
118
|
subject.delete_query(query.id)
|
94
119
|
|
95
120
|
lambda {
|
@@ -10,9 +10,20 @@ describe Oculus::Presenters::QueryPresenter do
|
|
10
10
|
presenter.description.should == 'foo'
|
11
11
|
end
|
12
12
|
|
13
|
-
it "has a formatted
|
14
|
-
query.
|
15
|
-
presenter.
|
13
|
+
it "has a formatted start time" do
|
14
|
+
query.started_at = Time.mktime(2010, 1, 1, 12, 34)
|
15
|
+
presenter.formatted_start_time.should == '2010-01-01 12:34 PM'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "has a formatted finish time" do
|
19
|
+
query.finished_at = Time.mktime(2010, 1, 1, 12, 34)
|
20
|
+
presenter.formatted_finish_time.should == '2010-01-01 12:34 PM'
|
21
|
+
end
|
22
|
+
|
23
|
+
it "has an elapsed time" do
|
24
|
+
query.started_at = Time.mktime(2010, 1, 1, 10, 30)
|
25
|
+
query.finished_at = Time.mktime(2010, 1, 1, 12, 34)
|
26
|
+
presenter.elapsed_time.should == '2 hours 4 minutes'
|
16
27
|
end
|
17
28
|
|
18
29
|
it "reports successful queries" do
|
data/spec/query_spec.rb
CHANGED
@@ -2,25 +2,25 @@ require 'oculus'
|
|
2
2
|
|
3
3
|
describe Oculus::Query do
|
4
4
|
before do
|
5
|
-
Oculus.data_store = stub
|
5
|
+
Oculus.data_store = stub(:load_query => {}, :save_query => nil)
|
6
6
|
end
|
7
7
|
|
8
8
|
it "runs the query against the supplied connection" do
|
9
|
-
connection = stub
|
9
|
+
connection = stub(:thread_id => 42)
|
10
10
|
query = Oculus::Query.new(:query => 'SELECT * FROM users')
|
11
11
|
connection.should_receive(:execute).with('SELECT * FROM users')
|
12
12
|
query.execute(connection)
|
13
13
|
end
|
14
14
|
|
15
15
|
it "stores the results of running the query" do
|
16
|
-
connection = stub(:execute => [['id', 'name'], [1, 'Paul']])
|
16
|
+
connection = stub(:execute => [['id', 'name'], [1, 'Paul']], :thread_id => 42)
|
17
17
|
query = Oculus::Query.new(:query => 'SELECT * FROM users')
|
18
18
|
query.execute(connection)
|
19
19
|
query.results.should == [['id', 'name'], [1, 'Paul']]
|
20
20
|
end
|
21
21
|
|
22
22
|
it "stores errors when queries fail" do
|
23
|
-
connection = stub
|
23
|
+
connection = stub(:thread_id => 42)
|
24
24
|
query = Oculus::Query.new(:query => 'SELECT * FROM users')
|
25
25
|
connection.stub(:execute).and_raise(Oculus::Connection::Error.new('You have an error in your SQL syntax'))
|
26
26
|
query.execute(connection)
|
@@ -37,16 +37,39 @@ describe Oculus::Query do
|
|
37
37
|
query.thread_id.should == 42
|
38
38
|
end
|
39
39
|
|
40
|
-
it "has a
|
40
|
+
it "has a start time" do
|
41
41
|
query = Oculus::Query.new
|
42
|
-
query.
|
42
|
+
query.started_at.should be nil
|
43
43
|
end
|
44
44
|
|
45
|
-
it "
|
46
|
-
Oculus.
|
45
|
+
it "has a finish time" do
|
46
|
+
query = Oculus::Query.new
|
47
|
+
query.finished_at.should be nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it "updates start time when executing" do
|
47
51
|
Time.stub(:now).and_return(now = stub)
|
48
|
-
|
49
|
-
query.
|
52
|
+
connection = stub(:execute => [['id', 'name'], [1, 'Paul']], :thread_id => 42)
|
53
|
+
query = Oculus::Query.new(:query => 'SELECT * FROM users')
|
54
|
+
query.execute(connection)
|
55
|
+
query.started_at.should == now
|
56
|
+
end
|
57
|
+
|
58
|
+
it "updates finish time when executing" do
|
59
|
+
Time.stub(:now).and_return(now = stub)
|
60
|
+
connection = stub(:execute => [['id', 'name'], [1, 'Paul']], :thread_id => 42)
|
61
|
+
query = Oculus::Query.new(:query => 'SELECT * FROM users')
|
62
|
+
query.execute(connection)
|
63
|
+
query.finished_at.should == now
|
64
|
+
end
|
65
|
+
|
66
|
+
it "updates finish time when execution fails" do
|
67
|
+
Time.stub(:now).and_return(now = stub)
|
68
|
+
connection = stub(:thread_id => 42)
|
69
|
+
connection.stub(:execute).and_raise(Oculus::Connection::Error.new('You have an error in your SQL syntax'))
|
70
|
+
query = Oculus::Query.new(:query => 'SELECT * FROM users')
|
71
|
+
query.execute(connection)
|
72
|
+
query.finished_at.should == now
|
50
73
|
end
|
51
74
|
|
52
75
|
it "has a name" do
|
@@ -59,6 +82,11 @@ describe Oculus::Query do
|
|
59
82
|
query.author.should == 'Paul'
|
60
83
|
end
|
61
84
|
|
85
|
+
it "can be starred" do
|
86
|
+
query = Oculus::Query.new(:starred => true)
|
87
|
+
query.starred.should == true
|
88
|
+
end
|
89
|
+
|
62
90
|
it "stores new queries in the data store on creation" do
|
63
91
|
Oculus.data_store.should_receive(:save_query)
|
64
92
|
query = Oculus::Query.create(:results => [['id', 'name'], [1, 'Paul']])
|
@@ -75,18 +103,23 @@ describe Oculus::Query do
|
|
75
103
|
Oculus::Query.find(1)
|
76
104
|
end
|
77
105
|
|
78
|
-
it "is not complete when
|
106
|
+
it "is not complete when it has not been executed" do
|
79
107
|
query = Oculus::Query.new(:query => 'SELECT * FROM users')
|
80
108
|
query.should_not be_complete
|
81
109
|
end
|
82
110
|
|
83
|
-
it "is complete when
|
84
|
-
|
111
|
+
it "is complete when it has been executed" do
|
112
|
+
connection = stub(:execute => [['id', 'name'], [1, 'Paul']], :thread_id => 42)
|
113
|
+
query = Oculus::Query.new
|
114
|
+
query.execute(connection)
|
85
115
|
query.should be_complete
|
86
116
|
end
|
87
117
|
|
88
118
|
it "is complete when there is an error" do
|
89
|
-
|
119
|
+
connection = stub(:thread_id => 42)
|
120
|
+
connection.stub(:execute).and_raise(Oculus::Connection::Error.new('You have an error in your SQL syntax'))
|
121
|
+
query = Oculus::Query.new
|
122
|
+
query.execute(connection)
|
90
123
|
query.should be_complete
|
91
124
|
end
|
92
125
|
|
@@ -96,12 +129,23 @@ describe Oculus::Query do
|
|
96
129
|
end
|
97
130
|
|
98
131
|
it "is successful when results are present" do
|
99
|
-
|
132
|
+
connection = stub(:execute => [['id', 'name'], [1, 'Paul']], :thread_id => 42)
|
133
|
+
query = Oculus::Query.new
|
134
|
+
query.execute(connection)
|
100
135
|
query.succeeded?.should be true
|
101
136
|
end
|
102
137
|
|
103
138
|
it "is not successful when there is an error" do
|
104
|
-
|
139
|
+
connection = stub(:thread_id => 42)
|
140
|
+
connection.stub(:execute).and_raise(Oculus::Connection::Error.new('You have an error in your SQL syntax'))
|
141
|
+
query = Oculus::Query.new
|
142
|
+
query.execute(connection)
|
105
143
|
query.succeeded?.should be false
|
106
144
|
end
|
145
|
+
|
146
|
+
it "exports to CSV" do
|
147
|
+
query = Oculus::Query.new
|
148
|
+
query.results = [['id', 'name'], [1, 'Paul']]
|
149
|
+
query.to_csv.should == "id,name\n1,Paul\n"
|
150
|
+
end
|
107
151
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: oculus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-05-
|
12
|
+
date: 2012-05-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sinatra
|
16
|
-
requirement: &
|
16
|
+
requirement: &70114293249540 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 1.3.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70114293249540
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: mysql2
|
27
|
-
requirement: &
|
27
|
+
requirement: &70114293249020 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 0.3.11
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70114293249020
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: vegas
|
38
|
-
requirement: &
|
38
|
+
requirement: &70114293248540 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 0.1.4
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70114293248540
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rake
|
49
|
-
requirement: &
|
49
|
+
requirement: &70114288904480 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70114288904480
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: cucumber
|
60
|
-
requirement: &
|
60
|
+
requirement: &70114288903580 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '1'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70114288903580
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
|
-
requirement: &
|
71
|
+
requirement: &70114288902160 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '2'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70114288902160
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: capybara
|
82
|
-
requirement: &
|
82
|
+
requirement: &70114288901540 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,7 +87,7 @@ dependencies:
|
|
87
87
|
version: '1'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *70114288901540
|
91
91
|
description: Oculus is a web-based logging SQL client. It keeps a history of your
|
92
92
|
queries and the results they returned, so your research is always at hand, easy
|
93
93
|
to share and easy to repeat or reproduce in the future.
|
@@ -131,6 +131,7 @@ files:
|
|
131
131
|
- lib/oculus/server/views/index.erb
|
132
132
|
- lib/oculus/server/views/layout.erb
|
133
133
|
- lib/oculus/server/views/show.erb
|
134
|
+
- lib/oculus/server/views/starred.erb
|
134
135
|
- lib/oculus/storage.rb
|
135
136
|
- lib/oculus/storage/file_store.rb
|
136
137
|
- lib/oculus/version.rb
|