decidim-comparative_stats 1.0.1

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.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +187 -0
  4. data/Rakefile +40 -0
  5. data/app/assets/config/admin/comparative_stats_manifest.css +4 -0
  6. data/app/assets/config/admin/comparative_stats_manifest.js +2 -0
  7. data/app/assets/config/comparative_stats_manifest.css +4 -0
  8. data/app/assets/config/comparative_stats_manifest.js +2 -0
  9. data/app/assets/images/bcn-logo.png +0 -0
  10. data/app/assets/images/decidim/comparative_stats/icon.svg +1 -0
  11. data/app/assets/images/platoniq-logo.png +0 -0
  12. data/app/assets/javascripts/decidim/comparative_stats/geocoded_events.js.es6 +94 -0
  13. data/app/assets/javascripts/decidim/comparative_stats/graphs.js.es6 +13 -0
  14. data/app/assets/stylesheets/decidim/comparative_stats/geocoded_events.scss +23 -0
  15. data/app/assets/stylesheets/decidim/comparative_stats/graphs.scss +6 -0
  16. data/app/assets/stylesheets/decidim/comparative_stats/widget.scss +8 -0
  17. data/app/cells/decidim/comparative_stats/metric_piecharts/show.erb +14 -0
  18. data/app/cells/decidim/comparative_stats/metric_piecharts_cell.rb +39 -0
  19. data/app/cells/decidim/comparative_stats/metric_timelines/show.erb +14 -0
  20. data/app/cells/decidim/comparative_stats/metric_timelines_cell.rb +23 -0
  21. data/app/cells/decidim/comparative_stats/participatory_processes_timeline/show.erb +55 -0
  22. data/app/cells/decidim/comparative_stats/participatory_processes_timeline_cell.rb +51 -0
  23. data/app/cells/decidim/comparative_stats/participatory_spaces_geocoded_events/show.erb +55 -0
  24. data/app/cells/decidim/comparative_stats/participatory_spaces_geocoded_events_cell.rb +108 -0
  25. data/app/commands/decidim/comparative_stats/admin/create_endpoint.rb +46 -0
  26. data/app/commands/decidim/comparative_stats/admin/destroy_endpoint.rb +43 -0
  27. data/app/commands/decidim/comparative_stats/admin/update_endpoint.rb +47 -0
  28. data/app/controllers/decidim/comparative_stats/admin/application_controller.rb +16 -0
  29. data/app/controllers/decidim/comparative_stats/admin/endpoints_controller.rb +83 -0
  30. data/app/controllers/decidim/comparative_stats/admin/graphs_controller.rb +16 -0
  31. data/app/controllers/decidim/comparative_stats/widgets_controller.rb +31 -0
  32. data/app/forms/decidim/comparative_stats/admin/endpoint_form.rb +26 -0
  33. data/app/helpers/decidim/comparative_stats/application_helper.rb +27 -0
  34. data/app/models/decidim/comparative_stats/application_record.rb +10 -0
  35. data/app/models/decidim/comparative_stats/endpoint.rb +22 -0
  36. data/app/permissions/decidim/comparative_stats/admin/permissions.rb +24 -0
  37. data/app/presenters/decidim/comparative_stats/admin_log/endpoint_presenter.rb +46 -0
  38. data/app/views/decidim/comparative_stats/admin/endpoints/_form.html.erb +7 -0
  39. data/app/views/decidim/comparative_stats/admin/endpoints/edit.html.erb +16 -0
  40. data/app/views/decidim/comparative_stats/admin/endpoints/index.html.erb +66 -0
  41. data/app/views/decidim/comparative_stats/admin/endpoints/new.html.erb +16 -0
  42. data/app/views/decidim/comparative_stats/admin/graphs/show.html.erb +15 -0
  43. data/app/views/decidim/comparative_stats/widgets/_all.html.erb +1 -0
  44. data/app/views/decidim/comparative_stats/widgets/_embed.html.erb +7 -0
  45. data/app/views/decidim/comparative_stats/widgets/_embed_modal.html.erb +27 -0
  46. data/app/views/decidim/comparative_stats/widgets/_global_stats.html.erb +4 -0
  47. data/app/views/decidim/comparative_stats/widgets/_global_stats_timeline.html.erb +4 -0
  48. data/app/views/decidim/comparative_stats/widgets/_processes_timeline.html.erb +4 -0
  49. data/app/views/decidim/comparative_stats/widgets/_spaces_geocoded_events.html.erb +5 -0
  50. data/app/views/decidim/comparative_stats/widgets/_tabs.html.erb +19 -0
  51. data/app/views/decidim/comparative_stats/widgets/show.html.erb +1 -0
  52. data/app/views/layouts/decidim/admin/comparative_stats.html.erb +28 -0
  53. data/app/views/layouts/decidim/comparative_stats/widget.html.erb +22 -0
  54. data/config/i18n-tasks.yml +10 -0
  55. data/config/locales/ca.yml +57 -0
  56. data/config/locales/cs.yml +57 -0
  57. data/config/locales/en.yml +64 -0
  58. data/config/locales/es.yml +57 -0
  59. data/db/migrate/20191219104548_create_decidim_comparative_stats_endpoints.rb +13 -0
  60. data/db/migrate/20200122072955_add_name_version_to_comparative_stats_endpoints.rb +8 -0
  61. data/db/migrate/20200130203914_rename_version_field_in_comparative_stats_endpoints.rb +7 -0
  62. data/db/seeds.rb +42 -0
  63. data/lib/decidim/comparative_stats.rb +22 -0
  64. data/lib/decidim/comparative_stats/admin.rb +10 -0
  65. data/lib/decidim/comparative_stats/admin_engine.rb +50 -0
  66. data/lib/decidim/comparative_stats/api_fetcher.rb +93 -0
  67. data/lib/decidim/comparative_stats/cached_http_adapter.rb +23 -0
  68. data/lib/decidim/comparative_stats/engine.rb +34 -0
  69. data/lib/decidim/comparative_stats/queries/global_events.graphql +84 -0
  70. data/lib/decidim/comparative_stats/queries/global_history_metrics.graphql +10 -0
  71. data/lib/decidim/comparative_stats/queries/global_metrics.graphql +6 -0
  72. data/lib/decidim/comparative_stats/queries/name_and_version.graphql +6 -0
  73. data/lib/decidim/comparative_stats/queries/participatory_processes.graphql +12 -0
  74. data/lib/decidim/comparative_stats/queries/v022/global_events.graphql +76 -0
  75. data/lib/decidim/comparative_stats/test/factories.rb +13 -0
  76. data/lib/decidim/comparative_stats/version.rb +10 -0
  77. data/vendor/assets/images/draw/layers-2x.png +0 -0
  78. data/vendor/assets/images/draw/layers.png +0 -0
  79. data/vendor/assets/images/draw/marker-icon-2x.png +0 -0
  80. data/vendor/assets/images/draw/marker-icon.png +0 -0
  81. data/vendor/assets/images/draw/marker-shadow.png +0 -0
  82. data/vendor/assets/images/draw/spritesheet-2x.png +0 -0
  83. data/vendor/assets/images/draw/spritesheet.png +0 -0
  84. data/vendor/assets/images/draw/spritesheet.svg +156 -0
  85. data/vendor/assets/images/images/layers-2x.png +0 -0
  86. data/vendor/assets/images/images/layers.png +0 -0
  87. data/vendor/assets/images/images/marker-icon-2x.png +0 -0
  88. data/vendor/assets/images/images/marker-icon.png +0 -0
  89. data/vendor/assets/images/images/marker-shadow.png +0 -0
  90. data/vendor/assets/javascripts/leaflet.js +5 -0
  91. metadata +234 -0
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "decidim/comparative_stats/admin"
4
+ require "decidim/comparative_stats/engine"
5
+ require "decidim/comparative_stats/admin_engine"
6
+ require "chartkick"
7
+
8
+ module Decidim
9
+ # This namespace holds the logic of the `ComparativeStats` component. This component
10
+ # allows users to create comparative_stats in a participatory space.
11
+ module ComparativeStats
12
+ include ActiveSupport::Configurable
13
+
14
+ autoload :ApiFetcher, "decidim/comparative_stats/api_fetcher"
15
+ autoload :CachedHTTPAdapter, "decidim/comparative_stats/cached_http_adapter"
16
+
17
+ # Sets the expiration time for the statistic data.
18
+ config_accessor :stats_cache_expiration_time do
19
+ 24.hours
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module ComparativeStats
5
+ # This module contains all the domain logic associated to Decidim's ComparativeStats
6
+ # component admin panel.
7
+ module Admin
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module ComparativeStats
5
+ # This is the engine that runs on the public interface of `ComparativeStats`.
6
+ class AdminEngine < ::Rails::Engine
7
+ isolate_namespace Decidim::ComparativeStats::Admin
8
+
9
+ paths["db/migrate"] = nil
10
+ paths["lib/tasks"] = nil
11
+
12
+ routes do
13
+ # Add admin engine routes here
14
+ resources :endpoints
15
+ get "graphs/:graph", to: "graphs#show", as: :graphs, defaults: { graph: "global_stats" }
16
+ root to: "endpoints#index"
17
+ end
18
+
19
+ initializer "decidim_comparative_stats.admin_mount_routes" do
20
+ Decidim::Core::Engine.routes do
21
+ mount Decidim::ComparativeStats::AdminEngine, at: "/admin/comparative_stats", as: "decidim_admin_comparative_stats"
22
+ end
23
+ end
24
+
25
+ initializer "decidim_comparative_stats.add_cells_view_paths" do
26
+ Cell::ViewModel.view_paths << File.expand_path("#{Decidim::ComparativeStats::Engine.root}/app/cells")
27
+ Cell::ViewModel.view_paths << File.expand_path("#{Decidim::ComparativeStats::Engine.root}/app/views") # for partials
28
+ end
29
+
30
+ initializer "decidim_comparative_stats.admin_assets" do |app|
31
+ app.config.assets.precompile += %w(admin/comparative_stats_manifest.js admin/comparative_stats_manifest.css)
32
+ end
33
+
34
+ def load_seed
35
+ nil
36
+ end
37
+
38
+ initializer "decidim_comparative_stats.admin_menu" do
39
+ Decidim.menu :admin_menu do |menu|
40
+ menu.item I18n.t("menu.comparative_stats", scope: "decidim.admin"),
41
+ decidim_admin_comparative_stats.endpoints_path,
42
+ icon_name: "graph",
43
+ position: 3.5,
44
+ active: is_active_link?(decidim_admin_comparative_stats.endpoints_path, :inclusive) ||
45
+ is_active_link?(decidim_admin_comparative_stats.graphs_path, :inclusive)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "graphlient"
4
+ require "decidim/comparative_stats/version"
5
+
6
+ module Decidim
7
+ module ComparativeStats
8
+ # Class used to fetch and validate Decidim API calls
9
+ class ApiFetcher
10
+ def initialize(endpoint)
11
+ @errors = []
12
+ @queries = {}
13
+ @endpoint = endpoint
14
+ end
15
+
16
+ attr_reader :errors, :endpoint
17
+
18
+ attr_writer :client
19
+
20
+ def client
21
+ @client ||= Graphlient::Client.new(endpoint,
22
+ http: CachedHTTPAdapter,
23
+ http_options: {
24
+ read_timeout: 20,
25
+ write_timeout: 30
26
+ })
27
+ end
28
+
29
+ # When creating name and version are fetched from the api
30
+ # Update action should allow the user to change the name but not the version
31
+ def name_and_version
32
+ @name_and_version ||= fetch_name_and_version.data.decidim
33
+ end
34
+
35
+ # Queries the GraphQL api using one of files in lib/decidim/comparative_stats/queries
36
+ def query(tag)
37
+ @queries[tag] ||= client.query versioned_query(tag)
38
+ rescue Faraday::Error
39
+ @errors << "Not a valid Decidim API URL"
40
+ rescue Graphlient::Errors::Error => e
41
+ @errors << e.message
42
+ end
43
+
44
+ # Checks if is a valid Decidim API URL
45
+ def valid?(min_version = MIN_API_VERSION)
46
+ response = fetch_name_and_version
47
+ return false unless response
48
+
49
+ if Gem::Version.new(min_version) > Gem::Version.new(response.data.decidim.version)
50
+ @errors << "Decidim version detect (#{response.data.decidim.version}) should be at least #{min_version}"
51
+ return false
52
+ end
53
+ true
54
+ end
55
+
56
+ # returns the last error
57
+ def error
58
+ @errors.last
59
+ end
60
+
61
+ private
62
+
63
+ def versioned_query(tag)
64
+ file = File.join(__dir__, "queries", "#{tag}.graphql")
65
+ raise NameError unless File.exist?(file)
66
+
67
+ unless tag == "name_and_version"
68
+ if Gem::Version.new(name_and_version.version) < Gem::Version.new("0.23")
69
+ alt_file = File.join(__dir__, "queries", "v022", "#{tag}.graphql")
70
+ file = alt_file if File.exist?(alt_file)
71
+ end
72
+ end
73
+ File.open(file).read
74
+ end
75
+
76
+ # Syntactic sugar to query graphql queries:
77
+ # i.e:
78
+ # query "name_and_version" => fetch_name_and_version
79
+ def method_missing(name)
80
+ _, key = name.to_s.split("fetch_")
81
+ if key
82
+ query key
83
+ else
84
+ super
85
+ end
86
+ end
87
+
88
+ def respond_to_missing?(method_name, include_private = false)
89
+ method_name.to_s.start_with?("fetch_") || super
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "graphlient/adapters/http/http_adapter"
4
+
5
+ module Decidim
6
+ module ComparativeStats
7
+ class CachedHTTPAdapter < Graphlient::Adapters::HTTP::FaradayAdapter
8
+ def execute(document:, operation_name: nil, variables: {}, context: {})
9
+ body = {}
10
+ # Strip uniq identifier generated by GraphQL class
11
+ body["query"] = document.to_query_string.gsub(/GraphQL__Client__OperationDefinition_[0-9 ]+/, "")
12
+ body["variables"] = variables if variables.any?
13
+
14
+ Rails.cache.fetch(
15
+ "graphlient/api_queries/#{url.hash}/#{body.to_json.hash}",
16
+ expires_in: Decidim::ComparativeStats.stats_cache_expiration_time
17
+ ) do
18
+ super
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails"
4
+ require "decidim/core"
5
+
6
+ module Decidim
7
+ module ComparativeStats
8
+ # This is the engine that runs on the public interface of comparative_stats.
9
+ class Engine < ::Rails::Engine
10
+ isolate_namespace Decidim::ComparativeStats
11
+
12
+ routes do
13
+ # Add engine routes here
14
+ get "widgets/:graph", to: "widgets#show", as: :widget, defaults: { graph: "global_stats" }
15
+ root to: "widgets#show"
16
+ end
17
+
18
+ initializer "decidim_comparative_stats.mount_routes" do
19
+ Decidim::Core::Engine.routes do
20
+ mount Decidim::ComparativeStats::Engine, at: "/comparative_stats", as: "decidim_comparative_stats"
21
+ end
22
+ end
23
+
24
+ initializer "decidim_comparative_stats.add_cells_view_paths" do
25
+ Cell::ViewModel.view_paths << File.expand_path("#{Decidim::ComparativeStats::Engine.root}/app/cells")
26
+ Cell::ViewModel.view_paths << File.expand_path("#{Decidim::ComparativeStats::Engine.root}/app/views") # for partials
27
+ end
28
+
29
+ initializer "decidim_comparative_stats.assets" do |app|
30
+ app.config.assets.precompile += %w(comparative_stats_manifest.js comparative_stats_manifest.css)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,84 @@
1
+ query {
2
+ assemblies {
3
+ id
4
+ slug
5
+ components (filter: {withGeolocationEnabled: true }) {
6
+ id
7
+ __typename
8
+ ...geolocatedMeetings
9
+ ...geolocatedProposals
10
+ }
11
+ }
12
+ participatoryProcesses {
13
+ id
14
+ slug
15
+ components (filter: {withGeolocationEnabled: true }) {
16
+ id
17
+ __typename
18
+ ...geolocatedMeetings
19
+ ...geolocatedProposals
20
+ }
21
+ }
22
+ }
23
+
24
+ fragment geolocatedMeetings on Meetings {
25
+ meetings {
26
+ edges {
27
+ node {
28
+ id
29
+ address
30
+ title {
31
+ translations {
32
+ text
33
+ }
34
+ }
35
+ description {
36
+ translations {
37
+ text
38
+ }
39
+ }
40
+ startTime
41
+ endTime
42
+ location {
43
+ translations {
44
+ text
45
+ }
46
+ }
47
+ locationHints {
48
+ translations {
49
+ text
50
+ }
51
+ }
52
+ coordinates {
53
+ latitude
54
+ longitude
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ fragment geolocatedProposals on Proposals {
62
+ proposals {
63
+ edges {
64
+ node {
65
+ id
66
+ address
67
+ title {
68
+ translations {
69
+ text
70
+ }
71
+ }
72
+ body {
73
+ translations {
74
+ text
75
+ }
76
+ }
77
+ coordinates {
78
+ latitude
79
+ longitude
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,10 @@
1
+ query {
2
+ metrics {
3
+ count
4
+ name
5
+ history {
6
+ key
7
+ value
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,6 @@
1
+ query {
2
+ metrics {
3
+ count
4
+ name
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ query {
2
+ decidim {
3
+ applicationName
4
+ version
5
+ }
6
+ }
@@ -0,0 +1,12 @@
1
+ query {
2
+ participatoryProcesses {
3
+ id
4
+ title {
5
+ translations {
6
+ text
7
+ }
8
+ }
9
+ startDate
10
+ endDate
11
+ }
12
+ }
@@ -0,0 +1,76 @@
1
+ query {
2
+ assemblies {
3
+ id
4
+ slug
5
+ components (filter: {withGeolocationEnabled: true }) {
6
+ id
7
+ __typename
8
+ ...geolocatedMeetings
9
+ ...geolocatedProposals
10
+ }
11
+ }
12
+ participatoryProcesses {
13
+ id
14
+ slug
15
+ components (filter: {withGeolocationEnabled: true }) {
16
+ id
17
+ __typename
18
+ ...geolocatedMeetings
19
+ ...geolocatedProposals
20
+ }
21
+ }
22
+ }
23
+
24
+ fragment geolocatedMeetings on Meetings {
25
+ meetings {
26
+ edges {
27
+ node {
28
+ id
29
+ address
30
+ title {
31
+ translations {
32
+ text
33
+ }
34
+ }
35
+ description {
36
+ translations {
37
+ text
38
+ }
39
+ }
40
+ startTime
41
+ endTime
42
+ location {
43
+ translations {
44
+ text
45
+ }
46
+ }
47
+ locationHints {
48
+ translations {
49
+ text
50
+ }
51
+ }
52
+ coordinates {
53
+ latitude
54
+ longitude
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ fragment geolocatedProposals on Proposals {
62
+ proposals {
63
+ edges {
64
+ node {
65
+ id
66
+ address
67
+ title
68
+ body
69
+ coordinates {
70
+ latitude
71
+ longitude
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "decidim/core/test/factories"
4
+
5
+ FactoryBot.define do
6
+ factory :endpoint, class: "Decidim::ComparativeStats::Endpoint" do
7
+ endpoint { Faker::Internet.url }
8
+ name { Faker::Name.name }
9
+ api_version { Decidim::ComparativeStats::MIN_API_VERSION }
10
+ organization { create :organization }
11
+ active { true }
12
+ end
13
+ end