decidim-comparative_stats 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE-AGPLv3.txt +661 -0
- data/README.md +187 -0
- data/Rakefile +40 -0
- data/app/assets/config/admin/comparative_stats_manifest.css +4 -0
- data/app/assets/config/admin/comparative_stats_manifest.js +2 -0
- data/app/assets/config/comparative_stats_manifest.css +4 -0
- data/app/assets/config/comparative_stats_manifest.js +2 -0
- data/app/assets/images/bcn-logo.png +0 -0
- data/app/assets/images/decidim/comparative_stats/icon.svg +1 -0
- data/app/assets/images/platoniq-logo.png +0 -0
- data/app/assets/javascripts/decidim/comparative_stats/geocoded_events.js.es6 +94 -0
- data/app/assets/javascripts/decidim/comparative_stats/graphs.js.es6 +13 -0
- data/app/assets/stylesheets/decidim/comparative_stats/geocoded_events.scss +23 -0
- data/app/assets/stylesheets/decidim/comparative_stats/graphs.scss +6 -0
- data/app/assets/stylesheets/decidim/comparative_stats/widget.scss +8 -0
- data/app/cells/decidim/comparative_stats/metric_piecharts/show.erb +14 -0
- data/app/cells/decidim/comparative_stats/metric_piecharts_cell.rb +39 -0
- data/app/cells/decidim/comparative_stats/metric_timelines/show.erb +14 -0
- data/app/cells/decidim/comparative_stats/metric_timelines_cell.rb +23 -0
- data/app/cells/decidim/comparative_stats/participatory_processes_timeline/show.erb +55 -0
- data/app/cells/decidim/comparative_stats/participatory_processes_timeline_cell.rb +51 -0
- data/app/cells/decidim/comparative_stats/participatory_spaces_geocoded_events/show.erb +55 -0
- data/app/cells/decidim/comparative_stats/participatory_spaces_geocoded_events_cell.rb +108 -0
- data/app/commands/decidim/comparative_stats/admin/create_endpoint.rb +46 -0
- data/app/commands/decidim/comparative_stats/admin/destroy_endpoint.rb +43 -0
- data/app/commands/decidim/comparative_stats/admin/update_endpoint.rb +47 -0
- data/app/controllers/decidim/comparative_stats/admin/application_controller.rb +16 -0
- data/app/controllers/decidim/comparative_stats/admin/endpoints_controller.rb +83 -0
- data/app/controllers/decidim/comparative_stats/admin/graphs_controller.rb +16 -0
- data/app/controllers/decidim/comparative_stats/widgets_controller.rb +31 -0
- data/app/forms/decidim/comparative_stats/admin/endpoint_form.rb +26 -0
- data/app/helpers/decidim/comparative_stats/application_helper.rb +27 -0
- data/app/models/decidim/comparative_stats/application_record.rb +10 -0
- data/app/models/decidim/comparative_stats/endpoint.rb +22 -0
- data/app/permissions/decidim/comparative_stats/admin/permissions.rb +24 -0
- data/app/presenters/decidim/comparative_stats/admin_log/endpoint_presenter.rb +46 -0
- data/app/views/decidim/comparative_stats/admin/endpoints/_form.html.erb +7 -0
- data/app/views/decidim/comparative_stats/admin/endpoints/edit.html.erb +16 -0
- data/app/views/decidim/comparative_stats/admin/endpoints/index.html.erb +66 -0
- data/app/views/decidim/comparative_stats/admin/endpoints/new.html.erb +16 -0
- data/app/views/decidim/comparative_stats/admin/graphs/show.html.erb +15 -0
- data/app/views/decidim/comparative_stats/widgets/_all.html.erb +1 -0
- data/app/views/decidim/comparative_stats/widgets/_embed.html.erb +7 -0
- data/app/views/decidim/comparative_stats/widgets/_embed_modal.html.erb +27 -0
- data/app/views/decidim/comparative_stats/widgets/_global_stats.html.erb +4 -0
- data/app/views/decidim/comparative_stats/widgets/_global_stats_timeline.html.erb +4 -0
- data/app/views/decidim/comparative_stats/widgets/_processes_timeline.html.erb +4 -0
- data/app/views/decidim/comparative_stats/widgets/_spaces_geocoded_events.html.erb +5 -0
- data/app/views/decidim/comparative_stats/widgets/_tabs.html.erb +19 -0
- data/app/views/decidim/comparative_stats/widgets/show.html.erb +1 -0
- data/app/views/layouts/decidim/admin/comparative_stats.html.erb +28 -0
- data/app/views/layouts/decidim/comparative_stats/widget.html.erb +22 -0
- data/config/i18n-tasks.yml +10 -0
- data/config/locales/ca.yml +57 -0
- data/config/locales/cs.yml +57 -0
- data/config/locales/en.yml +64 -0
- data/config/locales/es.yml +57 -0
- data/db/migrate/20191219104548_create_decidim_comparative_stats_endpoints.rb +13 -0
- data/db/migrate/20200122072955_add_name_version_to_comparative_stats_endpoints.rb +8 -0
- data/db/migrate/20200130203914_rename_version_field_in_comparative_stats_endpoints.rb +7 -0
- data/db/seeds.rb +42 -0
- data/lib/decidim/comparative_stats.rb +22 -0
- data/lib/decidim/comparative_stats/admin.rb +10 -0
- data/lib/decidim/comparative_stats/admin_engine.rb +50 -0
- data/lib/decidim/comparative_stats/api_fetcher.rb +93 -0
- data/lib/decidim/comparative_stats/cached_http_adapter.rb +23 -0
- data/lib/decidim/comparative_stats/engine.rb +34 -0
- data/lib/decidim/comparative_stats/queries/global_events.graphql +84 -0
- data/lib/decidim/comparative_stats/queries/global_history_metrics.graphql +10 -0
- data/lib/decidim/comparative_stats/queries/global_metrics.graphql +6 -0
- data/lib/decidim/comparative_stats/queries/name_and_version.graphql +6 -0
- data/lib/decidim/comparative_stats/queries/participatory_processes.graphql +12 -0
- data/lib/decidim/comparative_stats/queries/v022/global_events.graphql +76 -0
- data/lib/decidim/comparative_stats/test/factories.rb +13 -0
- data/lib/decidim/comparative_stats/version.rb +10 -0
- data/vendor/assets/images/draw/layers-2x.png +0 -0
- data/vendor/assets/images/draw/layers.png +0 -0
- data/vendor/assets/images/draw/marker-icon-2x.png +0 -0
- data/vendor/assets/images/draw/marker-icon.png +0 -0
- data/vendor/assets/images/draw/marker-shadow.png +0 -0
- data/vendor/assets/images/draw/spritesheet-2x.png +0 -0
- data/vendor/assets/images/draw/spritesheet.png +0 -0
- data/vendor/assets/images/draw/spritesheet.svg +156 -0
- data/vendor/assets/images/images/layers-2x.png +0 -0
- data/vendor/assets/images/images/layers.png +0 -0
- data/vendor/assets/images/images/marker-icon-2x.png +0 -0
- data/vendor/assets/images/images/marker-icon.png +0 -0
- data/vendor/assets/images/images/marker-shadow.png +0 -0
- data/vendor/assets/javascripts/leaflet.js +5 -0
- metadata +234 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module ComparativeStats
|
5
|
+
module Admin
|
6
|
+
# This controller is the abstract class from which all other controllers of
|
7
|
+
# this engine inherit.
|
8
|
+
#
|
9
|
+
class ApplicationController < Decidim::Admin::ApplicationController
|
10
|
+
def permission_class_chain
|
11
|
+
[::Decidim::ComparativeStats::Admin::Permissions] + super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module ComparativeStats
|
5
|
+
module Admin
|
6
|
+
class EndpointsController < ComparativeStats::Admin::ApplicationController
|
7
|
+
helper_method :organization_endpoints
|
8
|
+
layout "decidim/admin/comparative_stats"
|
9
|
+
|
10
|
+
def index
|
11
|
+
enforce_permission_to :index, :endpoint
|
12
|
+
end
|
13
|
+
|
14
|
+
def new
|
15
|
+
enforce_permission_to :create, :endpoint
|
16
|
+
@form = form(EndpointForm).instance
|
17
|
+
end
|
18
|
+
|
19
|
+
def edit
|
20
|
+
enforce_permission_to :update, :endpoint, endpoint: current_endpoint
|
21
|
+
@form = form(EndpointForm).from_model(current_endpoint, endpoint: current_endpoint)
|
22
|
+
end
|
23
|
+
|
24
|
+
def create
|
25
|
+
enforce_permission_to :create, :endpoint
|
26
|
+
@form = form(EndpointForm).from_params(params, api: api)
|
27
|
+
CreateEndpoint.call(@form) do
|
28
|
+
on(:ok) do
|
29
|
+
flash[:notice] = I18n.t("endpoints.create.success", scope: "decidim.comparative_stats.admin")
|
30
|
+
redirect_to decidim_admin_comparative_stats.endpoints_path
|
31
|
+
end
|
32
|
+
|
33
|
+
on(:invalid) do
|
34
|
+
flash.now[:alert] = I18n.t("endpoints.create.error", scope: "decidim.comparative_stats.admin")
|
35
|
+
render :new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def update
|
41
|
+
enforce_permission_to :update, :endpoint, endpoint: current_endpoint
|
42
|
+
|
43
|
+
form = form(EndpointForm).from_params(params, endpoint: current_endpoint, api: api)
|
44
|
+
|
45
|
+
UpdateEndpoint.call(current_endpoint, form, current_user) do
|
46
|
+
on(:ok) do
|
47
|
+
flash[:notice] = I18n.t("endpoints.update.success", scope: "decidim.comparative_stats.admin")
|
48
|
+
redirect_to decidim_admin_comparative_stats.endpoints_path
|
49
|
+
end
|
50
|
+
|
51
|
+
on(:invalid) do
|
52
|
+
flash.now[:alert] = I18n.t("endpoints.update.error", scope: "decidim.comparative_stats.admin")
|
53
|
+
render :edit
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def destroy
|
59
|
+
enforce_permission_to :destroy, :endpoint, endpoint: current_endpoint
|
60
|
+
|
61
|
+
DestroyEndpoint.call(current_endpoint, current_user) do
|
62
|
+
on(:ok) do
|
63
|
+
flash[:notice] = I18n.t("endpoints.destroy.success", scope: "decidim.comparative_stats.admin")
|
64
|
+
redirect_to decidim_admin_comparative_stats.endpoints_path
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def organization_endpoints
|
70
|
+
@organization_endpoints ||= Endpoint.where(organization: current_organization)
|
71
|
+
end
|
72
|
+
|
73
|
+
def api
|
74
|
+
@api ||= ApiFetcher.new(params[:endpoint][:endpoint])
|
75
|
+
end
|
76
|
+
|
77
|
+
def current_endpoint
|
78
|
+
@current_endpoint ||= Endpoint.find(params[:id])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module ComparativeStats
|
5
|
+
module Admin
|
6
|
+
class GraphsController < ComparativeStats::Admin::ApplicationController
|
7
|
+
helper ComparativeStats::ApplicationHelper
|
8
|
+
layout "decidim/admin/comparative_stats"
|
9
|
+
|
10
|
+
def show
|
11
|
+
enforce_permission_to :show, :graph
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module ComparativeStats
|
5
|
+
class WidgetsController < Decidim::ApplicationController
|
6
|
+
helper ComparativeStats::ApplicationHelper
|
7
|
+
skip_before_action :verify_authenticity_token
|
8
|
+
after_action :allow_iframe, only: :show
|
9
|
+
|
10
|
+
helper_method :graphs_path
|
11
|
+
|
12
|
+
layout "decidim/comparative_stats/widget"
|
13
|
+
|
14
|
+
def show
|
15
|
+
is_valid = lookup_context.exists?(params[:graph], "decidim/comparative_stats/widgets", true)
|
16
|
+
render plain: "not found", status: :not_found unless is_valid
|
17
|
+
end
|
18
|
+
|
19
|
+
def allow_iframe
|
20
|
+
response.headers.delete "X-Frame-Options"
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Mock admin path for tabs.html.erb
|
26
|
+
def graphs_path(graph)
|
27
|
+
decidim_comparative_stats.widget_url graph
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module ComparativeStats
|
5
|
+
module Admin
|
6
|
+
# A form object used to configure the endpoint.
|
7
|
+
#
|
8
|
+
class EndpointForm < Decidim::Form
|
9
|
+
mimic :endpoint
|
10
|
+
|
11
|
+
attribute :endpoint, String, presence: true
|
12
|
+
attribute :active, Boolean
|
13
|
+
attribute :name, String
|
14
|
+
|
15
|
+
validate :valid_api_version
|
16
|
+
|
17
|
+
def valid_api_version
|
18
|
+
unless context.api.valid?
|
19
|
+
errors.add :endpoint, :invalid
|
20
|
+
errors.add :endpoint, context.api.error if context.api.error
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module ComparativeStats
|
5
|
+
# Custom helpers, scoped to the comparative_stats engine.
|
6
|
+
#
|
7
|
+
module ApplicationHelper
|
8
|
+
def embed_modal_for(url)
|
9
|
+
embed_code = String.new(content_tag(:iframe, "", src: url, frameborder: 0, width: "100%", height: "420", scrolling: "vertical"))
|
10
|
+
render partial: "decidim/comparative_stats/widgets/embed_modal", locals: { url: url, embed_code: embed_code }
|
11
|
+
end
|
12
|
+
|
13
|
+
def active_endpoints
|
14
|
+
Endpoint.active.where(organization: current_organization)
|
15
|
+
end
|
16
|
+
|
17
|
+
def available_graphs
|
18
|
+
[
|
19
|
+
:global_stats,
|
20
|
+
:global_stats_timeline,
|
21
|
+
:processes_timeline,
|
22
|
+
:spaces_geocoded_events
|
23
|
+
]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim::ComparativeStats
|
4
|
+
class Endpoint < ApplicationRecord
|
5
|
+
include Decidim::Traceable
|
6
|
+
include Decidim::Loggable
|
7
|
+
|
8
|
+
self.table_name = "decidim_comparative_stats_endpoints"
|
9
|
+
|
10
|
+
belongs_to :organization, foreign_key: :decidim_organization_id, class_name: "Decidim::Organization"
|
11
|
+
|
12
|
+
scope :active, -> { where(active: true) }
|
13
|
+
|
14
|
+
def api
|
15
|
+
@api ||= ApiFetcher.new endpoint
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.log_presenter_class_for(_log)
|
19
|
+
Decidim::ComparativeStats::AdminLog::EndpointPresenter
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module ComparativeStats
|
5
|
+
module Admin
|
6
|
+
class Permissions < Decidim::DefaultPermissions
|
7
|
+
def permissions
|
8
|
+
return permission_action unless user
|
9
|
+
|
10
|
+
return permission_action if permission_action.scope != :admin
|
11
|
+
|
12
|
+
return permission_action unless permission_action.subject.in? [:endpoint, :graph, :aggregated_maps]
|
13
|
+
|
14
|
+
case permission_action.action
|
15
|
+
when :index, :show, :create, :update, :destroy
|
16
|
+
permission_action.allow!
|
17
|
+
end
|
18
|
+
|
19
|
+
permission_action
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module ComparativeStats
|
5
|
+
module AdminLog
|
6
|
+
# This class holds the logic to present a `Decidim::ComparativeStats::Endpoint`
|
7
|
+
# for the `AdminLog` log.
|
8
|
+
#
|
9
|
+
# Usage should be automatic and you shouldn't need to call this class
|
10
|
+
# directly, but here's an example:
|
11
|
+
#
|
12
|
+
# action_log = Decidim::ActionLog.last
|
13
|
+
# view_helpers # => this comes from the views
|
14
|
+
# EndpointPresenter.new(action_log, view_helpers).present
|
15
|
+
class EndpointPresenter < Decidim::Log::BasePresenter
|
16
|
+
private
|
17
|
+
|
18
|
+
def diff_fields_mapping
|
19
|
+
{
|
20
|
+
endpoint: :string,
|
21
|
+
name: :string,
|
22
|
+
api_version: :string,
|
23
|
+
active: :boolean
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def i18n_labels_scope
|
28
|
+
"activemodel.attributes.comparative_stats.endpoint"
|
29
|
+
end
|
30
|
+
|
31
|
+
def action_string
|
32
|
+
case action
|
33
|
+
when "create", "delete", "update"
|
34
|
+
"decidim.comparative_stats.admin_log.endpoint.#{action}"
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def has_diff?
|
41
|
+
action == "delete" || super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<%= decidim_form_for(@form) do |f| %>
|
2
|
+
<div class="card">
|
3
|
+
<div class="card-divider">
|
4
|
+
<h2 class="card-title"><%= t ".title" %></h2>
|
5
|
+
</div>
|
6
|
+
<div class="card-section">
|
7
|
+
<%= render partial: "form", object: f %>
|
8
|
+
<div class="row column">
|
9
|
+
<%= f.text_field :name %>
|
10
|
+
</div>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
<div class="button--double form-general-submit">
|
14
|
+
<%= f.submit t(".update") %>
|
15
|
+
</div>
|
16
|
+
<% end %>
|
@@ -0,0 +1,66 @@
|
|
1
|
+
<div class="card" id="translation_sets">
|
2
|
+
<div class="card-divider">
|
3
|
+
<h2 class="card-title flex--sbc">
|
4
|
+
<div>
|
5
|
+
<%= t ".title" %>
|
6
|
+
</div>
|
7
|
+
|
8
|
+
<div class="flex--cc flex-gap--1">
|
9
|
+
<div id="js-other-actions-wrapper">
|
10
|
+
<% if allowed_to? :create, :endpoint %>
|
11
|
+
<%= link_to t("endpoints.add", scope: "decidim.comparative_stats"), url_for(action: :new), class: "button tiny button--title new" %>
|
12
|
+
<% end %>
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
</h2>
|
16
|
+
</div>
|
17
|
+
|
18
|
+
<div class="card-section">
|
19
|
+
<div class="table-scroll">
|
20
|
+
<% if organization_endpoints.any? %>
|
21
|
+
<table class="table-list">
|
22
|
+
<thead>
|
23
|
+
<tr>
|
24
|
+
<th><%= t ".active" %></th>
|
25
|
+
<th><%= t ".endpoint" %></th>
|
26
|
+
<th><%= t ".name" %></th>
|
27
|
+
<th><%= t ".version" %></th>
|
28
|
+
<th></th>
|
29
|
+
</tr>
|
30
|
+
</thead>
|
31
|
+
<tbody>
|
32
|
+
<% organization_endpoints.each do |endpoint| %>
|
33
|
+
<tr data-id="<%= endpoint.id %>">
|
34
|
+
<td>
|
35
|
+
<% if endpoint.active? %>
|
36
|
+
<span class="icon-active"></span>
|
37
|
+
<% end %>
|
38
|
+
</td>
|
39
|
+
<td>
|
40
|
+
<%= endpoint.endpoint %><br>
|
41
|
+
</td>
|
42
|
+
<td>
|
43
|
+
<%= endpoint.name %><br>
|
44
|
+
</td>
|
45
|
+
<td>
|
46
|
+
<%= endpoint.api_version %><br>
|
47
|
+
</td>
|
48
|
+
<td class="table-list__actions">
|
49
|
+
<% if allowed_to? :update, :endpoint, endpoint: endpoint %>
|
50
|
+
<%= icon_link_to "pencil", edit_endpoint_path(endpoint), t("actions.edit", scope: "decidim.admin"), class: "action-icon--edit" %>
|
51
|
+
<% end %>
|
52
|
+
|
53
|
+
<% if allowed_to? :destroy, :endpoint, endpoint: endpoint %>
|
54
|
+
<%= icon_link_to "circle-x", endpoint_path(endpoint), t("actions.destroy", scope: "decidim.admin"), class: "action-icon--remove", method: :delete, data: { confirm: t("actions.confirm_destroy", scope: "decidim.admin") } %>
|
55
|
+
<% end %>
|
56
|
+
</td>
|
57
|
+
</tr>
|
58
|
+
<% end %>
|
59
|
+
</tbody>
|
60
|
+
</table>
|
61
|
+
<% else %>
|
62
|
+
<%= t ".no_endpoints" %>
|
63
|
+
<% end %>
|
64
|
+
</div>
|
65
|
+
</div>
|
66
|
+
</div>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<%= decidim_form_for(@form) do |f| %>
|
2
|
+
<div class="card">
|
3
|
+
<div class="card-divider">
|
4
|
+
<h2 class="card-title"><%= t ".title" %></h2>
|
5
|
+
</div>
|
6
|
+
<div class="card-section">
|
7
|
+
<p>
|
8
|
+
<%= t(".subtitle") %>
|
9
|
+
</p>
|
10
|
+
<%= render partial: "form", object: f %>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
<div class="button--double form-general-submit">
|
14
|
+
<%= f.submit t(".create") %>
|
15
|
+
</div>
|
16
|
+
<% end %>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<div class="card" id="translation_sets">
|
2
|
+
<div class="card-divider">
|
3
|
+
<h2 class="card-title flex--sbc">
|
4
|
+
<div>
|
5
|
+
<%= t ".title" %>
|
6
|
+
</div>
|
7
|
+
</h2>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<% if active_endpoints.any? %>
|
12
|
+
<%= render partial: "decidim/comparative_stats/widgets/tabs" %>
|
13
|
+
<% else %>
|
14
|
+
<%= t ".no_endpoints" %>
|
15
|
+
<% end %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= render partial: "tabs" %>
|