spotlight_search 0.1.8 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +81 -72
  3. data/README.md +93 -45
  4. data/app/controllers/spotlight_search/export_jobs_controller.rb +7 -7
  5. data/app/jobs/spotlight_search/export_job.rb +50 -20
  6. data/app/mailers/spotlight_search/export_mailer.rb +6 -0
  7. data/app/views/spotlight_search/export_mailer/send_error_message.html.erb +7 -0
  8. data/lib/generators/spotlight_search/filter_generator.rb +21 -0
  9. data/lib/generators/spotlight_search/install_generator.rb +27 -0
  10. data/lib/generators/spotlight_search/templates/application.css.scss +26 -0
  11. data/lib/generators/spotlight_search/templates/application.js +19 -0
  12. data/lib/generators/spotlight_search/templates/coffee.js +6 -0
  13. data/lib/generators/spotlight_search/templates/controller.rb.erb +91 -0
  14. data/lib/generators/spotlight_search/templates/environment.js +20 -0
  15. data/lib/generators/spotlight_search/templates/filters.html.erb +10 -0
  16. data/lib/generators/spotlight_search/templates/scaffolds.coffee +11 -0
  17. data/lib/generators/spotlight_search/templates/spotlight_search.rb +5 -0
  18. data/lib/generators/spotlight_search/templates/webpacker.yml +102 -0
  19. data/lib/generators/spotlight_search/templates/webpacker_gem_assets.rb +55 -0
  20. data/lib/spotlight_search.rb +9 -0
  21. data/lib/spotlight_search/exceptions.rb +1 -1
  22. data/lib/spotlight_search/exportable_columns.rb +24 -29
  23. data/lib/spotlight_search/exportable_columns_v2.rb +90 -0
  24. data/lib/spotlight_search/helpers.rb +80 -14
  25. data/lib/spotlight_search/utils.rb +69 -0
  26. data/lib/spotlight_search/version.rb +1 -1
  27. data/spotlight_search.gemspec +3 -4
  28. metadata +27 -26
@@ -1,7 +1,7 @@
1
1
  module SpotlightSearch
2
2
  module Exceptions
3
3
  class InvalidColumns < StandardError
4
- def initialize(columns: [])
4
+ def initialize(columns = [])
5
5
  message = 'Invalid columns found: ' + columns.map(&:to_s).join(', ')
6
6
  super(message)
7
7
  end
@@ -28,35 +28,30 @@ module SpotlightSearch
28
28
  # export_columns enabled: true, except: [:created_at, :updated_at]
29
29
  # end
30
30
  #
31
- def export_columns(enabled: false, only: nil, except: nil)
32
- begin
33
- unless ActiveRecord::Base.connection.migration_context.needs_migration?
34
- if enabled
35
- self.export_enabled = true
36
- all_columns = self.column_names.map(&:to_sym)
37
- if only.present?
38
- unless (valid_columns = only & all_columns).size == only.size
39
- invalid_columns = only - valid_columns
40
- raise SpotlightSearch::Exceptions::InvalidColumns.new(nil, invalid_columns)
41
- end
42
- self.enabled_columns = only
43
- else
44
- self.enabled_columns = all_columns
45
- end
46
- if except.present?
47
- unless (valid_columns = except & all_columns).size == except.size
48
- invalid_columns = except - valid_columns
49
- raise SpotlightSearch::Exceptions::InvalidColumns.new(nil, invalid_columns)
50
- end
51
- self.enabled_columns = self.enabled_columns - except
52
- end
53
- else
54
- self.export_enabled = false
55
- self.enabled_columns = nil
56
- end
31
+ def export_columns(enabled: false, only: nil, except: nil, associated: nil)
32
+ ActiveRecord::Base.connection.migration_context.needs_migration? && return
33
+ return unless enabled
34
+
35
+ self.export_enabled = true
36
+ all_columns = self.column_names.map(&:to_sym)
37
+ if only.present?
38
+ unless (valid_columns = only & all_columns).size == only.size
39
+ invalid_columns = only - valid_columns
40
+ raise SpotlightSearch::Exceptions::InvalidColumns, invalid_columns
41
+ end
42
+ self.enabled_columns = only
43
+ else
44
+ self.enabled_columns = all_columns
45
+ end
46
+ if except.present?
47
+ unless (valid_columns = except & all_columns).size == except.size
48
+ invalid_columns = except - valid_columns
49
+ raise SpotlightSearch::Exceptions::InvalidColumns, invalid_columns
57
50
  end
58
- rescue ActiveRecord::NoDatabaseError
51
+ self.enabled_columns = self.enabled_columns - except
59
52
  end
