adhoq 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +45 -12
- data/app/assets/javascripts/adhoq/application.js +1 -0
- data/app/assets/javascripts/adhoq/current_tables.js.coffee +4 -0
- data/app/assets/stylesheets/adhoq/adhoq.css.sass +44 -0
- data/app/assets/stylesheets/adhoq/bootstrap.scss +1 -1
- data/app/controllers/adhoq/authorization_methods.rb +1 -1
- data/app/controllers/adhoq/current_tables_controller.rb +20 -0
- data/app/controllers/adhoq/executions_controller.rb +2 -2
- data/app/helpers/adhoq/application_helper.rb +13 -0
- data/app/models/adhoq/execution.rb +1 -1
- data/app/models/adhoq/report.rb +10 -17
- data/app/views/adhoq/application/_global_nav.html.slim +1 -0
- data/app/views/adhoq/current_tables/index.html.slim +32 -0
- data/app/views/adhoq/queries/_form.html.slim +29 -9
- data/app/views/adhoq/queries/_query.html.slim +1 -1
- data/config/routes.rb +2 -1
- data/lib/adhoq.rb +7 -6
- data/lib/adhoq/adhoc_execution.rb +11 -0
- data/lib/adhoq/executor.rb +12 -1
- data/lib/adhoq/global_variable.rb +8 -5
- data/lib/adhoq/reporter.rb +14 -0
- data/lib/adhoq/storage.rb +12 -1
- data/lib/adhoq/storage/fog_storage.rb +22 -0
- data/lib/adhoq/storage/local_file.rb +10 -29
- data/lib/adhoq/storage/s3.rb +24 -0
- data/lib/adhoq/version.rb +1 -1
- data/spec/adhoq/executor_spec.rb +13 -0
- data/spec/adhoq/global_variable_spec.rb +37 -0
- data/spec/adhoq/reporter/xlsx_spec.rb +18 -0
- data/spec/adhoq/storage_spec.rb +12 -2
- data/spec/spec_helper.rb +29 -2
- data/spec/support/codeclimate_reporter.rb +4 -0
- data/spec/support/feature_spec_helper.rb +5 -0
- data/spec/support/have_values_in_xlsx_sheet_matcher.rb +9 -3
- metadata +97 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9fac13cfa25151dce3d5c579c39ba8098f07b6f
|
4
|
+
data.tar.gz: 23f822e233cc7977a2461dbe681a30d2426fffd5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6d1804945abba5952f20f671f68e5fab9141d474dfdcc70aab8a6317cd3c975296e244b6d480a27a2e19066ec2c56eeaadde874ddd3617281275b23c08de6bb
|
7
|
+
data.tar.gz: 25dac12d074dff4dead41cb23271447737f237103aa494f4148b509b0a6f8cb30be2c1bd3aff9e63fe83763836c744be5249e48422d78d290afdd26685473904
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
Adhoq [![Build Status](https://travis-ci.org/esminc/adhoq.svg)](https://travis-ci.org/esminc/adhoq) [![Code Climate](https://codeclimate.com/github/esminc/adhoq/badges/gpa.svg)](https://codeclimate.com/github/esminc/adhoq)
|
2
|
+
====
|
2
3
|
|
3
4
|
Rails engine to generate instant reports from adhoc SQL query.
|
4
5
|
|
@@ -6,16 +7,17 @@ Rails engine to generate instant reports from adhoc SQL query.
|
|
6
7
|
|
7
8
|
## Features
|
8
9
|
|
9
|
-
-
|
10
|
-
-
|
10
|
+
- Export ad-hoc SQL result to .xlsx file
|
11
|
+
- Persist generated report as local file or in AWS S3
|
12
|
+
- Rails 4.x & 3.2 support
|
13
|
+
- Nice administration console with rails engine
|
14
|
+
|
15
|
+
### Future planning
|
16
|
+
|
11
17
|
- Export reports in some formats:
|
12
|
-
- [x] .xlsx
|
13
18
|
- [ ] .csv
|
14
19
|
- [ ] .json
|
15
|
-
-
|
16
|
-
- [x] as local file
|
17
|
-
- [ ] S3 (via `Fog::Storage`)
|
18
|
-
- [ ] In application export function helper
|
20
|
+
- [ ] Label data substitution
|
19
21
|
|
20
22
|
## Installation
|
21
23
|
|
@@ -58,22 +60,53 @@ Edit initialization file in `config/initializer/adhoq.rb`
|
|
58
60
|
|
59
61
|
```ruby
|
60
62
|
Adhoq.configure do |config|
|
61
|
-
config.storage = [:local_file, Rails.root + '
|
63
|
+
config.storage = [:local_file, Rails.root + './path/to/store/report/files']
|
62
64
|
config.authorization = ->(controller) { controller.signed_in? }
|
63
65
|
end
|
64
66
|
```
|
65
67
|
|
66
|
-
See configuration example in [dummy app](https://github.com/esminc/adhoq/
|
68
|
+
See configuration example in [dummy app](https://github.com/esminc/adhoq/blob/master/spec/dummy/config/initializers/adhoq.rb).
|
67
69
|
|
68
70
|
Then restart server and try it out.
|
69
71
|
|
70
72
|
### As Plain old library (application export helper)
|
71
73
|
|
72
|
-
|
74
|
+
Adhoq also provides report generation from SQL string, not from mounted rails engine.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
execution = Adhoq::AdhocExecution.new(
|
78
|
+
'xlsx',
|
79
|
+
'SELECT "hello" AS name ,"English greeting message" AS description'
|
80
|
+
)
|
81
|
+
|
82
|
+
Adhoq::Reporter.generate(execution) #=> report data
|
83
|
+
```
|
84
|
+
|
85
|
+
Persistence is also available without engine via `Adhoq::Storage::SomeClass#store`.
|
86
|
+
Below is example that generating report and persist to in Rails application report method.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
execution = Adhoq::AdhocExecution.new(
|
90
|
+
'xlsx',
|
91
|
+
'SELECT "hello" AS name ,"English greeting message" AS description'
|
92
|
+
)
|
93
|
+
|
94
|
+
storage = Storage::S3.new(
|
95
|
+
'my-adhoq-bucket',
|
96
|
+
aws_access_key_id: 'key_id',
|
97
|
+
aws_secret_access_key: 'access_key'
|
98
|
+
)
|
99
|
+
|
100
|
+
# generate report and store it to S3, returns `key` to get report data
|
101
|
+
key = storage.store('.xlsx') { Adhoq::Reporter.generate(execution) }
|
102
|
+
|
103
|
+
...
|
104
|
+
storage.get(key) #=> report data
|
105
|
+
```
|
73
106
|
|
74
107
|
## Contributing
|
75
108
|
|
76
|
-
1. Fork it ( https://github.com/
|
109
|
+
1. Fork it ( https://github.com/esminc/adhoq/fork )
|
77
110
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
78
111
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
79
112
|
4. Push to the branch (`git push origin my-new-feature`)
|
@@ -55,3 +55,47 @@ form.query-form
|
|
55
55
|
|
56
56
|
.fa-pad-l
|
57
57
|
padding-left: $short-span / 2
|
58
|
+
|
59
|
+
#current-tables
|
60
|
+
font-size: $font-size-base * 0.9
|
61
|
+
|
62
|
+
table
|
63
|
+
width: 800px
|
64
|
+
|
65
|
+
caption
|
66
|
+
text-align: left
|
67
|
+
margin-bottom: $short-span
|
68
|
+
font-size: $font-size-base * 1.5
|
69
|
+
|
70
|
+
span.name
|
71
|
+
font-family: monospace
|
72
|
+
small.count
|
73
|
+
font-size: $font-size-base * 1
|
74
|
+
color: gray
|
75
|
+
margin-left: $font-size-base
|
76
|
+
|
77
|
+
th.pk, td.pk
|
78
|
+
width: $font-size-base * 1.5
|
79
|
+
th, td.icon
|
80
|
+
text-align: center
|
81
|
+
th.number, td.number
|
82
|
+
text-align: right
|
83
|
+
th.monospace, td.monospace
|
84
|
+
font-family: monospace
|
85
|
+
th.data
|
86
|
+
width: $font-size-base * 10
|
87
|
+
|
88
|
+
.tab-content
|
89
|
+
margin-top: $font-size-base
|
90
|
+
padding-left: 1em
|
91
|
+
padding-right: 1em
|
92
|
+
|
93
|
+
max-height: 600px
|
94
|
+
overflow: scroll
|
95
|
+
|
96
|
+
.tab-pane > h3
|
97
|
+
font-size: $font-size-base * 1.5
|
98
|
+
|
99
|
+
small
|
100
|
+
font-size: $font-size-base
|
101
|
+
margin-left: $font-size-base
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Adhoq
|
2
|
+
class CurrentTablesController < Adhoq::ApplicationController
|
3
|
+
before_filter :eager_load_models
|
4
|
+
|
5
|
+
def index
|
6
|
+
@ar_classes = ActiveRecord::Base.subclasses.
|
7
|
+
reject {|klass| klass.name == 'ActiveRecord::SchemaMigration' }.
|
8
|
+
sort_by(&:name)
|
9
|
+
|
10
|
+
render layout: false
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def eager_load_models
|
16
|
+
return unless Rails.env.development?
|
17
|
+
[Rails.application, Adhoq::Engine].each(&:eager_load!)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Adhoq
|
2
|
-
class ExecutionsController < ApplicationController
|
2
|
+
class ExecutionsController < Adhoq::ApplicationController
|
3
3
|
def show
|
4
4
|
@execution = current_query.executions.where(id: params[:id], report_format: params[:format]).first!
|
5
5
|
|
@@ -19,7 +19,7 @@ module Adhoq
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def respond_report(report)
|
22
|
-
send_data report.data
|
22
|
+
send_data report.data, type: report.mime_type, filename: report.name, disposition: 'attachment'
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
@@ -7,5 +7,18 @@ module Adhoq
|
|
7
7
|
klass.model_name.humanize
|
8
8
|
end
|
9
9
|
end
|
10
|
+
|
11
|
+
def icon_fa(name, additional_classes = [])
|
12
|
+
tag('i', class: ['fa', "fa-#{name}", *additional_classes])
|
13
|
+
end
|
14
|
+
|
15
|
+
def schema_version
|
16
|
+
if defined?(ActiveRecord::SchemaMigration)
|
17
|
+
ActiveRecord::SchemaMigration.maximum(:version)
|
18
|
+
else
|
19
|
+
result = Adhoq::Executor.select("SELECT MAX(version) AS current_version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}")
|
20
|
+
result.rows.first
|
21
|
+
end
|
22
|
+
end
|
10
23
|
end
|
11
24
|
end
|
data/app/models/adhoq/report.rb
CHANGED
@@ -1,11 +1,8 @@
|
|
1
1
|
module Adhoq
|
2
2
|
class Report < ActiveRecord::Base
|
3
|
-
BUFSIZE = 10.kilobytes.to_i
|
4
|
-
|
5
3
|
belongs_to :execution
|
6
4
|
|
7
|
-
delegate :name,
|
8
|
-
delegate :mime_type, to: :reporter
|
5
|
+
delegate :name, to: 'execution'
|
9
6
|
|
10
7
|
def generate!(storage = Adhoq.current_storage)
|
11
8
|
self.identifier = generate_and_persist_report!(storage)
|
@@ -15,28 +12,24 @@ module Adhoq
|
|
15
12
|
save!
|
16
13
|
end
|
17
14
|
|
18
|
-
def
|
19
|
-
|
15
|
+
def available?
|
16
|
+
identifier.present? && (storage == Adhoq.current_storage.identifier)
|
20
17
|
end
|
21
18
|
|
22
19
|
def data(storage = Adhoq.current_storage)
|
23
20
|
storage.get(identifier)
|
24
21
|
end
|
25
22
|
|
26
|
-
|
27
|
-
|
28
|
-
def reporter
|
29
|
-
{'xlsx' => Adhoq::Reporter::Xlsx}[execution.report_format]
|
23
|
+
def mime_type
|
24
|
+
Adhoq::Reporter.lookup(execution.report_format).mime_type
|
30
25
|
end
|
31
26
|
|
32
|
-
|
33
|
-
storage.store(".#{execution.report_format}") do |file, *|
|
34
|
-
executor = Executor.new(execution.raw_sql)
|
27
|
+
private
|
35
28
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
29
|
+
def generate_and_persist_report!(storage)
|
30
|
+
storage.store(".#{execution.report_format}") {
|
31
|
+
Adhoq::Reporter.generate(execution)
|
32
|
+
}
|
40
33
|
end
|
41
34
|
end
|
42
35
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
h3
|
2
|
+
| Current database schema
|
3
|
+
small= "Version #{schema_version}"
|
4
|
+
|
5
|
+
ul.list-unstyled.tables
|
6
|
+
- @ar_classes.each do |ar_class|
|
7
|
+
- first_record = ar_class.unscoped.order(:id).first
|
8
|
+
|
9
|
+
li.ar_class data-table-name=ar_class.table_name
|
10
|
+
table.table.table-striped.table-hover.table-bordered
|
11
|
+
caption
|
12
|
+
span.name= ar_class.table_name
|
13
|
+
small.count #{ar_class.unscoped.count} rows
|
14
|
+
thead
|
15
|
+
tr
|
16
|
+
th.col-sm-1.pk PK
|
17
|
+
th.col-sm-2.name Name
|
18
|
+
th.col-sm-1.type Type
|
19
|
+
th.col-sm-1.null Non-Null
|
20
|
+
th.col-sm-1.limit Limit
|
21
|
+
th.col-sm-2.default Default
|
22
|
+
th.col-sm-4.data unscoped.first
|
23
|
+
tbody
|
24
|
+
- ar_class.columns.each do |column|
|
25
|
+
tr
|
26
|
+
td.pk.icon= column.primary ? icon_fa('check-circle') : ''
|
27
|
+
td.monospace= column.name
|
28
|
+
td= column.type
|
29
|
+
td.null.icon= column.null ? '' : icon_fa('check')
|
30
|
+
td.limit.number= column.limit
|
31
|
+
td= column.default
|
32
|
+
td.monospace= first_record.try {|r| truncate(r.read_attribute_before_type_cast(column.name).to_s, length: 50) }
|
@@ -24,15 +24,35 @@
|
|
24
24
|
.col-sm-8
|
25
25
|
= f.text_area :query, class: 'form-control', rows: 10, required: true
|
26
26
|
|
27
|
-
.
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
ul.nav.nav-tabs[role='tablist']
|
28
|
+
li.active
|
29
|
+
a[role='tab' data-toggle='tab' href='#preview' ]
|
30
|
+
i.fa.fa-eye.fa-pad-r
|
31
|
+
| Preview
|
32
|
+
li
|
33
|
+
a[role='tab' data-toggle='tab' href='#current-tables']
|
34
|
+
i.fa.fa-database.fa-pad-r
|
35
|
+
| Tables
|
33
36
|
|
34
|
-
|
35
|
-
|
37
|
+
.tab-content
|
38
|
+
#preview.tab-pane.active
|
39
|
+
h3
|
40
|
+
| Query preview
|
41
|
+
small
|
42
|
+
= link_to preview_path, class: 'js-preview-button', data: {source: '#query_query', result: '.js-preview-result', remote: true, method: 'POST'} do
|
43
|
+
i.fa.fa-refresh.fa-pad-r[data-title='Refresh preview']
|
44
|
+
| Reflesh
|
45
|
+
|
46
|
+
.js-preview-result
|
47
|
+
.alert.alert-info Preview is shown here
|
48
|
+
|
49
|
+
#current-tables.tab-pane
|
50
|
+
a.loading[href=current_tables_path]
|
36
51
|
|
37
52
|
javascript:
|
38
|
-
$(function() {
|
53
|
+
$(function() {
|
54
|
+
Adhoq.enablePreview($('#preview a.js-preview-button'));
|
55
|
+
|
56
|
+
$('a[data-toggle="tab"]').on('show.bs.tab', function(ev) { Adhoq.loadCurrentTableTabOnce($(ev.target)) });
|
57
|
+
});
|
58
|
+
|
@@ -38,6 +38,6 @@ section.query
|
|
38
38
|
span.label[class=(exec.success? ? 'label-success' : 'label-danger')]= exec.status_label
|
39
39
|
td.report
|
40
40
|
- if exec.success?
|
41
|
-
= link_to(
|
41
|
+
= link_to(adhoq.query_execution_path(query, exec, format: exec.report_format), class: 'btn btn-sm btn-default') do
|
42
42
|
i.fa.fa-download.fa-pad-r
|
43
43
|
= exec.report_format
|
data/config/routes.rb
CHANGED
data/lib/adhoq.rb
CHANGED
@@ -2,12 +2,13 @@ require 'adhoq/engine'
|
|
2
2
|
require 'adhoq/global_variable'
|
3
3
|
|
4
4
|
module Adhoq
|
5
|
-
autoload '
|
6
|
-
autoload '
|
7
|
-
autoload '
|
8
|
-
autoload '
|
9
|
-
autoload '
|
10
|
-
autoload '
|
5
|
+
autoload 'AdhocExecution', 'adhoq/adhoc_execution'
|
6
|
+
autoload 'Configuration', 'adhoq/configuration'
|
7
|
+
autoload 'Error', 'adhoq/error'
|
8
|
+
autoload 'Executor', 'adhoq/executor'
|
9
|
+
autoload 'Reporter', 'adhoq/reporter'
|
10
|
+
autoload 'Result', 'adhoq/result'
|
11
|
+
autoload 'Storage', 'adhoq/storage'
|
11
12
|
|
12
13
|
extend Adhoq::GlobalVariable
|
13
14
|
end
|
data/lib/adhoq/executor.rb
CHANGED
@@ -2,12 +2,23 @@ module Adhoq
|
|
2
2
|
class Executor
|
3
3
|
class << self
|
4
4
|
def select(query)
|
5
|
-
|
5
|
+
with_sandbox do
|
6
|
+
current_connection.exec_query(query)
|
7
|
+
end
|
6
8
|
end
|
7
9
|
|
8
10
|
def current_connection
|
9
11
|
ActiveRecord::Base.connection
|
10
12
|
end
|
13
|
+
|
14
|
+
def with_sandbox
|
15
|
+
result = nil
|
16
|
+
ActiveRecord::Base.transaction do
|
17
|
+
result = yield
|
18
|
+
raise ActiveRecord::Rollback
|
19
|
+
end
|
20
|
+
result
|
21
|
+
end
|
11
22
|
end
|
12
23
|
|
13
24
|
def initialize(query)
|
@@ -22,13 +22,16 @@ module Adhoq
|
|
22
22
|
|
23
23
|
private
|
24
24
|
|
25
|
-
# TODO Implement S3
|
26
25
|
def setup_storage(type, *args)
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
klass =
|
27
|
+
case type
|
28
|
+
when :local_file then Adhoq::Storage::LocalFile
|
29
|
+
when :s3 then Adhoq::Storage::S3
|
30
|
+
else
|
31
|
+
raise NotImplementedError
|
32
|
+
end
|
30
33
|
|
31
|
-
|
34
|
+
klass.new(*args)
|
32
35
|
end
|
33
36
|
end
|
34
37
|
end
|
data/lib/adhoq/reporter.rb
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
module Adhoq
|
2
2
|
module Reporter
|
3
3
|
autoload 'Xlsx', 'adhoq/reporter/xlsx'
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def generate(execution)
|
7
|
+
executor = Executor.new(execution.raw_sql)
|
8
|
+
reporter = lookup(execution.report_format).new(executor.execute)
|
9
|
+
|
10
|
+
reporter.build_report
|
11
|
+
end
|
12
|
+
|
13
|
+
def lookup(format)
|
14
|
+
@map ||= {'xlsx' => Adhoq::Reporter::Xlsx}
|
15
|
+
@map[format]
|
16
|
+
end
|
17
|
+
end
|
4
18
|
end
|
5
19
|
end
|
data/lib/adhoq/storage.rb
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
module Adhoq
|
2
2
|
module Storage
|
3
|
-
autoload '
|
3
|
+
autoload 'FogStorage', 'adhoq/storage/fog_storage'
|
4
|
+
autoload 'LocalFile', 'adhoq/storage/local_file'
|
5
|
+
autoload 'S3', 'adhoq/storage/s3'
|
6
|
+
|
7
|
+
def with_new_identifier(suffix = nil, seed = Time.now)
|
8
|
+
dirname, fname_seed = ['%Y-%m-%d', '%H%M%S.%L'].map {|f| seed.strftime(f) }
|
9
|
+
|
10
|
+
basename = "%s_%05d%s" % [fname_seed, Process.pid, suffix]
|
11
|
+
|
12
|
+
[dirname, basename].join('/').tap {|id| yield id }
|
13
|
+
end
|
14
|
+
module_function :with_new_identifier
|
4
15
|
end
|
5
16
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Adhoq
|
2
|
+
module Storage
|
3
|
+
class FogStorage
|
4
|
+
def store(suffix = nil, seed = Time.now, &block)
|
5
|
+
Adhoq::Storage.with_new_identifier(suffix, seed) do |identifier|
|
6
|
+
io = yield
|
7
|
+
io.rewind
|
8
|
+
|
9
|
+
directory.files.create(key: identifier, body: io, public: false)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(identifier)
|
14
|
+
get_raw(identifier).body
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_raw(identifier)
|
18
|
+
directory.files.head(identifier)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,44 +1,25 @@
|
|
1
|
+
require 'fog'
|
2
|
+
|
1
3
|
module Adhoq
|
2
4
|
module Storage
|
3
|
-
class LocalFile
|
5
|
+
class LocalFile < FogStorage
|
4
6
|
attr_reader :root
|
5
7
|
|
6
8
|
def initialize(root_path)
|
7
|
-
|
8
|
-
end
|
9
|
-
|
10
|
-
def identifier
|
11
|
-
"file://#{@root.realpath}"
|
12
|
-
end
|
13
|
-
|
14
|
-
def store(suffix = nil, seed = Time.now, &block)
|
15
|
-
calculate_identifier(suffix, seed).tap do |identifier|
|
16
|
-
mkpath!(identifier)
|
9
|
+
path = Pathname.new(root_path)
|
17
10
|
|
18
|
-
|
19
|
-
|
20
|
-
file.flush
|
21
|
-
end
|
22
|
-
end
|
11
|
+
@fog = Fog::Storage.new(provider: 'Local', local_root: path.parent)
|
12
|
+
@dir = path.basename.to_s
|
23
13
|
end
|
24
14
|
|
25
|
-
def
|
26
|
-
|
15
|
+
def identifier
|
16
|
+
"file://#{[@fog.local_root, @dir].join('/')}"
|
27
17
|
end
|
28
18
|
|
29
19
|
private
|
30
20
|
|
31
|
-
def
|
32
|
-
|
33
|
-
|
34
|
-
basename = "%s_%05d%s" % [fname_seed, Process.pid, suffix]
|
35
|
-
|
36
|
-
identifier = [dirname, basename].join('/')
|
37
|
-
end
|
38
|
-
|
39
|
-
def mkpath!(identifier)
|
40
|
-
dir = identifier.split('/').first
|
41
|
-
(@root + dir).mkpath
|
21
|
+
def directory
|
22
|
+
@fog.directories.get(@dir) || @fog.directories.create(key: @dir)
|
42
23
|
end
|
43
24
|
end
|
44
25
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'fog'
|
2
|
+
|
3
|
+
module Adhoq
|
4
|
+
module Storage
|
5
|
+
class S3 < FogStorage
|
6
|
+
def initialize(bucket, s3_options = {})
|
7
|
+
@bucket = bucket
|
8
|
+
@s3 = Fog::Storage.new({provider: 'AWS'}.merge(s3_options))
|
9
|
+
end
|
10
|
+
|
11
|
+
def identifier
|
12
|
+
"s3://#{@bucket}"
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def directory
|
18
|
+
return @directory if @directory
|
19
|
+
|
20
|
+
@directory = @s3.directories.get(@bucket) || @s3.directories.create(key: @bucket, public: false)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/adhoq/version.rb
CHANGED
data/spec/adhoq/executor_spec.rb
CHANGED
@@ -7,5 +7,18 @@ module Adhoq
|
|
7
7
|
|
8
8
|
specify { expect(executor.execute).to eq Adhoq::Result.new(%w[answer], [[42]]) }
|
9
9
|
end
|
10
|
+
|
11
|
+
describe '.select' do
|
12
|
+
specify 'Do not reflect write access' do
|
13
|
+
expect {
|
14
|
+
Executor.select(<<-INSERT_SQL.strip_heredoc)
|
15
|
+
INSERT INTO "adhoq_queries"
|
16
|
+
("description", "name", "query", "updated_at", "created_at")
|
17
|
+
VALUES
|
18
|
+
("description", "name", "SELECT 1", "NOW", "NOW")
|
19
|
+
INSERT_SQL
|
20
|
+
}.not_to change(Adhoq::Query, :count)
|
21
|
+
end
|
22
|
+
end
|
10
23
|
end
|
11
24
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Adhoq
|
2
|
+
RSpec.describe GlobalVariable, type: :model do
|
3
|
+
def reset_storage_config(*storage_config)
|
4
|
+
Adhoq.instance_variable_set('@current_storage', nil)
|
5
|
+
Adhoq.config.storage = storage_config
|
6
|
+
end
|
7
|
+
|
8
|
+
around(:each) do |example|
|
9
|
+
begin
|
10
|
+
original_config = Adhoq.config.storage
|
11
|
+
example.run
|
12
|
+
ensure
|
13
|
+
reset_storage_config(*original_config)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'config.storage = [:local_file, ....]' do
|
18
|
+
before do
|
19
|
+
reset_storage_config(:local_file, Rails.root + "/tmp/adhoq/#{Rails.env}")
|
20
|
+
end
|
21
|
+
|
22
|
+
specify do
|
23
|
+
expect(Adhoq.current_storage).to be_instance_of Adhoq::Storage::LocalFile
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'config.storage = [:s3, ....]' do
|
28
|
+
before do
|
29
|
+
reset_storage_config(:s3, 'my-bucket-name', aws_access_key_id: 'key-id', aws_secret_access_key: 'secret', region: 'paris-01')
|
30
|
+
end
|
31
|
+
|
32
|
+
specify do
|
33
|
+
expect(Adhoq.current_storage).to be_instance_of Adhoq::Storage::S3
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Adhoq
|
2
|
+
RSpec.describe Reporter::Xlsx, type: :model do
|
3
|
+
context 'create xlsx report' do
|
4
|
+
let(:report_data) do
|
5
|
+
Adhoq::Reporter.generate(Adhoq::AdhocExecution.new('xlsx', <<-SQL.strip_heredoc))
|
6
|
+
SELECT "hello" AS name ,"English greeting message" AS description
|
7
|
+
SQL
|
8
|
+
end
|
9
|
+
|
10
|
+
specify do
|
11
|
+
expect(report_data.read).to have_values_in_xlsx_sheet([
|
12
|
+
%w[name description],
|
13
|
+
['hello', 'English greeting message']
|
14
|
+
])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/spec/adhoq/storage_spec.rb
CHANGED
@@ -10,10 +10,20 @@ module Adhoq
|
|
10
10
|
let(:storage) { Storage::LocalFile.new(tempdir) }
|
11
11
|
|
12
12
|
let(:identifier) do
|
13
|
-
storage.store('.txt') {
|
13
|
+
storage.store('.txt') { StringIO.new("Hello adhoq!\n") }
|
14
14
|
end
|
15
15
|
|
16
|
-
specify { expect(storage.get(identifier)
|
16
|
+
specify { expect(storage.get(identifier)).to eq "Hello adhoq!\n" }
|
17
|
+
end
|
18
|
+
|
19
|
+
describe Storage::S3, :fog_mock do
|
20
|
+
let(:storage) { Storage::S3.new('my-adhoq-bucket', aws_access_key_id: 'key_id', aws_secret_access_key: 'access_key') }
|
21
|
+
|
22
|
+
let(:identifier) do
|
23
|
+
storage.store('.txt') { StringIO.new("Hello adhoq!\n") }
|
24
|
+
end
|
25
|
+
|
26
|
+
specify { expect(storage.get(identifier)).to eq "Hello adhoq!\n" }
|
17
27
|
end
|
18
28
|
end
|
19
29
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,13 +2,18 @@ ENV['RAILS_ENV'] ||= 'test'
|
|
2
2
|
require_relative 'dummy/config/environment'
|
3
3
|
|
4
4
|
require 'rspec/rails'
|
5
|
-
require 'factory_girl_rails'
|
6
5
|
|
6
|
+
require 'capybara/rspec'
|
7
|
+
require 'capybara/poltergeist'
|
8
|
+
require 'database_cleaner'
|
9
|
+
require 'factory_girl_rails'
|
7
10
|
require 'pry-byebug'
|
8
11
|
|
9
12
|
Rails.backtrace_cleaner.remove_silencers!
|
10
13
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f }
|
11
14
|
|
15
|
+
Capybara.default_driver = :poltergeist
|
16
|
+
|
12
17
|
RSpec.configure do |config|
|
13
18
|
config.expect_with :rspec do |expectations|
|
14
19
|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
@@ -18,7 +23,7 @@ RSpec.configure do |config|
|
|
18
23
|
mocks.verify_partial_doubles = true
|
19
24
|
end
|
20
25
|
|
21
|
-
config.use_transactional_fixtures =
|
26
|
+
config.use_transactional_fixtures = false
|
22
27
|
config.filter_run :focus
|
23
28
|
config.run_all_when_everything_filtered = true
|
24
29
|
|
@@ -33,4 +38,26 @@ RSpec.configure do |config|
|
|
33
38
|
|
34
39
|
config.include FactoryGirl::Syntax::Methods
|
35
40
|
Kernel.srand config.seed
|
41
|
+
|
42
|
+
config.around(:each, :fog_mock) do |example|
|
43
|
+
begin
|
44
|
+
Fog.mock!
|
45
|
+
Fog::Mock.reset
|
46
|
+
|
47
|
+
example.run
|
48
|
+
ensure
|
49
|
+
Fog.unmock!
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
config.before(:suite) do
|
54
|
+
DatabaseCleaner.strategy = :truncation
|
55
|
+
DatabaseCleaner.clean_with :truncation
|
56
|
+
end
|
57
|
+
|
58
|
+
config.around(:each) do |example|
|
59
|
+
DatabaseCleaner.cleaning do
|
60
|
+
example.run
|
61
|
+
end
|
62
|
+
end
|
36
63
|
end
|
@@ -1,16 +1,22 @@
|
|
1
1
|
require 'rspec/matchers'
|
2
2
|
require 'simple_xlsx_reader'
|
3
3
|
|
4
|
-
RSpec::Matchers.define :have_values_in_xlsx_sheet do |
|
4
|
+
RSpec::Matchers.define :have_values_in_xlsx_sheet do |expect_values|
|
5
5
|
match do |data|
|
6
|
-
|
6
|
+
@actual_values = extract_values(data)
|
7
|
+
expect(@actual_values).to eq expect_values
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
failure_message do
|
12
|
+
RSpec::Expectations.differ.diff_as_object(@actual_values, expect_values)
|
7
13
|
end
|
8
14
|
|
9
15
|
private
|
10
16
|
|
11
17
|
def extract_values(data)
|
12
18
|
Tempfile.open(%w[actual .xlsx], Dir.tmpdir, encoding: 'BINARY') do |f|
|
13
|
-
f.write data
|
19
|
+
f.write data
|
14
20
|
f.flush
|
15
21
|
|
16
22
|
sheet = SimpleXlsxReader::Document.new(f.path).sheets.first
|
metadata
CHANGED
@@ -1,31 +1,45 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: adhoq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kyosuke MOROHASHI
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-10-
|
11
|
+
date: 2014-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: axlsx
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
16
30
|
requirements:
|
17
31
|
- - "~>"
|
18
32
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
33
|
+
version: '2.0'
|
20
34
|
type: :runtime
|
21
35
|
prerelease: false
|
22
36
|
version_requirements: !ruby/object:Gem::Requirement
|
23
37
|
requirements:
|
24
38
|
- - "~>"
|
25
39
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
40
|
+
version: '2.0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
42
|
+
name: coffee-rails
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - ">="
|
@@ -39,7 +53,35 @@ dependencies:
|
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
56
|
+
name: fog
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.23'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.23'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: font-awesome-sass
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 4.2.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 4.2.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: jquery-rails
|
43
85
|
requirement: !ruby/object:Gem::Requirement
|
44
86
|
requirements:
|
45
87
|
- - ">="
|
@@ -53,7 +95,7 @@ dependencies:
|
|
53
95
|
- !ruby/object:Gem::Version
|
54
96
|
version: '0'
|
55
97
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
98
|
+
name: sass-rails
|
57
99
|
requirement: !ruby/object:Gem::Requirement
|
58
100
|
requirements:
|
59
101
|
- - ">="
|
@@ -67,7 +109,7 @@ dependencies:
|
|
67
109
|
- !ruby/object:Gem::Version
|
68
110
|
version: '0'
|
69
111
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
112
|
+
name: slim-rails
|
71
113
|
requirement: !ruby/object:Gem::Requirement
|
72
114
|
requirements:
|
73
115
|
- - ">="
|
@@ -81,13 +123,27 @@ dependencies:
|
|
81
123
|
- !ruby/object:Gem::Version
|
82
124
|
version: '0'
|
83
125
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
126
|
+
name: capybara
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 2.4.3
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 2.4.3
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: database_cleaner
|
85
141
|
requirement: !ruby/object:Gem::Requirement
|
86
142
|
requirements:
|
87
143
|
- - ">="
|
88
144
|
- !ruby/object:Gem::Version
|
89
145
|
version: '0'
|
90
|
-
type: :
|
146
|
+
type: :development
|
91
147
|
prerelease: false
|
92
148
|
version_requirements: !ruby/object:Gem::Requirement
|
93
149
|
requirements:
|
@@ -95,13 +151,13 @@ dependencies:
|
|
95
151
|
- !ruby/object:Gem::Version
|
96
152
|
version: '0'
|
97
153
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
154
|
+
name: factory_girl_rails
|
99
155
|
requirement: !ruby/object:Gem::Requirement
|
100
156
|
requirements:
|
101
157
|
- - ">="
|
102
158
|
- !ruby/object:Gem::Version
|
103
159
|
version: '0'
|
104
|
-
type: :
|
160
|
+
type: :development
|
105
161
|
prerelease: false
|
106
162
|
version_requirements: !ruby/object:Gem::Requirement
|
107
163
|
requirements:
|
@@ -109,7 +165,7 @@ dependencies:
|
|
109
165
|
- !ruby/object:Gem::Version
|
110
166
|
version: '0'
|
111
167
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
168
|
+
name: launchy
|
113
169
|
requirement: !ruby/object:Gem::Requirement
|
114
170
|
requirements:
|
115
171
|
- - ">="
|
@@ -122,6 +178,20 @@ dependencies:
|
|
122
178
|
- - ">="
|
123
179
|
- !ruby/object:Gem::Version
|
124
180
|
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: poltergeist
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: 1.5.1
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - "~>"
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: 1.5.1
|
125
195
|
- !ruby/object:Gem::Dependency
|
126
196
|
name: pry-byebug
|
127
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -217,6 +287,7 @@ files:
|
|
217
287
|
- app/assets/javascripts/adhoq/bootstrap/tab.js
|
218
288
|
- app/assets/javascripts/adhoq/bootstrap/tooltip.js
|
219
289
|
- app/assets/javascripts/adhoq/bootstrap/transition.js
|
290
|
+
- app/assets/javascripts/adhoq/current_tables.js.coffee
|
220
291
|
- app/assets/javascripts/adhoq/previewer.js.coffee
|
221
292
|
- app/assets/stylesheets/adhoq/_bootstrap-compass.scss
|
222
293
|
- app/assets/stylesheets/adhoq/_bootstrap-mincer.scss
|
@@ -296,6 +367,7 @@ files:
|
|
296
367
|
- app/assets/stylesheets/adhoq/bootstrap/mixins/_vendor-prefixes.scss
|
297
368
|
- app/controllers/adhoq/application_controller.rb
|
298
369
|
- app/controllers/adhoq/authorization_methods.rb
|
370
|
+
- app/controllers/adhoq/current_tables_controller.rb
|
299
371
|
- app/controllers/adhoq/executions_controller.rb
|
300
372
|
- app/controllers/adhoq/previews_controller.rb
|
301
373
|
- app/controllers/adhoq/queries_controller.rb
|
@@ -306,6 +378,7 @@ files:
|
|
306
378
|
- app/models/adhoq/time_based_orders.rb
|
307
379
|
- app/views/adhoq/application/_global_nav.html.slim
|
308
380
|
- app/views/adhoq/application/_sidebar_queries_index.html.slim
|
381
|
+
- app/views/adhoq/current_tables/index.html.slim
|
309
382
|
- app/views/adhoq/previews/create.html.slim
|
310
383
|
- app/views/adhoq/previews/statement_invalid.html.slim
|
311
384
|
- app/views/adhoq/queries/_form.html.slim
|
@@ -320,6 +393,7 @@ files:
|
|
320
393
|
- db/migrate/20141006014750_create_adhoq_executions.rb
|
321
394
|
- db/migrate/20141007052308_create_adhoq_reports.rb
|
322
395
|
- lib/adhoq.rb
|
396
|
+
- lib/adhoq/adhoc_execution.rb
|
323
397
|
- lib/adhoq/configuration.rb
|
324
398
|
- lib/adhoq/engine.rb
|
325
399
|
- lib/adhoq/error.rb
|
@@ -329,16 +403,22 @@ files:
|
|
329
403
|
- lib/adhoq/reporter/xlsx.rb
|
330
404
|
- lib/adhoq/result.rb
|
331
405
|
- lib/adhoq/storage.rb
|
406
|
+
- lib/adhoq/storage/fog_storage.rb
|
332
407
|
- lib/adhoq/storage/local_file.rb
|
408
|
+
- lib/adhoq/storage/s3.rb
|
333
409
|
- lib/adhoq/version.rb
|
334
410
|
- lib/tasks/adhoq_tasks.rake
|
335
411
|
- spec/adhoq/executor_spec.rb
|
412
|
+
- spec/adhoq/global_variable_spec.rb
|
413
|
+
- spec/adhoq/reporter/xlsx_spec.rb
|
336
414
|
- spec/adhoq/storage_spec.rb
|
337
415
|
- spec/factories/adhoq_queries.rb
|
338
416
|
- spec/models/adhoq/execution_spec.rb
|
339
417
|
- spec/models/adhoq/query_spec.rb
|
340
418
|
- spec/models/adhoq/report_spec.rb
|
341
419
|
- spec/spec_helper.rb
|
420
|
+
- spec/support/codeclimate_reporter.rb
|
421
|
+
- spec/support/feature_spec_helper.rb
|
342
422
|
- spec/support/have_values_in_xlsx_sheet_matcher.rb
|
343
423
|
homepage: https://github.com/esminc/adhoq
|
344
424
|
licenses:
|
@@ -366,10 +446,14 @@ specification_version: 4
|
|
366
446
|
summary: DB management console in the wild.
|
367
447
|
test_files:
|
368
448
|
- spec/adhoq/executor_spec.rb
|
449
|
+
- spec/adhoq/global_variable_spec.rb
|
450
|
+
- spec/adhoq/reporter/xlsx_spec.rb
|
369
451
|
- spec/adhoq/storage_spec.rb
|
370
452
|
- spec/factories/adhoq_queries.rb
|
371
453
|
- spec/models/adhoq/execution_spec.rb
|
372
454
|
- spec/models/adhoq/query_spec.rb
|
373
455
|
- spec/models/adhoq/report_spec.rb
|
456
|
+
- spec/support/codeclimate_reporter.rb
|
457
|
+
- spec/support/feature_spec_helper.rb
|
374
458
|
- spec/support/have_values_in_xlsx_sheet_matcher.rb
|
375
459
|
- spec/spec_helper.rb
|