dbhero 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +21 -0
- data/app/assets/javascripts/dbhero/application.js +45 -0
- data/app/assets/javascripts/dbhero/dataclips.js +42 -0
- data/app/assets/stylesheets/dbhero/application.css.scss +88 -0
- data/app/controllers/dbhero/application_controller.rb +30 -0
- data/app/controllers/dbhero/dataclips_controller.rb +84 -0
- data/app/helpers/dbhero/application_helper.rb +4 -0
- data/app/helpers/dbhero/dataclips_helper.rb +4 -0
- data/app/models/dbhero/dataclip.rb +50 -0
- data/app/views/dbhero/dataclips/_clip_table.html.slim +38 -0
- data/app/views/dbhero/dataclips/_form.html.slim +32 -0
- data/app/views/dbhero/dataclips/edit.html.slim +6 -0
- data/app/views/dbhero/dataclips/index.html.slim +25 -0
- data/app/views/dbhero/dataclips/new.html.slim +2 -0
- data/app/views/dbhero/dataclips/show.html.slim +18 -0
- data/app/views/layouts/dbhero/application.html.slim +24 -0
- data/config/routes.rb +6 -0
- data/lib/dbhero.rb +7 -0
- data/lib/dbhero/configuration.rb +39 -0
- data/lib/dbhero/engine.rb +9 -0
- data/lib/dbhero/gdrive_exporter.rb +56 -0
- data/lib/dbhero/router_constraint.rb +43 -0
- data/lib/dbhero/version.rb +3 -0
- data/lib/generators/dbhero/install/install_generator.rb +29 -0
- data/lib/generators/dbhero/install/templates/dbhero.rb +26 -0
- data/lib/generators/dbhero/install/templates/migrations/create_dbhero_dataclips.rb +17 -0
- data/lib/tasks/dbhero_tasks.rake +4 -0
- data/spec/controllers/dbhero/dataclips_controller_spec.rb +293 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +27 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +85 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +78 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/assets.rb +8 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/dbhero.rb +26 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/migrate/20150323172444_create_dbhero_dataclips.rb +17 -0
- data/spec/dummy/db/schema.rb +32 -0
- data/spec/dummy/log/development.log +27 -0
- data/spec/dummy/log/test.log +3758 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories.rb +8 -0
- data/spec/models/dbhero/dataclip_spec.rb +119 -0
- data/spec/rails_helper.rb +57 -0
- data/spec/spec_helper.rb +90 -0
- metadata +312 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 04ea97a58981185948410f15d73215eb9caa4c7e
|
4
|
+
data.tar.gz: fb418837a8c175fec3dcd489427203da481b3a18
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 25d1e1f03d9fbf3af2457c4f2cf3d22e829737804703f010d9f2ca145f38266beea804810e8faf4c39b29ea9cba7ece97eef915c531e125d6fb1676436b76b58
|
7
|
+
data.tar.gz: 4d959ad723d1be89cd081f37a3ade030e80685f408afcc26b29680f9ffbbf611d6a809709350df240c0975325ae7522f032e5d5ed6c22ac80d97062b61630aa6
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
|
8
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
9
|
+
|
10
|
+
load 'rails/tasks/engine.rake'
|
11
|
+
|
12
|
+
Bundler::GemHelper.install_tasks
|
13
|
+
|
14
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
|
15
|
+
|
16
|
+
require 'rspec/core'
|
17
|
+
require 'rspec/core/rake_task'
|
18
|
+
|
19
|
+
desc "Run all specs in spec directory (excluding plugin specs)"
|
20
|
+
RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
|
21
|
+
task :default => :spec
|
@@ -0,0 +1,45 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require_tree .
|
15
|
+
|
16
|
+
$(function(){
|
17
|
+
|
18
|
+
$('.dropdown-button').dropdown({
|
19
|
+
inDuration: 300,
|
20
|
+
outDuration: 225,
|
21
|
+
constrain_width: false, // Does not change width of dropdown to that of the activator
|
22
|
+
hover: false, // Activate on click
|
23
|
+
alignment: 'left', // Aligns dropdown to left or right edge (works with constrain_width)
|
24
|
+
gutter: 0, // Spacing from edge
|
25
|
+
belowOrigin: false // Displays dropdown below the button
|
26
|
+
}
|
27
|
+
);
|
28
|
+
|
29
|
+
try {
|
30
|
+
var $table = $('table#clip_table')
|
31
|
+
|
32
|
+
$table.dataTable({
|
33
|
+
scrollX: true,
|
34
|
+
searching: false,
|
35
|
+
lengthChange: false,
|
36
|
+
pagingType: 'simple'
|
37
|
+
});
|
38
|
+
|
39
|
+
//$table.floatThead({
|
40
|
+
// scrollingTop: 'pageTop',
|
41
|
+
//});
|
42
|
+
} catch(e) {
|
43
|
+
console.log(e);
|
44
|
+
}
|
45
|
+
});
|
@@ -0,0 +1,42 @@
|
|
1
|
+
$(function(){
|
2
|
+
|
3
|
+
startAce = function() {
|
4
|
+
var textarea = $('textarea#dataclip_raw_query');
|
5
|
+
console.log(textarea.width());
|
6
|
+
console.log(textarea.height());
|
7
|
+
|
8
|
+
var editDiv = $('<div>', {
|
9
|
+
id: "ace_editor",
|
10
|
+
position: 'absolute',
|
11
|
+
width: textarea.width(),
|
12
|
+
height: textarea.height(),
|
13
|
+
'class': textarea.attr('class')
|
14
|
+
}).insertBefore(textarea);
|
15
|
+
|
16
|
+
textarea.css('display', 'none');
|
17
|
+
|
18
|
+
var editor = ace.edit(editDiv[0]);
|
19
|
+
editor.renderer.setShowGutter(true);
|
20
|
+
|
21
|
+
editor.getSession().setUseWrapMode(true);
|
22
|
+
editor.getSession().setValue(textarea.val());
|
23
|
+
editor.getSession().setTabSize(2);
|
24
|
+
editor.getSession().setUseSoftTabs(true);
|
25
|
+
editor.getSession().setMode("ace/mode/pgsql");
|
26
|
+
|
27
|
+
textarea.closest('form').submit(function () {
|
28
|
+
textarea.val(editor.getSession().getValue());
|
29
|
+
});
|
30
|
+
|
31
|
+
$('.ace_editor').css({'padding':'0'});
|
32
|
+
|
33
|
+
editor.setTheme("ace/theme/xcode");
|
34
|
+
$(editDiv).css({ 'font-size': '15px' })
|
35
|
+
}
|
36
|
+
|
37
|
+
if($('textarea#dataclip_raw_query').length > 0){
|
38
|
+
startAce();
|
39
|
+
}
|
40
|
+
|
41
|
+
});
|
42
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
16
|
+
.editor-style {
|
17
|
+
height: 300px;
|
18
|
+
border-radius: 1px;
|
19
|
+
}
|
20
|
+
|
21
|
+
header {
|
22
|
+
border-bottom: 1px solid #cfd8dc;
|
23
|
+
.row {
|
24
|
+
margin-bottom: 0;
|
25
|
+
}
|
26
|
+
}
|
27
|
+
.dataTables_scrollBody {
|
28
|
+
overflow-y: hidden !important;
|
29
|
+
}
|
30
|
+
|
31
|
+
.bottom-bord {
|
32
|
+
border-bottom: 1px solid #cfd8dc;
|
33
|
+
}
|
34
|
+
.clip-title {
|
35
|
+
p {
|
36
|
+
margin-bottom: 5px;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
.end {
|
41
|
+
margin-bottom: 0 !important;
|
42
|
+
}
|
43
|
+
header .btn-group {
|
44
|
+
margin-top: 15px;
|
45
|
+
}
|
46
|
+
|
47
|
+
header h1 {
|
48
|
+
font-size: 22px;
|
49
|
+
margin: 0;
|
50
|
+
color: #455a64;
|
51
|
+
}
|
52
|
+
|
53
|
+
.style-text-field {
|
54
|
+
/* label color */
|
55
|
+
.input-field label {
|
56
|
+
color: #455a64;
|
57
|
+
font-size: 22px;
|
58
|
+
}
|
59
|
+
/* label focus color */
|
60
|
+
.input-field input[type=text]:focus + label,
|
61
|
+
.input-field textarea:focus + label,
|
62
|
+
{
|
63
|
+
color: #90a4ae;
|
64
|
+
font-size: 18px;
|
65
|
+
}
|
66
|
+
/* label underline focus color */
|
67
|
+
.input-field input[type=text]:focus,
|
68
|
+
.input-field textarea:focus {
|
69
|
+
border-bottom: 1px solid #455a64;
|
70
|
+
box-shadow: 0 1px 0 0 #455a64;
|
71
|
+
}
|
72
|
+
/* valid color */
|
73
|
+
.input-field input[type=text].valid,
|
74
|
+
.input-field textarea.valid {
|
75
|
+
border-bottom: 1px solid #455a64;
|
76
|
+
box-shadow: 0 1px 0 0 #455a64;
|
77
|
+
}
|
78
|
+
/* invalid color */
|
79
|
+
.input-field input[type=text].invalid,
|
80
|
+
.input-field textarea.invalid {
|
81
|
+
border-bottom: 1px solid #455a64;
|
82
|
+
box-shadow: 0 1px 0 0 #455a64;
|
83
|
+
}
|
84
|
+
/* icon prefix focus color */
|
85
|
+
.input-field .prefix.active {
|
86
|
+
color: #455a64;
|
87
|
+
}
|
88
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Dbhero
|
2
|
+
class ApplicationController < ActionController::Base
|
3
|
+
def check_auth
|
4
|
+
if Dbhero.authenticate
|
5
|
+
unless _current_user && call_custom_auth
|
6
|
+
raise ActionController::RoutingError.new('Forbidden')
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def user_representation
|
12
|
+
_current_user.send(Dbhero.user_representation) if _current_user
|
13
|
+
end
|
14
|
+
|
15
|
+
def _current_user
|
16
|
+
if Dbhero.current_user_method.present?
|
17
|
+
send(Dbhero.current_user_method)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def call_custom_auth
|
22
|
+
cond = Dbhero.custom_user_auth_condition
|
23
|
+
if cond.present? && cond.is_a?(Proc)
|
24
|
+
cond.call(_current_user)
|
25
|
+
else
|
26
|
+
true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require_dependency "dbhero/application_controller"
|
2
|
+
require_dependency "responders"
|
3
|
+
require_dependency "google/api_client"
|
4
|
+
require_dependency "google_drive"
|
5
|
+
|
6
|
+
module Dbhero
|
7
|
+
class DataclipsController < ApplicationController
|
8
|
+
before_action :check_auth, except: [:show]
|
9
|
+
before_action :set_dataclip, only: [:show, :edit, :update, :destroy]
|
10
|
+
has_scope :search
|
11
|
+
respond_to :html, :csv
|
12
|
+
|
13
|
+
def index
|
14
|
+
@dataclips = apply_scopes(Dataclip.ordered)
|
15
|
+
end
|
16
|
+
|
17
|
+
def drive
|
18
|
+
token = session.delete(:clip_token)
|
19
|
+
|
20
|
+
google_client.fetch_access_token!(params[:code])
|
21
|
+
google_client.options[:import_data_url] = dataclip_url(id: token, format: 'csv')
|
22
|
+
google_client.export_clip_by_token token
|
23
|
+
|
24
|
+
redirect_to dataclip_path(google_client.dataclip, gdrive_file_url: google_client.exported_file_url)
|
25
|
+
end
|
26
|
+
|
27
|
+
def show
|
28
|
+
check_auth if @dataclip.private?
|
29
|
+
|
30
|
+
@dataclip.query_result
|
31
|
+
|
32
|
+
respond_to do |format|
|
33
|
+
format.html do
|
34
|
+
if params[:export_gdrive]
|
35
|
+
session[:clip_token] = @dataclip.token
|
36
|
+
redirect_to google_client.auth.authorization_uri.to_s
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
format.csv do
|
41
|
+
send_data @dataclip.csv_string, type: Mime::CSV, disposition: "attachment; filename=#{@dataclip.token}.csv"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def new
|
47
|
+
@dataclip = Dataclip.new
|
48
|
+
end
|
49
|
+
|
50
|
+
def edit
|
51
|
+
@dataclip.query_result
|
52
|
+
end
|
53
|
+
|
54
|
+
def create
|
55
|
+
@dataclip = Dataclip.create(dataclip_params.merge(user: user_representation))
|
56
|
+
respond_with @dataclip, location: edit_dataclip_path(@dataclip),notice: 'Dataclip was successfully created.'
|
57
|
+
end
|
58
|
+
|
59
|
+
def update
|
60
|
+
@dataclip.update(dataclip_params)
|
61
|
+
respond_with @dataclip, location: edit_dataclip_path(@dataclip), notice: 'Dataclip was successfully updated.'
|
62
|
+
end
|
63
|
+
|
64
|
+
def destroy
|
65
|
+
@dataclip.destroy
|
66
|
+
redirect_to dataclips_url, notice: 'Dataclip was successfully destroyed.'
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
# Use callbacks to share common setup or constraints between actions.
|
71
|
+
def set_dataclip
|
72
|
+
@dataclip = Dataclip.find_by_token(params[:id])
|
73
|
+
end
|
74
|
+
|
75
|
+
# Only allow a trusted parameter "white list" through.
|
76
|
+
def dataclip_params
|
77
|
+
params.require(:dataclip).permit(:description, :raw_query, :private)
|
78
|
+
end
|
79
|
+
|
80
|
+
def google_client
|
81
|
+
@g_client ||= Dbhero::GdriveExporter.new(redirect_uri: drive_dataclips_url)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Dbhero
|
2
|
+
class Dataclip < ActiveRecord::Base
|
3
|
+
before_create :set_token
|
4
|
+
|
5
|
+
scope :ordered, -> { order(updated_at: :desc) }
|
6
|
+
scope :search, ->(term) { where(arel_table[:description].matches("%#{term}%")) }
|
7
|
+
|
8
|
+
validates :description, :raw_query, presence: true
|
9
|
+
attr_reader :q_result
|
10
|
+
|
11
|
+
def set_token
|
12
|
+
self.token = SecureRandom.uuid unless self.token
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_param
|
16
|
+
self.token
|
17
|
+
end
|
18
|
+
|
19
|
+
def title
|
20
|
+
description.split("\n")[0]
|
21
|
+
end
|
22
|
+
|
23
|
+
def description_without_title
|
24
|
+
description.split("\n")[1..-1].join("\n")
|
25
|
+
end
|
26
|
+
|
27
|
+
def total_rows
|
28
|
+
@total_rows ||= @q_result.rows.length
|
29
|
+
end
|
30
|
+
|
31
|
+
def query_result
|
32
|
+
Dataclip.transaction do
|
33
|
+
begin
|
34
|
+
@q_result ||= ActiveRecord::Base.connection.select_all(self.raw_query)
|
35
|
+
rescue => e
|
36
|
+
self.errors.add(:base, e.message)
|
37
|
+
end
|
38
|
+
raise ActiveRecord::Rollback
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def csv_string
|
43
|
+
query_result
|
44
|
+
csv = ''
|
45
|
+
csv << "#{@q_result.columns.join(',')}\n"
|
46
|
+
@q_result.rows.each { |row| csv << "#{row.join(',')}\n" }
|
47
|
+
csv
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
= stylesheet_link_tag "//cdn.datatables.net/1.10.5/css/jquery.dataTables.min.css"
|
2
|
+
= javascript_include_tag "//cdn.datatables.net/1.10.5/js/jquery.dataTables.min.js"
|
3
|
+
= javascript_include_tag "//cdnjs.cloudflare.com/ajax/libs/floatthead/1.2.10/jquery.floatThead.min.js"
|
4
|
+
|
5
|
+
- show_public_link = defined?(show_public_link) ? show_public_link : false
|
6
|
+
.section
|
7
|
+
.row
|
8
|
+
.col.s6.style-text-field
|
9
|
+
- if show_public_link
|
10
|
+
.input-field
|
11
|
+
i.tiny.mdi-social-share.prefix
|
12
|
+
input#icon_prefix.validate type="text" value=dataclip_url(@dataclip)
|
13
|
+
label for="icon_prefix"
|
14
|
+
strong PUBLIC URL
|
15
|
+
- else
|
16
|
+
p style="margin-top: 40px; margin-bottom: 0;"
|
17
|
+
| total rows returned
|
18
|
+
strong= @dataclip.total_rows
|
19
|
+
.col.s6.right-align style="margin-top: 25px;"
|
20
|
+
a class='dropdown-button btn' href='#' data-activates='export-drop' Export
|
21
|
+
|
22
|
+
ul id='export-drop' class='dropdown-content'
|
23
|
+
li
|
24
|
+
= link_to 'csv', dataclip_path(@dataclip, format: 'csv')
|
25
|
+
li
|
26
|
+
= link_to 'google drive', dataclip_path(@dataclip, export_gdrive: true)
|
27
|
+
|
28
|
+
.col.s12 style="overflow: auto;"
|
29
|
+
table#clip_table.cell-border.striped data-page-length='300'
|
30
|
+
thead.grey.lighten-2
|
31
|
+
tr
|
32
|
+
- @dataclip.q_result.columns.each do |column|
|
33
|
+
th= column
|
34
|
+
tbody
|
35
|
+
- @dataclip.q_result.rows.each do |row|
|
36
|
+
tr
|
37
|
+
- row.each do |value|
|
38
|
+
td style="min-width: 200px;"= value
|