53
+ rescue ActiveRecord::NoDatabaseError
54
+ Rails.logger.info("No database error")
60
55
  end
61
56
 
62
57
  # Validates whether the selected columns are allowed for export
@@ -72,8 +67,8 @@ module SpotlightSearch
72
67
  end
73
68
 
74
69
  included do
75
- class_attribute :enabled_columns, instance_accessor: false
76
- class_attribute :export_enabled, instance_accessor: false
70
+ class_attribute :enabled_columns, instance_accessor: false, default: nil
71
+ class_attribute :export_enabled, instance_accessor: false, default: false
77
72
  end
78
73
  end
79
74
  end
@@ -0,0 +1,90 @@
1
+ module SpotlightSearch
2
+ module ExportableColumnsV2
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ # Enables or disables export and specifies which all columns can be
7
+ # exported. For enabling export for all columns in all models
8
+ #
9
+ # class ApplicationRecord < ActiveRecord::Base
10
+ # export_columns enabled: true
11
+ # end
12
+ #
13
+ # For disabling export for only specific models
14
+ #
15
+ # class Person < ActiveRecord::Base
16
+ # export_columns enabled: false
17
+ # end
18
+ #
19
+ # For allowing export for only specific columns in a model
20
+ #
21
+ # class Person < ActiveRecord::Base
22
+ # export_columns enabled: true, only: [:created_at, :updated_at]
23
+ # end
24
+ #
25
+ # For excluding only specific columns and allowing all others
26
+ #
27
+ # class Person < ActiveRecord::Base
28
+ # export_columns enabled: true, except: [:created_at, :updated_at]
29
+ # end
30
+ #
31
+ def export_columns(*record_fields, **associated_fields)
32
+ self.enabled_columns = [*record_fields, **associated_fields]
33
+ end
34
+
35
+ def _model_exportable_columns(klass, *record_fields, **associated_fields)
36
+ # Gets all the valid columns of a model
37
+ # If any column is invalid, it also returns it
38
+ raise SpotlightSearch::Exceptions::InvalidValue, "Expected ActiveRecord::Base, Received #{klass}" unless klass < ActiveRecord::Base
39
+
40
+ valid_columns = []
41
+ invalid_columns = []
42
+
43
+ # Base case: verify all the columns that belong to this record
44
+ record_fields.each do |field|
45
+ klass.new.respond_to?(field) ? valid_columns << field : invalid_columns << field
46
+ end
47
+
48
+ # Recursive case: check all associations and verify that they are all valid too
49
+ associated_fields.each do |association, association_record_fields|
50
+ reflection = klass.reflect_on_association(association)
51
+ invalid_columns << association && next unless reflection # Add whole association to invalid columns if it doesn't exist
52
+
53
+ case reflection
54
+ when ActiveRecord::Reflection::BelongsToReflection, ActiveRecord::Reflection::HasOneReflection
55
+ if reflection.polymorphic?
56
+ # We cannot process them further, so we'll assume it works and call it a day
57
+ valid_columns << { association => association_record_fields }
58
+ else
59
+ columns_hash = _model_exportable_columns(reflection.klass, *association_record_fields)
60
+ valid_columns << { association => columns_hash[:valid_columns] }
61
+ invalid_columns << { association => columns_hash[:invalid_columns] } if columns_hash[:invalid_columns].size.positive?
62
+ end
63
+ else
64
+ # one to many relationshops cannot be supported
65
+ invalid_columns << association
66
+ next
67
+ end
68
+ end
69
+
70
+ # return all the valid and invalid columns in a hash
71
+ {
72
+ valid_columns: valid_columns,
73
+ invalid_columns: invalid_columns
74
+ }
75
+ end
76
+
77
+ # Validates whether the selected columns are allowed for export
78
+ def validate_exportable_columns(columns)
79
+ unless columns.is_a? Array
80
+ raise SpotlightSearch::Exceptions::InvalidValue, 'Expected Array. Invalid type received'
81
+ end
82
+ (columns & SpotlightSearch::Utils.serialize_csv_columns(*enabled_columns)).size == columns.size # returns true if all columns are valid
83
+ end
84
+ end
85
+
86
+ included do
87
+ class_attribute :enabled_columns, instance_accessor: false, default: nil
88
+ end
89
+ end
90
+ end
@@ -11,10 +11,10 @@ module SpotlightSearch
11
11
  content_tag("a", title, class: css_class, data: {sort_column: column, sort_direction: direction, behaviour: 'sort', type: 'anchor-filter'})
12
12
  end
13
13
 
