geo_labels 0.3.3 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/README.md +1 -0
- data/app/assets/javascripts/geo_labels/application.coffee +1 -1
- data/app/controllers/geo_labels/application_controller.rb +5 -0
- data/app/controllers/geo_labels/contacts_controller.rb +1 -1
- data/app/controllers/geo_labels/dashboard_controller.rb +30 -8
- data/app/helpers/geo_labels/application_helper.rb +1 -0
- data/app/models/geo_labels/contact.rb +15 -1
- data/app/models/geo_labels/contact_label.rb +1 -0
- data/app/models/geo_labels/label.rb +14 -0
- data/app/views/geo_labels/contacts/_form.html.slim +9 -2
- data/app/views/geo_labels/contacts/index.html.slim +6 -0
- data/app/views/geo_labels/contacts/show.html.slim +5 -1
- data/app/views/geo_labels/dashboard/about.html.slim +3 -0
- data/app/views/geo_labels/dashboard/export.html.slim +7 -3
- data/app/views/geo_labels/dashboard/main.html.slim +8 -1
- data/app/views/geo_labels/menu/_navigation.html.slim +23 -0
- data/app/views/geo_labels/menu/_right_menu_general.html.slim +11 -0
- data/app/views/geo_labels/users/_abilities.html.slim +20 -0
- data/app/views/layouts/geo_labels/application.html.slim +1 -1
- data/config/locales/geo_labels.en.yml +17 -0
- data/config/locales/geo_labels.es.yml +20 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20231206183544_add_url_to_geo_labels_contacts.rb +5 -0
- data/lib/geo_labels/exporter.rb +5 -2
- data/lib/geo_labels/importer/csv_data.rb +43 -0
- data/lib/geo_labels/importer/yaml_data.rb +116 -0
- data/lib/geo_labels/importer.rb +33 -112
- data/lib/geo_labels/version.rb +1 -1
- data/lib/geo_labels.rb +2 -0
- metadata +12 -6
- data/app/views/geo_labels/application/_navigation.html.slim +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30768188b560ce1db38ddd61a8f06c37eb44334bba0ea8309efbf1f0b6b809fe
|
4
|
+
data.tar.gz: b295252da7f1b305a7a7029884b3e50c6c239ba54b3914727b51469d38021f4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d145b7d63a43d47d771b1a76c06b17afa032d035d385bc64f89fb0d565b1809b3021f6d970fb018e1ba9983c8a270443d41b5600261046cb612a6cb2e14ef209
|
7
|
+
data.tar.gz: fe7b5b251a2a7e51f76f4a39a0aafa8ac308f830c0ad63544cb7fad9d469a96162ea212c07cc41bdc3f48c3dcde8c49d744656a7c20420c33188c6267a33d34f
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,31 @@
|
|
1
1
|
CHANGELOG of geo\_labels
|
2
2
|
============================
|
3
3
|
|
4
|
+
2023-12-23 v0.4.3
|
5
|
+
-------------------------
|
6
|
+
* Optimize nested label representation to sort of N+1 query strategy
|
7
|
+
* Show labels on main query result page of contacts
|
8
|
+
|
9
|
+
2023-10-25 v0.4.2
|
10
|
+
-------------------------
|
11
|
+
* Truncate label names to prevent ugly outputting
|
12
|
+
|
13
|
+
2023-10-25 v0.4.1
|
14
|
+
-------------------------
|
15
|
+
* Add CSV import options
|
16
|
+
* Add authorization options
|
17
|
+
* Allow unsecure access by explicit authorization in `app/models/ability.rb`
|
18
|
+
<code>
|
19
|
+
can :manage, GeoLabels::Engine
|
20
|
+
can :manage, GeoLabels::Contact
|
21
|
+
can :manage, GeoLabels::Label
|
22
|
+
</code>
|
23
|
+
|
24
|
+
2023-10-24 v0.4.0
|
25
|
+
-------------------------
|
26
|
+
* Set default map zoom level to 13
|
27
|
+
* Add CSV export option
|
28
|
+
|
4
29
|
2023-09-07 v0.3.3
|
5
30
|
-------------------------
|
6
31
|
* OpenStreetMap gives coordinates in the url after double click. Support copy paste of that format
|
data/README.md
CHANGED
@@ -35,6 +35,7 @@ In your `config/routes.rb` file add:
|
|
35
35
|
```
|
36
36
|
|
37
37
|
### Add the authorizations
|
38
|
+
Authorization is handled by [cancan](https://github.com/CanCanCommunity/cancancan).
|
38
39
|
In your `app/models/ability.rb` file add the authorizations.
|
39
40
|
This is a custom operation that you have to adjust to your needs.
|
40
41
|
To allow all users full controll to the contracts add:
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module GeoLabels
|
3
3
|
class ApplicationController < ::ApplicationController
|
4
|
+
before_action :authorize_engine!
|
4
5
|
|
5
6
|
helper_method :query
|
6
7
|
|
@@ -13,5 +14,9 @@ module GeoLabels
|
|
13
14
|
def query
|
14
15
|
params[:q]
|
15
16
|
end
|
17
|
+
|
18
|
+
def authorize_engine!
|
19
|
+
authorize! :read, GeoLabels::Engine
|
20
|
+
end
|
16
21
|
end
|
17
22
|
end
|
@@ -9,7 +9,7 @@ module GeoLabels
|
|
9
9
|
authorize! :index, record_class
|
10
10
|
@q = record_class.ransack query
|
11
11
|
@q.sorts = 'name asc' if @q.sorts.empty?
|
12
|
-
@records = @q.result.page(params[:page]).per(100)
|
12
|
+
@records = @q.result.includes(:labels).page(params[:page]).per(100)
|
13
13
|
respond_with(@records)
|
14
14
|
end
|
15
15
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module GeoLabels
|
3
4
|
class DashboardController < ApplicationController
|
4
5
|
|
@@ -9,27 +10,48 @@ module GeoLabels
|
|
9
10
|
# fomantic-ui uses comma separated id values
|
10
11
|
label_ids = label_ids.to_s.split(',') unless label_ids.is_a?(Array)
|
11
12
|
states_array = (params[:states].presence || 'approved').split(',')
|
12
|
-
@contacts = Contact.geocoded.for_label_ids(label_ids, predication: params[:predication], states: states_array)
|
13
|
+
@contacts = Contact.geocoded.includes(:labels).for_label_ids(label_ids, predication: params[:predication], states: states_array)
|
13
14
|
end
|
14
15
|
|
16
|
+
def about; end
|
17
|
+
|
18
|
+
# SHOW action for the view to call the export_file action
|
15
19
|
def export
|
16
|
-
|
20
|
+
authorize! :read, GeoLabels::Contact
|
21
|
+
authorize! :read, GeoLabels::Label
|
17
22
|
end
|
18
23
|
|
19
24
|
def export_file
|
25
|
+
authorize! :read, GeoLabels::Contact
|
26
|
+
authorize! :read, GeoLabels::Label
|
27
|
+
|
20
28
|
timestamp = Time.now.utc.iso8601.first(19).gsub(/-|:/, '')
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
29
|
+
respond_to do |format|
|
30
|
+
format.yaml do
|
31
|
+
send_data GeoLabels::Exporter.export_str,
|
32
|
+
filename: "GeoLabels-export-#{timestamp}.yml",
|
33
|
+
type: :text,
|
34
|
+
disposition: :attachment
|
35
|
+
end
|
36
|
+
format.csv do
|
37
|
+
send_data GeoLabels::Contact.to_csv,
|
38
|
+
filename: "GeoLabels-export-#{timestamp}.csv",
|
39
|
+
type: :text,
|
40
|
+
disposition: :attachment
|
41
|
+
end
|
42
|
+
end
|
25
43
|
end
|
26
44
|
|
27
45
|
def import
|
28
|
-
|
46
|
+
authorize! :update, GeoLabels::Contact
|
47
|
+
authorize! :update, GeoLabels::Label
|
29
48
|
end
|
30
49
|
|
31
50
|
def import_file
|
32
|
-
|
51
|
+
authorize! :update, GeoLabels::Contact
|
52
|
+
authorize! :update, GeoLabels::Label
|
53
|
+
|
54
|
+
GeoLabels::Importer.import params[:file]
|
33
55
|
redirect_to root_path, notice: 'File imported'
|
34
56
|
rescue => e
|
35
57
|
redirect_to import_path, alert: e.message
|
@@ -13,6 +13,7 @@ module GeoLabels
|
|
13
13
|
label_name = link_to(item[:name], label_path(item[:id]))
|
14
14
|
edit_link = table_edit_link(item, geo_labels.edit_label_path(item[:id]))
|
15
15
|
add_child_link = link_to(tag.i(class: 'plus icon'), geo_labels.new_label_path(parent_id: item[:id]))
|
16
|
+
add_child_link = edit_link = '' unless can? :edit, GeoLabels::Label
|
16
17
|
output += "#{lead_space}#{tag.li(label_name + add_child_link + edit_link)}\n".html_safe
|
17
18
|
next unless item[:children].present?
|
18
19
|
|
@@ -7,7 +7,7 @@ module GeoLabels
|
|
7
7
|
include RatingsSupport
|
8
8
|
|
9
9
|
RANSACKABLE_ATTRIBUTES = %w[
|
10
|
-
city country created_at department description
|
10
|
+
city country created_at department url description
|
11
11
|
id latitude longitude name state street
|
12
12
|
subsection updated_at
|
13
13
|
].freeze
|
@@ -42,6 +42,20 @@ module GeoLabels
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
def self.to_csv
|
46
|
+
require 'csv'
|
47
|
+
contacts = includes(:labels).all
|
48
|
+
additional_attributes = %w[description state street subsection city department country url latitude longitude]
|
49
|
+
CSV.generate do |csv|
|
50
|
+
csv << %w[name labels] + additional_attributes
|
51
|
+
contacts.each do |contact|
|
52
|
+
csv << [contact.name, contact.labels.map(&:name).compact.sort.join('|')] +
|
53
|
+
contact.attributes.fetch_values(*additional_attributes)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
45
59
|
def label_ids=(array_or_string)
|
46
60
|
super array_or_string.is_a?(Array) ? array_or_string : array_or_string.split(',')
|
47
61
|
end
|
@@ -9,6 +9,7 @@ module GeoLabels
|
|
9
9
|
|
10
10
|
acts_as_nested_set
|
11
11
|
|
12
|
+
normalizes :name, with: ->(name) { name.strip }
|
12
13
|
validates :name, presence: true
|
13
14
|
|
14
15
|
def self.descendants_for_ids(ids)
|
@@ -20,9 +21,22 @@ module GeoLabels
|
|
20
21
|
descendants_scope
|
21
22
|
end
|
22
23
|
|
24
|
+
def self.each_h_with_level(&blk)
|
25
|
+
returner = proc do |label, level|
|
26
|
+
blk.call(label, level)
|
27
|
+
label[:children].try :each do |child|
|
28
|
+
returner.call(child, level + 1)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
Exporter.labels_tree_h.each do |label| # {name: 'Food', id: 3, children: [{name: 'Italian'...}]}
|
32
|
+
returner.call(label, 0)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
23
36
|
def associated_contacts
|
24
37
|
ids = ContactLabel.joins(:label).merge(self_and_descendants).pluck(:contact_id).uniq
|
25
38
|
Contact.order(:name).find(ids)
|
26
39
|
end
|
40
|
+
|
27
41
|
end
|
28
42
|
end
|
@@ -36,9 +36,12 @@
|
|
36
36
|
.menu
|
37
37
|
- GeoLabels::Contact::STATE_OPTIONS.each do |option|
|
38
38
|
.item data-value=option = option
|
39
|
+
.field
|
40
|
+
label= at :url
|
41
|
+
= f.text_field :url
|
39
42
|
.field
|
40
43
|
label= at :description
|
41
|
-
= f.
|
44
|
+
= f.text_area :description, rows: 3
|
42
45
|
.field
|
43
46
|
.ui.compact.accordion
|
44
47
|
.title
|
@@ -53,7 +56,11 @@
|
|
53
56
|
i.dropdown.icon
|
54
57
|
.default.text= GeoLabels::Label.model_name.human_plural
|
55
58
|
.menu
|
56
|
-
-
|
59
|
+
- GeoLabels::Label.each_h_with_level do |label_h, level|
|
60
|
+
.item data-value=label_h[:id]
|
61
|
+
=> '-' * level
|
62
|
+
= label_h[:name]
|
63
|
+
/- nested_set_options(GeoLabels::Label) {|i| "#{'-' * i.level} #{i.name}" }.each do |(label_name, label_id)|
|
57
64
|
.item data-value=label_id
|
58
65
|
= label_name
|
59
66
|
.ui.bottom.attached.segment
|
@@ -11,6 +11,7 @@
|
|
11
11
|
th= sort_link @q, :city, at(:city), default_order: :asc
|
12
12
|
th= sort_link @q, :department, at(:department), default_order: :asc
|
13
13
|
th= sort_link @q, :country, at(:country), default_order: :asc
|
14
|
+
th= GeoLabels::Label.model_name.human_plural
|
14
15
|
th.collapsing.column-filter.actions
|
15
16
|
= search_result_info @records
|
16
17
|
= search_row class: 'ui mini form' do
|
@@ -18,6 +19,7 @@
|
|
18
19
|
th= f.search_field :city_cont, placeholder: at(:city)
|
19
20
|
th= f.search_field :department_cont, placeholder: at(:department)
|
20
21
|
th= f.search_field :country_cont, placeholder: at(:country)
|
22
|
+
th
|
21
23
|
th.collapsing.column-filter.actions
|
22
24
|
button.ui.mini.primary.icon.button
|
23
25
|
i.filter.icon
|
@@ -32,6 +34,10 @@
|
|
32
34
|
td= record.city
|
33
35
|
td= record.department
|
34
36
|
td= record.country
|
37
|
+
td
|
38
|
+
.ui.horizontal.divided.list
|
39
|
+
- record.labels.each do |label|
|
40
|
+
= link_to label.name, label, class: 'item'
|
35
41
|
td.collapsing.actions
|
36
42
|
= table_show_link record
|
37
43
|
= table_edit_link record
|
@@ -27,9 +27,13 @@
|
|
27
27
|
= link_to 'Approve', geo_labels.approve_contact_path(@record.id), method: :put, class: 'ui right floated button positive'
|
28
28
|
.ui.basic.label class=(@record.state == 'rejected' ? 'red' : (@record.state == 'approved' ? 'green' : 'yelow'))
|
29
29
|
= @record.state
|
30
|
+
tr
|
31
|
+
td= at :url
|
32
|
+
td
|
33
|
+
a href=@record.url target='_blank' = @record.url
|
30
34
|
tr
|
31
35
|
td= at :description
|
32
|
-
td
|
36
|
+
td== @record.description
|
33
37
|
- if @record.geocoded?
|
34
38
|
tr
|
35
39
|
td Location
|
@@ -1,4 +1,8 @@
|
|
1
|
-
.ui.
|
1
|
+
.ui.container
|
2
2
|
= page_title t('geo_labels.export')
|
3
|
-
.ui.segment
|
4
|
-
|
3
|
+
.ui.top.attached.segment
|
4
|
+
= link_to t('geo_labels.export_yml.title'), geo_labels.export_file_path(format: :yml), class: 'ui primary button'
|
5
|
+
.ui.basic.left.pointing.label= t 'geo_labels.export_yml.explanation'
|
6
|
+
.ui.bottom.attached.segment
|
7
|
+
= link_to t('geo_labels.export_csv.title'), geo_labels.export_file_path(format: :csv), class: 'ui primary button'
|
8
|
+
.ui.basic.left.pointing.label= t 'geo_labels.export_csv.explanation'
|
@@ -11,7 +11,11 @@
|
|
11
11
|
.default.text= GeoLabels::Label.model_name.human_plural
|
12
12
|
.menu
|
13
13
|
/- ISO3166::Country.all.each do |country|
|
14
|
-
-
|
14
|
+
- GeoLabels::Label.each_h_with_level do |label_h, level|
|
15
|
+
.item data-value=label_h[:id]
|
16
|
+
=> '-' * level
|
17
|
+
= label_h[:name]
|
18
|
+
/- nested_set_options(GeoLabels::Label) {|i| "#{'-' * i.level} #{i.name}" }.each do |(label_name, label_id)|
|
15
19
|
.item data-value=label_id
|
16
20
|
= label_name
|
17
21
|
.four.wide.field
|
@@ -47,6 +51,9 @@
|
|
47
51
|
.ui.item
|
48
52
|
= link_to contact.name, contact
|
49
53
|
= address_tag contact.address, title: contact.description
|
54
|
+
- contact.labels.sort_by(&:name).each do |label|
|
55
|
+
span.ui.label= label.name
|
56
|
+
|
50
57
|
.ui.bottom.attached.tab.segment data-tab='map'
|
51
58
|
#map-canvas
|
52
59
|
/.ui.horizontal.divider= GeoLabels::Contact.model_name.human_plural
|
@@ -0,0 +1,23 @@
|
|
1
|
+
.ui.inverted.menu.fixed
|
2
|
+
= link_to main_app.root_path, class: 'header item'
|
3
|
+
= Rails.application.config.x.geo_labels.link_home_content.call.to_s.html_safe
|
4
|
+
= link_to geo_labels.root_path, class: 'header item'
|
5
|
+
.ui.icon
|
6
|
+
i.map.icon
|
7
|
+
= Rails.application.config.x.geo_labels.application_title.call.to_s.html_safe
|
8
|
+
- if can? :read, GeoLabels::Contact
|
9
|
+
= link_to GeoLabels::Contact.model_name.human_plural, [GeoLabels::Contact], class: ['item', active_class('geo_labels/contacts')]
|
10
|
+
- if can? :read, GeoLabels::Label
|
11
|
+
= link_to GeoLabels::Label.model_name.human_plural, [GeoLabels::Label], class: ['item', active_class('geo_labels/labels')]
|
12
|
+
.right.menu
|
13
|
+
.ui.dropdown.item
|
14
|
+
span= current_user.try(:nickname) || current_user.try(:email) || 'Menu'
|
15
|
+
i.dropdown.icon
|
16
|
+
.menu
|
17
|
+
= render 'geo_labels/menu/right_menu_general'
|
18
|
+
- if current_user.present?
|
19
|
+
= link_to main_app.destroy_user_session_path, method: :delete, class: 'item'
|
20
|
+
i.sign.out.icon
|
21
|
+
span.text= t 'user.sign_out'
|
22
|
+
- else
|
23
|
+
= link_to t('devise.sessions.new.sign_in'), main_app.new_user_session_path, class: 'item'
|
@@ -0,0 +1,11 @@
|
|
1
|
+
= link_to geo_labels.about_path, class: 'item'
|
2
|
+
i.question.circle.outline.icon
|
3
|
+
span.text= t 'geo_labels.about.title'
|
4
|
+
- if can? :read, GeoLabels::Contact and can? :read, GeoLabels::Label
|
5
|
+
= link_to geo_labels.export_path, class: 'item'
|
6
|
+
i.file.export.icon
|
7
|
+
span.text= t('geo_labels.export')
|
8
|
+
- if can? :update, GeoLabels::Contact and can? :update, GeoLabels::Label
|
9
|
+
= link_to geo_labels.import_path, class: 'item'
|
10
|
+
i.file.import.icon
|
11
|
+
span.text= t('geo_labels.import')
|
@@ -0,0 +1,20 @@
|
|
1
|
+
.ui.top.attached.block.header= Rails.application.config.x.geo_labels.application_title.call
|
2
|
+
table.roles.ui.bottom.attached.compact.celled.table
|
3
|
+
thead
|
4
|
+
/th= HawkEye::Analysis.model_name.human
|
5
|
+
th
|
6
|
+
th read
|
7
|
+
th manage
|
8
|
+
tbody
|
9
|
+
tr
|
10
|
+
td Engine
|
11
|
+
td= dunlop_ability :read, 'engine', 'geo_labels'
|
12
|
+
td= dunlop_ability :manage, 'engine', 'geo_labels'
|
13
|
+
tr
|
14
|
+
td= GeoLabels::Contact.model_name.human
|
15
|
+
td= dunlop_class_ability :read, GeoLabels::Contact
|
16
|
+
td= dunlop_class_ability :manage, GeoLabels::Contact
|
17
|
+
tr
|
18
|
+
td= GeoLabels::Label.model_name.human
|
19
|
+
td= dunlop_class_ability :read, GeoLabels::Label
|
20
|
+
td= dunlop_class_ability :manage, GeoLabels::Label
|
@@ -15,7 +15,7 @@ html lang="en"
|
|
15
15
|
window.geo_labels = #{{paths: GeoLabels::Engine.client_paths}.to_json.html_safe};
|
16
16
|
= content_for :head
|
17
17
|
body
|
18
|
-
header= render 'navigation'
|
18
|
+
header= render 'geo_labels/menu/navigation'
|
19
19
|
span= Rails.env
|
20
20
|
main role="main"
|
21
21
|
= render 'messages'
|
@@ -1,6 +1,23 @@
|
|
1
1
|
en:
|
2
|
+
activerecord:
|
3
|
+
models:
|
4
|
+
geo_labels/contact: Contact
|
5
|
+
geo_labels/label: Label
|
6
|
+
plural:
|
7
|
+
geo_labels/contact: Contacts
|
8
|
+
geo_labels/label: Labels
|
9
|
+
attributes:
|
10
|
+
geo_labels/contact:
|
11
|
+
url: 'URL'
|
12
|
+
|
2
13
|
geo_labels:
|
3
14
|
export: Export
|
15
|
+
export_yml:
|
16
|
+
title: 'Export backup file (yaml)'
|
17
|
+
explanation: 'This export can be used to change and rebuild the data'
|
18
|
+
export_csv:
|
19
|
+
title: 'Export CSV'
|
20
|
+
explanation: 'This file is for spreadsheet views'
|
4
21
|
import: Import
|
5
22
|
tree_title: Tree
|
6
23
|
select_labels_text: Select %{models} and search...
|
@@ -6,9 +6,19 @@ es:
|
|
6
6
|
plural:
|
7
7
|
geo_labels/contact: Contactos
|
8
8
|
geo_labels/label: Etiquetas
|
9
|
+
attributes:
|
10
|
+
geo_labels/contact:
|
11
|
+
url: 'URL'
|
12
|
+
|
9
13
|
|
10
14
|
geo_labels:
|
11
15
|
export: Exportar
|
16
|
+
export_yml:
|
17
|
+
title: 'Exportar archivo de restaurar (yaml)'
|
18
|
+
explanation: 'Ese archivo se puede utilizar en rehacer el sistema'
|
19
|
+
export_csv:
|
20
|
+
title: 'Exportar CSV'
|
21
|
+
explanation: 'Ese archivo es para ver los datos en hoja de cálculo'
|
12
22
|
import: Importar
|
13
23
|
tree_title: Árbol
|
14
24
|
select_labels_text: Seleccionar %{models} y busca...
|
@@ -20,3 +30,13 @@ es:
|
|
20
30
|
Pues en Google maps se puede copiar y pegar<br>
|
21
31
|
coordinatos manuál. Ejemplo:<br>
|
22
32
|
<code>4.973122, -75.623981</code>
|
33
|
+
about:
|
34
|
+
title: 'Sobre GEO'
|
35
|
+
content: >
|
36
|
+
<p>El sistema para manegar listas de contactos y poner etiquetas para buscarlos tiene
|
37
|
+
algunas opciones</p>
|
38
|
+
|
39
|
+
<h3>Manegar etiquetas</h3>
|
40
|
+
<p>Las etiquetas son flexibles en como estructurar los contactos. Etiquetas pueden tener
|
41
|
+
una jerarquia.
|
42
|
+
</p>
|
data/config/routes.rb
CHANGED
data/lib/geo_labels/exporter.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'yaml'
|
3
4
|
module GeoLabels
|
5
|
+
# Yaml exporter of the engine
|
4
6
|
class Exporter
|
5
7
|
|
6
8
|
def self.export_h
|
@@ -20,7 +22,7 @@ module GeoLabels
|
|
20
22
|
output = ''
|
21
23
|
tree_formatter = proc do |items, lead_space|
|
22
24
|
items.each do |item|
|
23
|
-
output
|
25
|
+
output = "#{output}#{lead_space}#{item[:name]}\n"
|
24
26
|
tree_formatter.call(item[:children], "#{lead_space} ") if item[:children].present?
|
25
27
|
end
|
26
28
|
end
|
@@ -33,7 +35,7 @@ module GeoLabels
|
|
33
35
|
GeoLabels::Contact.includes(:labels).map do |contact|
|
34
36
|
contact
|
35
37
|
.attributes
|
36
|
-
.slice(*%w[name street subsection city department country state description latitude longitude])
|
38
|
+
.slice(*%w[name street subsection city department country state url description latitude longitude])
|
37
39
|
.merge('labels' => contact.labels.map(&:name))
|
38
40
|
.select { |_, v| v.present? }
|
39
41
|
.transform_values do |value|
|
@@ -58,5 +60,6 @@ module GeoLabels
|
|
58
60
|
end
|
59
61
|
tree[nil][:children]
|
60
62
|
end
|
63
|
+
|
61
64
|
end
|
62
65
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'csv'
|
3
|
+
|
4
|
+
module GeoLabels
|
5
|
+
module Importer
|
6
|
+
class CsvData
|
7
|
+
attr_reader :new_contacts
|
8
|
+
ROW_ATTRIBUTES = %w[name description state street subsection city department country url latitude longitude]
|
9
|
+
|
10
|
+
def import(data)
|
11
|
+
@new_contacts = []
|
12
|
+
|
13
|
+
counter = 2 # start counting from 1 and skip the header. Works with Apple -> Numbers application
|
14
|
+
CSV.parse(data, headers: true).each do |row|
|
15
|
+
name = row['name']
|
16
|
+
raise "Row #{counter} has no name" unless name.present?
|
17
|
+
|
18
|
+
label_names = row['labels'].to_s.strip.split('|').map(&:strip).select(&:present?)
|
19
|
+
raise "Row #{counter} (#{name}) has no labels" unless label_names.any?
|
20
|
+
labels = label_names.map do |label_name|
|
21
|
+
labels_h[label_name] or raise "Row #{counter} has a label: '#{label_name}' that is not yet in the system. Please add before using it in an import"
|
22
|
+
end
|
23
|
+
|
24
|
+
contact = Contact.new(row.to_h.slice(*ROW_ATTRIBUTES))
|
25
|
+
contact.labels = labels
|
26
|
+
raise "Row #{counter} creates an invalid contact. Errors: #{contact.errors.full_messages.to_sentence}" unless contact.valid?
|
27
|
+
new_contacts.push contact
|
28
|
+
counter += 1
|
29
|
+
end
|
30
|
+
|
31
|
+
Contact.delete_all
|
32
|
+
ContactLabel.delete_all
|
33
|
+
new_contacts.each do |contact|
|
34
|
+
contact.save!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def labels_h
|
39
|
+
@labels_h ||= Label.all.map{ |l| [l.name, l] }.to_h
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GeoLabels
|
4
|
+
module Importer
|
5
|
+
class YamlData
|
6
|
+
def self.import(yaml_h)
|
7
|
+
GeoLabels::Contact.delete_all
|
8
|
+
GeoLabels::Label.delete_all
|
9
|
+
GeoLabels::ContactLabel.delete_all
|
10
|
+
labels_dict = persist_labels_tree yaml_h['labels']
|
11
|
+
yaml_h['contacts'].each do |contact_attributes|
|
12
|
+
label_names = Array(contact_attributes.delete('labels'))
|
13
|
+
contact = GeoLabels::Contact.new(contact_attributes)
|
14
|
+
contact.labels = label_names.map{ labels_dict[_1] }.compact
|
15
|
+
contact.save
|
16
|
+
end
|
17
|
+
GeoLabels::Contact.count
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.labels_text_to_tree(tree_text)
|
21
|
+
txt = tree_text.gsub("\n\u0001", "\n") # remove START OF HEADING TRASH
|
22
|
+
txt.gsub! /^(\s*)[-*] /, '\1 ' # remove list punctuations and interpret just the spaces
|
23
|
+
lines = txt.split("\n")
|
24
|
+
root_label, current_indentation = ImportLabel.new('root'), -1
|
25
|
+
working_object = root_label
|
26
|
+
lines.each do |line|
|
27
|
+
line_indentation = (line.match(/\s+/).try(:[], 0).try(:size) || 0) / 2
|
28
|
+
# name = line[(2*line_indentation)..] # strip the leading spaces
|
29
|
+
name = line.strip
|
30
|
+
label = ImportLabel.new(name)
|
31
|
+
if line_indentation > current_indentation # level deeper
|
32
|
+
working_object = working_object.add_child(label)
|
33
|
+
elsif line_indentation < current_indentation
|
34
|
+
(current_indentation - line_indentation).times do
|
35
|
+
working_object = working_object.parent unless working_object.parent.is_root?
|
36
|
+
end
|
37
|
+
working_object = working_object.add_sibling(label)
|
38
|
+
else
|
39
|
+
working_object = working_object.add_sibling label
|
40
|
+
end
|
41
|
+
current_indentation = line_indentation
|
42
|
+
end
|
43
|
+
root_label
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.persist_labels_tree(tree_or_text = nil)
|
47
|
+
tree = case tree_or_text
|
48
|
+
when ImportLabel then tree_or_text
|
49
|
+
when String then labels_text_to_tree(tree_or_text)
|
50
|
+
else
|
51
|
+
raise 'Must provide a tree label object or tree text'
|
52
|
+
end
|
53
|
+
record_lookup_dict = {}
|
54
|
+
persister = proc do |items, parent|
|
55
|
+
items.each do |item|
|
56
|
+
record = Label.create name: item.name, parent: parent
|
57
|
+
record_lookup_dict[item.name] = record
|
58
|
+
persister.call item.children, record if item.children.present?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
persister.call(tree.children, nil)
|
62
|
+
record_lookup_dict
|
63
|
+
end
|
64
|
+
|
65
|
+
class LabelList < Array
|
66
|
+
attr_accessor :parent
|
67
|
+
end
|
68
|
+
|
69
|
+
class ImportLabel
|
70
|
+
attr_accessor :name, :parent
|
71
|
+
|
72
|
+
def initialize(name)
|
73
|
+
@name = name
|
74
|
+
end
|
75
|
+
|
76
|
+
def is_root?
|
77
|
+
name == 'root'
|
78
|
+
end
|
79
|
+
|
80
|
+
def children
|
81
|
+
@children ||= LabelList.new
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_sibling(label)
|
85
|
+
label.parent = parent
|
86
|
+
parent.children.push label
|
87
|
+
label
|
88
|
+
end
|
89
|
+
|
90
|
+
def add_child(label)
|
91
|
+
children.push label
|
92
|
+
label.parent = self
|
93
|
+
label
|
94
|
+
end
|
95
|
+
|
96
|
+
def inspect
|
97
|
+
# "#{name} => #{children.inspect}"
|
98
|
+
# children.any? ? "<#{name}> => #{children.inpsect}" : "#{name}"
|
99
|
+
base = name.to_s
|
100
|
+
base = "#{base} => #{children.inspect}" if children.any?
|
101
|
+
base
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_h
|
105
|
+
if is_root?
|
106
|
+
{labels: children.map(&:to_h)}
|
107
|
+
else
|
108
|
+
r = {name: name}
|
109
|
+
r[:children] = children.map(&:to_h) if children.any?
|
110
|
+
r
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/geo_labels/importer.rb
CHANGED
@@ -1,128 +1,49 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module GeoLabels
|
3
4
|
module Importer
|
4
5
|
def self.import(subject)
|
5
|
-
|
6
|
-
when String
|
7
|
-
if subject.start_with?('/') or subject.start_with?('./')
|
8
|
-
YAML.load_file(subject)
|
9
|
-
else
|
10
|
-
YAML.safe_load(subject)
|
11
|
-
end
|
12
|
-
when File then YAML.safe_load(subject.read)
|
13
|
-
when Pathname then YAML.safe_load(subject.read)
|
14
|
-
when ActionDispatch::Http::UploadedFile then YAML.safe_load(subject.read)
|
15
|
-
else
|
16
|
-
raise 'Argument not recognized'
|
17
|
-
end
|
18
|
-
GeoLabels::Contact.delete_all
|
19
|
-
GeoLabels::Label.delete_all
|
20
|
-
GeoLabels::ContactLabel.delete_all
|
21
|
-
labels_dict = persist_labels_tree yaml_h['labels']
|
22
|
-
yaml_h['contacts'].each do |contact_attributes|
|
23
|
-
label_names = Array(contact_attributes.delete('labels'))
|
24
|
-
contact = GeoLabels::Contact.new(contact_attributes)
|
25
|
-
contact.labels = label_names.map{ labels_dict[_1] }.compact
|
26
|
-
contact.save
|
27
|
-
end
|
28
|
-
GeoLabels::Contact.count
|
29
|
-
end
|
6
|
+
type, data = get_type_and_data(subject)
|
30
7
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
lines.each do |line|
|
38
|
-
line_indentation = (line.match(/\s+/).try(:[], 0).try(:size) || 0) / 2
|
39
|
-
# name = line[(2*line_indentation)..] # strip the leading spaces
|
40
|
-
name = line.strip
|
41
|
-
label = ImportLabel.new(name)
|
42
|
-
if line_indentation > current_indentation # level deeper
|
43
|
-
working_object = working_object.add_child(label)
|
44
|
-
elsif line_indentation < current_indentation
|
45
|
-
(current_indentation - line_indentation).times do
|
46
|
-
working_object = working_object.parent unless working_object.parent.is_root?
|
47
|
-
end
|
48
|
-
working_object = working_object.add_sibling(label)
|
49
|
-
else
|
50
|
-
working_object = working_object.add_sibling label
|
51
|
-
end
|
52
|
-
current_indentation = line_indentation
|
8
|
+
if type == :yml
|
9
|
+
GeoLabels::Importer::YamlData.import(YAML.safe_load(data))
|
10
|
+
elsif type == :csv
|
11
|
+
GeoLabels::Importer::CsvData.new.import(data)
|
12
|
+
else
|
13
|
+
raise 'Argument not recognized'
|
53
14
|
end
|
54
|
-
root_label
|
55
15
|
end
|
56
16
|
|
57
|
-
def self.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
17
|
+
def self.get_type_and_data(subject)
|
18
|
+
case subject
|
19
|
+
when String
|
20
|
+
if subject.start_with?('/') or subject.start_with?('./') # is a path spec
|
21
|
+
return recognize_path(subject), File.read(subject)
|
22
|
+
else
|
23
|
+
if subject.start_with? "---\n" or subject.start_with?("labels: |\n")
|
24
|
+
return [:yml, subject]
|
25
|
+
elsif subject.start_with?('name,labels,')
|
26
|
+
return [:csv, subject]
|
27
|
+
else
|
28
|
+
raise 'The import string could not be recognized as either YAML or CSV'
|
29
|
+
end
|
70
30
|
end
|
31
|
+
when File
|
32
|
+
return recognize_path(subject.path), subject.read
|
33
|
+
when Pathname
|
34
|
+
return recognize_path(subject.to_s), subject.read
|
35
|
+
when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
|
36
|
+
return recognize_path(subject.original_filename), subject.read
|
37
|
+
else
|
38
|
+
raise 'Format of import not recognized'
|
71
39
|
end
|
72
|
-
persister.call(tree.children, nil)
|
73
|
-
record_lookup_dict
|
74
|
-
end
|
75
|
-
|
76
|
-
class LabelList < Array
|
77
|
-
|
78
|
-
attr_accessor :parent
|
79
40
|
end
|
80
41
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
def initialize(name)
|
86
|
-
@name = name
|
87
|
-
end
|
42
|
+
def self.recognize_path(path)
|
43
|
+
return :yml if path.end_with?('.yml')
|
44
|
+
return :csv if path.end_with?('.csv')
|
88
45
|
|
89
|
-
|
90
|
-
name == 'root'
|
91
|
-
end
|
92
|
-
|
93
|
-
def children
|
94
|
-
@children ||= LabelList.new
|
95
|
-
end
|
96
|
-
|
97
|
-
def add_sibling(label)
|
98
|
-
label.parent = parent
|
99
|
-
parent.children.push label
|
100
|
-
label
|
101
|
-
end
|
102
|
-
|
103
|
-
def add_child(label)
|
104
|
-
children.push label
|
105
|
-
label.parent = self
|
106
|
-
label
|
107
|
-
end
|
108
|
-
|
109
|
-
def inspect
|
110
|
-
# "#{name} => #{children.inspect}"
|
111
|
-
# children.any? ? "<#{name}> => #{children.inpsect}" : "#{name}"
|
112
|
-
base = name.to_s
|
113
|
-
base = "#{base} => #{children.inspect}" if children.any?
|
114
|
-
base
|
115
|
-
end
|
116
|
-
|
117
|
-
def to_h
|
118
|
-
if is_root?
|
119
|
-
{labels: children.map(&:to_h)}
|
120
|
-
else
|
121
|
-
r = {name: name}
|
122
|
-
r[:children] = children.map(&:to_h) if children.any?
|
123
|
-
r
|
124
|
-
end
|
125
|
-
end
|
46
|
+
raise 'Imported path is neither YAML or CSV file'
|
126
47
|
end
|
127
48
|
end
|
128
49
|
end
|
data/lib/geo_labels/version.rb
CHANGED
data/lib/geo_labels.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: geo_labels
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benjamin ter Kuile
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-12-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: awesome_nested_set
|
@@ -128,14 +128,14 @@ dependencies:
|
|
128
128
|
requirements:
|
129
129
|
- - ">="
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
131
|
+
version: '7.1'
|
132
132
|
type: :runtime
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
136
|
- - ">="
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
138
|
+
version: '7.1'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: ransack
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -228,12 +228,12 @@ files:
|
|
228
228
|
- app/views/geo_labels/application/_button-edit-record.html.slim
|
229
229
|
- app/views/geo_labels/application/_button-new-record.html.slim
|
230
230
|
- app/views/geo_labels/application/_messages.html.slim
|
231
|
-
- app/views/geo_labels/application/_navigation.html.slim
|
232
231
|
- app/views/geo_labels/contacts/_form.html.slim
|
233
232
|
- app/views/geo_labels/contacts/edit.html.slim
|
234
233
|
- app/views/geo_labels/contacts/index.html.slim
|
235
234
|
- app/views/geo_labels/contacts/new.html.slim
|
236
235
|
- app/views/geo_labels/contacts/show.html.slim
|
236
|
+
- app/views/geo_labels/dashboard/about.html.slim
|
237
237
|
- app/views/geo_labels/dashboard/export.html.slim
|
238
238
|
- app/views/geo_labels/dashboard/import.html.slim
|
239
239
|
- app/views/geo_labels/dashboard/main.html.slim
|
@@ -242,6 +242,9 @@ files:
|
|
242
242
|
- app/views/geo_labels/labels/index.html.slim
|
243
243
|
- app/views/geo_labels/labels/new.html.slim
|
244
244
|
- app/views/geo_labels/labels/show.html.slim
|
245
|
+
- app/views/geo_labels/menu/_navigation.html.slim
|
246
|
+
- app/views/geo_labels/menu/_right_menu_general.html.slim
|
247
|
+
- app/views/geo_labels/users/_abilities.html.slim
|
245
248
|
- app/views/layouts/geo_labels/application.html.slim
|
246
249
|
- config/environment.rb
|
247
250
|
- config/initializers/human_plural.rb
|
@@ -254,10 +257,13 @@ files:
|
|
254
257
|
- db/migrate/20230602182522_geo_labels_contacts_complex_name_to_name.rb
|
255
258
|
- db/migrate/20230726154822_add_state_to_geo_labels_contacts.rb
|
256
259
|
- db/migrate/20230801145902_add_food_rating_to_geo_labels_contacts.rb
|
260
|
+
- db/migrate/20231206183544_add_url_to_geo_labels_contacts.rb
|
257
261
|
- lib/geo_labels.rb
|
258
262
|
- lib/geo_labels/engine.rb
|
259
263
|
- lib/geo_labels/exporter.rb
|
260
264
|
- lib/geo_labels/importer.rb
|
265
|
+
- lib/geo_labels/importer/csv_data.rb
|
266
|
+
- lib/geo_labels/importer/yaml_data.rb
|
261
267
|
- lib/geo_labels/version.rb
|
262
268
|
- lib/tasks/geo_labels_tasks.rake
|
263
269
|
homepage: https://gitlab.com/benja-2/geo_labels
|
@@ -282,7 +288,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
282
288
|
- !ruby/object:Gem::Version
|
283
289
|
version: '0'
|
284
290
|
requirements: []
|
285
|
-
rubygems_version: 3.
|
291
|
+
rubygems_version: 3.3.26
|
286
292
|
signing_key:
|
287
293
|
specification_version: 4
|
288
294
|
summary: Rails mountable engine to label search locations
|
@@ -1,29 +0,0 @@
|
|
1
|
-
.ui.inverted.menu.fixed
|
2
|
-
= link_to main_app.root_path, class: 'header item'
|
3
|
-
= Rails.application.config.x.geo_labels.link_home_content.call.to_s.html_safe
|
4
|
-
= link_to geo_labels.root_path, class: 'header item'
|
5
|
-
.ui.icon
|
6
|
-
i.map.icon
|
7
|
-
= Rails.application.config.x.geo_labels.application_title.call.to_s.html_safe
|
8
|
-
- if current_user.present?
|
9
|
-
- if can? :read, GeoLabels::Contact
|
10
|
-
= link_to GeoLabels::Contact.model_name.human_plural, [GeoLabels::Contact], class: ['item', active_class('geo_labels/contacts')]
|
11
|
-
- if can? :read, GeoLabels::Label
|
12
|
-
= link_to GeoLabels::Label.model_name.human_plural, [GeoLabels::Label], class: ['item', active_class('geo_labels/labels')]
|
13
|
-
.right.menu
|
14
|
-
- if current_user.present?
|
15
|
-
.ui.dropdown.item
|
16
|
-
span= current_user.respond_to?(:nickname) ? current_user.nickname : current_user.email
|
17
|
-
i.dropdown.icon
|
18
|
-
.menu
|
19
|
-
= link_to geo_labels.export_path, class: 'item'
|
20
|
-
i.file.export.icon
|
21
|
-
span.text= t('geo_labels.export')
|
22
|
-
= link_to geo_labels.import_path, class: 'item'
|
23
|
-
i.file.import.icon
|
24
|
-
span.text= t('geo_labels.import')
|
25
|
-
= link_to main_app.destroy_user_session_path, method: :delete, class: 'item'
|
26
|
-
i.sign.out.icon
|
27
|
-
span.text= t 'user.sign_out'
|
28
|
-
- else
|
29
|
-
= link_to t('devise.sessions.new.sign_in'), main_app.new_user_session_path, class: 'item'
|