decidim-comparative_stats 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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