14
- def exportable(email, klass)
15
- tag.div do
16
- concat tag.button "Export as excel", class: "modal-btn", data: {toggle: "modal", target: "#exportmodal"}
17
- concat column_pop_up(email, klass)
14
+ def exportable(email, klass, html_class: [])
15
+ tag.a "Export as excel", class: html_class.append("filter-btn modal-btn mr-2"), data: {toggle: "modal", target: "#exportmodal"} do
16
+ concat tag.i class: 'fa fa-download'
17
+ concat tag.span " Excel"
18
18
  end
19
19
  end
20
20
 
@@ -46,16 +46,29 @@ module SpotlightSearch
46
46
 
47
47
  def pop_up_body(email, klass)
48
48
  tag.div class: "modal-body" do
49
- form_tag '/spotlight_search/export_to_file', id: 'export-to-file-form', style: "width: 100%;" do
49
+ form_tag '/spotlight_search/export_to_file', id: 'export-to-file-form', style: "width: 100%;", class:"spotlight-csv-export-form" do
50
50
  concat hidden_field_tag 'email', email, id: 'export-to-file-email'
51
- concat hidden_field_tag 'filters', nil, id: 'export-to-file-filters'
52
- concat hidden_field_tag 'klass', klass.to_s, id: 'export-to-file-klass'
53
- concat checkbox_row(klass)
54
- concat submit_tag 'Export as excel', class: 'btn btn-bordered export-to-file-btn'
51
+ concat hidden_field_tag 'class_name', klass.to_s, id: 'export-to-file-klass'
52
+ params_to_post_helper(filters: controller.filter_params) if controller.filter_params
53
+ params_to_post_helper(sort: controller.sort_params) if controller.sort_params
54
+ case SpotlightSearch.exportable_columns_version
55
+ when :v1
56
+ concat checkbox_row(klass)
57
+ when :v2
58
+ concat checkbox_row_v2(klass)
59
+ end
60
+ concat tag.hr
61
+ concat submit_tag 'Export as excel', class: 'btn btn-primary btn-bordered export-to-file-btn'
55
62
  end
56
63
  end
57
64
  end
58
65
 
66
+ def params_to_post_helper(params)
67
+ URI.decode_www_form(params.to_param).each do |param|
68
+ concat hidden_field_tag param[0], param[1]
69
+ end
70
+ end
71
+
59
72
  def checkbox_row(klass)
60
73
  tag.div class: "row" do
61
74
  klass.enabled_columns.each do |column_name|
@@ -67,30 +80,83 @@ module SpotlightSearch
67
80
  def create_checkbox(column_name)
68
81
  tag.div class: "col-md-4" do
69
82
  concat check_box_tag "columns[]", column_name.to_s
70
- concat column_name.to_s
83
+ concat column_name.to_s.humanize
84
+ end
85
+ end
86
+
87
+ def filter_wrapper(data_behaviours, classes=nil)
88
+ tag.div class: "filter-wrapper d-flex filters #{classes}", data: data_behaviours do
89
+ yield
90
+ end
91
+ end
92
+
93
+ def cm_filter_tag(input_type, scope_name, value, classes = nil, placeholder = nil)
94
+ case input_type
95
+ when 'input'
96
+ tag.div class: 'filter-field' do
97
+ concat text_field_tag scope_name, '', class: "#{classes}", data: {behaviour: "filter", scope: scope_name, type: "input-filter"}, placeholder: "#{placeholder}"
98
+ concat tag.span class: 'fa fa-search search-icon'
99
+ end
100
+ when 'single-select'
101
+ tag.div class: 'filter-field' do
102
+ select_tag scope_name, options_for_select(value), class: "#{classes} select2-single", data: {behaviour: "filter", scope: scope_name, type: "select-filter"}, include_blank: "#{placeholder}"
103
+ end
104
+ when 'multi-select'
105
+ tag.div class: 'filter-field' do
106
+ select_tag scope_name, options_for_select(value), class: "#{classes} select2-single", data: {behaviour: "filter", scope: scope_name, type: "select-filter"}, include_blank: "#{placeholder}", multiple: true
107
+ end
108
+ when 'datetime'
109
+ tag.div class: 'filter-field' do
110
+ concat text_field_tag scope_name, '', class: "#{classes}", data: {behaviour: "filter", scope: scope_name, type: "input-filter", provide: "datepicker"}, placeholder: "#{placeholder}"
111
+ concat tag.span class: 'fa fa-search search-icon'
112
+ end
113
+ when 'daterange'
114
+ tag.div class: 'filter-field' do
115
+ concat text_field_tag scope_name, '', class: "#{classes} filter-rangepicker", data: {behaviour: "filter", scope: scope_name, type: "input-filter"}, placeholder: "#{placeholder}"
116
+ concat tag.span class: 'fa fa-search search-icon'
117
+ end
118
+ end
119
+ end
120
+
121
+ def clear_filters(clear_path, classes=nil, data_behaviours=nil, clear_text=nil)
122
+ link_to "#{clear_text}", clear_path, class: "#{classes}", data: data_behaviours
123
+ end
124
+
125
+ def checkbox_row_v2(klass)
126
+ tag.div class: "row" do
127
+ SpotlightSearch::Utils.serialize_csv_columns(*klass.enabled_columns).each do |column_path|
128
+ concat create_checkbox_v2(column_path)
129
+ end
130
+ end
131
+ end
132
+
133
+ def create_checkbox_v2(column_path)
134
+ tag.div class: "col-md-4" do
135
+ concat check_box_tag "columns[]", column_path, id: column_path.to_s.gsub('/', '-')
136
+ concat " " + column_path.to_s.gsub('/', '_').humanize
71
137
  end
72
138
  end
73
139
 
74
140
  def cm_paginate(facets)
75
- tag.div class: 'text-center' do
141
+ tag.div class: 'cm-pagination' do
76
142
  tag.div class: 'nav navbar navbar-inner' do
77
143
  tag.ul class: 'pagination' do
78
144
  if facets.previous_page != false
79
145
  previous_page = tag.li do
80
- tag.button class: 'btn btn-bordered', data: { behaviour: 'previous-page'} do
146
+ tag.button class: 'cm-pagination__item', data: { behaviour: 'previous-page'} do
81
147
  tag.span "Previous"
82
148
  end
83
149
  end
84
150
  end
85
151
  current_page = content_tag :li do
86
- tag.a class: 'btn btn-bordered mx-2', data: {sort_column: facets.sort[:sort_column], sort_direction: facets.sort[:sort_direction], page: facets.current_page, behaviour: 'current-page' } do
152
+ tag.button class: 'cm-pagination__item', data: {sort_column: facets.sort[:sort_column], sort_direction: facets.sort[:sort_direction], page: facets.current_page, behaviour: 'current-page' } do
87
153
  "Showing #{facets.current_page} of #{facets.total_pages} pages"
88
154
  end
89
155
  end
90
156
 
91
157
  if facets.next_page != false
92
158
  next_page = tag.li do
93
- tag.button class: 'btn btn-bordered', data: { behaviour: 'next-page'} do
159
+ tag.button class: 'cm-pagination__item', data: { behaviour: 'next-page'} do
94
160
  tag.span "Next"
95
161
  end
96
162
  end
@@ -0,0 +1,69 @@
1
+
2
+
3
+ module SpotlightSearch::Utils
4
+ class << self
5
+ def serialize_csv_columns(*columns, **hashes)
6
+ # Turns an arbitrary list of args and kwargs into a list of params to be used in a form
7
+ # For example, turns SpotlightSearch::Utils.serialize_csv_columns(:a, :b, c: [:d, e: :h], f: :g)
8
+ # into [:a, :b, "c/d", "c/e/h", "f/g"]
9
+ columns.map(&:to_s) + hashes.map do |key, value|
10
+ serialize_csv_columns(*value).map do |column|
11
+ "#{key}/#{column}"
12
+ end
13
+ end.reduce([], :+)
14
+ end
15
+
16
+ def deserialize_csv_columns(list, method)
17
+ # Does the opposite operation of the above
18
+ list.reduce(recursive_hash) do |acc, item|
19
+ tokens = item.to_s.split('/')
20
+ send(method, acc, tokens.shift, tokens)
21
+ end
22
+ end
23
+
24
+ def base(hash, key, tokens) # recursive
25
+ hash.empty? && hash = { columns: [], associations: recursive_hash }
26
+ if tokens.empty?
27
+ # base case
28
+ hash[:columns] << key
29
+ else
30
+ # recursive case
31
+ # hash[:associations] ||= {}
32
+ hash[:associations][key] = base(hash[:associations][key], tokens.shift, tokens)
33
+ end
34
+ hash
35
+ end
36
+
37
+ def as_json_params(hash, key, tokens)
38
+ hash.empty? && hash = { only: [], methods: [], include: recursive_hash }
39
+ if tokens.empty?
40
+ # base case
41
+ hash[:methods] << key
42
+ else
43
+ # recursive case
44
+ # hash[:associations] ||= {}
45
+ hash[:include][key] = as_json_params(hash[:include][key], tokens.shift, tokens)
46
+ end
47
+ hash
48
+ end
49
+
50
+ def recursive_hash
51
+ func = ->(h, k) { h[k] = Hash.new(&func) }
52
+ # This hash creates a new hash, infinitely deep, whenever a value is not found
53
+ # recursive_hash[:a][:b][:c][:d] will never fail
54
+ Hash.new(&func)
55
+ end
56
+
57
+ def flatten_hash(hash, prefix="", separator="_")
58
+ hash.reduce({}) do |acc, item|
59
+ case item[1]
60
+ when Hash
61
+ acc.merge(flatten_hash(item[1], "#{prefix}#{item[0]}#{separator}"))
62
+ else
63
+ acc.merge("#{prefix}#{item[0]}" => item[1])
64
+ end
65
+ end
66
+ end
67
+
68
+ end
69
+ end
@@ -1,3 +1,3 @@
1
1
  module SpotlightSearch
2
- VERSION = "0.1.8"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -24,9 +24,8 @@ Gem::Specification.new do |s|
24
24
  # s.require_paths = ["lib"]
25
25
 
26
26
  s.add_development_dependency "bundler", "~> 2.0"
27
- s.add_development_dependency "rake", "~> 10.0"
28
- s.add_development_dependency "rails", "~> 5.0.7"
27
+ s.add_development_dependency "rake", ">= 12.3.3"
28
+ s.add_development_dependency "rails", "~> 5.2.4.2"
29
29
  s.add_development_dependency "rspec", "~> 3.0"
30
- s.add_runtime_dependency 'axlsx'
31
- s.add_runtime_dependency 'zip-zip'
30
+ s.add_runtime_dependency 'caxlsx', "~> 3.0.1"
32
31
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spotlight_search
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anbazhagan Palani
@@ -28,30 +28,30 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: 12.3.3
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: 12.3.3
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rails
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 5.0.7
47
+ version: 5.2.4.2
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 5.0.7
54
+ version: 5.2.4.2
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -67,33 +67,19 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '3.0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: axlsx
70
+ name: caxlsx
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: zip-zip
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
73
+ - - "~>"
88
74
  - !ruby/object:Gem::Version
89
- version: '0'
75
+ version: 3.0.1
90
76
  type: :runtime
91
77
  prerelease: false
92
78
  version_requirements: !ruby/object:Gem::Requirement
93
79
  requirements:
94
- - - ">="
80
+ - - "~>"
95
81
  - !ruby/object:Gem::Version
96
- version: '0'
82
+ version: 3.0.1
97
83
  description: |-
98
84
  This gem should help reduce the efforts in the admin panel.
99
85
  It has search, sort and pagination included
@@ -115,16 +101,31 @@ files:
115
101
  - app/controllers/spotlight_search/export_jobs_controller.rb
116
102
  - app/jobs/spotlight_search/export_job.rb
117
103
  - app/mailers/spotlight_search/export_mailer.rb
104
+ - app/views/spotlight_search/export_mailer/send_error_message.html.erb
118
105
  - app/views/spotlight_search/export_mailer/send_excel_file.html.erb
119
106
  - bin/console
120
107
  - bin/setup
121
108
  - config/routes.rb
109
+ - lib/generators/spotlight_search/filter_generator.rb
110
+ - lib/generators/spotlight_search/install_generator.rb
111
+ - lib/generators/spotlight_search/templates/application.css.scss
112
+ - lib/generators/spotlight_search/templates/application.js
113
+ - lib/generators/spotlight_search/templates/coffee.js
114
+ - lib/generators/spotlight_search/templates/controller.rb.erb
115
+ - lib/generators/spotlight_search/templates/environment.js
116
+ - lib/generators/spotlight_search/templates/filters.html.erb
117
+ - lib/generators/spotlight_search/templates/scaffolds.coffee
118
+ - lib/generators/spotlight_search/templates/spotlight_search.rb
119
+ - lib/generators/spotlight_search/templates/webpacker.yml
120
+ - lib/generators/spotlight_search/templates/webpacker_gem_assets.rb
122
121
  - lib/spotlight_search.rb
123
122
  - lib/spotlight_search/engine.rb
124
123
  - lib/spotlight_search/exceptions.rb
125
124
  - lib/spotlight_search/exportable_columns.rb
125
+ - lib/spotlight_search/exportable_columns_v2.rb
126
126
  - lib/spotlight_search/helpers.rb
127
127
  - lib/spotlight_search/railtie.rb
128
+ - lib/spotlight_search/utils.rb
128
129
  - lib/spotlight_search/version.rb
129
130
  - spotlight_search.gemspec
130
131
  homepage: https://github.com/commutatus/spotlight-search