diff --git a/Gemfile b/Gemfile index a6c1dc71e..d4a2777a9 100644 --- a/Gemfile +++ b/Gemfile @@ -53,6 +53,9 @@ gem 'turnout', '~> 2.4.0' gem 'uglifier', '~> 3.2.0' gem 'unicorn', '~> 5.3.0' gem 'whenever', '~> 0.9.7', require: false +source 'https://rails-assets.org' do + gem 'rails-assets-leaflet' +end group :development, :test do gem "bullet", '~> 5.5.1' diff --git a/Gemfile.lock b/Gemfile.lock index f15f0c6e9..6435af58e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -334,6 +334,7 @@ GEM bundler (>= 1.3.0, < 2.0) railties (= 4.2.9) sprockets-rails + rails-assets-leaflet (1.1.0) rails-assets-markdown-it (8.2.2) rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) @@ -542,6 +543,7 @@ DEPENDENCIES poltergeist (~> 1.15.0) quiet_assets (~> 1.1.0) rails (= 4.2.9) + rails-assets-leaflet! rails-assets-markdown-it (~> 8.2.1)! redcarpet (~> 3.4.0) responders (~> 2.4.0) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 0b3ee0201..d7c36e944 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -67,6 +67,8 @@ //= require tree_navigator //= require custom //= require tag_autocomplete +//= require leaflet +//= require map var initialize_modules = function() { App.Comments.initialize(); @@ -103,6 +105,7 @@ var initialize_modules = function() { App.Documentable.initialize(); App.Imageable.initialize(); App.TagAutocomplete.initialize(); + App.Map.initialize(); }; $(function(){ diff --git a/app/assets/javascripts/map.js.coffee b/app/assets/javascripts/map.js.coffee new file mode 100644 index 000000000..3f28024a7 --- /dev/null +++ b/app/assets/javascripts/map.js.coffee @@ -0,0 +1,78 @@ +App.Map = + + initialize: -> + maps = $('*[data-map]') + + if maps.length > 0 + $.each maps, (index, map) -> + App.Map.initializeMap map + + initializeMap: (element) -> + + mapCenterLatitude = $(element).data('map-center-latitude') + mapCenterLongitude = $(element).data('map-center-longitude') + markerLatitude = $(element).data('marker-latitude') + markerLongitude = $(element).data('marker-longitude') + zoom = $(element).data('map-zoom') + mapTilesProvider = $(element).data('map-tiles-provider') + mapAttribution = $(element).data('map-tiles-provider-attribution') + latitudeInputSelector = $(element).data('latitude-input-selector') + longitudeInputSelector = $(element).data('longitude-input-selector') + zoomInputSelector = $(element).data('zoom-input-selector') + removeMarkerSelector = $(element).data('marker-remove-selector') + editable = $(element).data('marker-editable') + marker = null; + markerIcon = L.divIcon( + className: 'map-marker' + iconSize: [30, 30] + iconAnchor: [15, 40] + html: '
') + + createMarker = (latitude, longitude) -> + markerLatLng = new (L.LatLng)(latitude, longitude) + marker = L.marker(markerLatLng, { icon: markerIcon, draggable: editable }) + if editable + marker.on 'dragend', updateFormfields + marker.addTo(map) + return marker + + removeMarker = (e) -> + e.preventDefault() + if marker + map.removeLayer(marker) + marker = null; + clearFormfields() + return + + moveOrPlaceMarker = (e) -> + if marker + marker.setLatLng(e.latlng) + else + marker = createMarker(e.latlng.lat, e.latlng.lng) + + updateFormfields() + return + + updateFormfields = -> + $(latitudeInputSelector).val marker.getLatLng().lat + $(longitudeInputSelector).val marker.getLatLng().lng + $(zoomInputSelector).val map.getZoom() + return + + clearFormfields = -> + $(latitudeInputSelector).val '' + $(longitudeInputSelector).val '' + $(zoomInputSelector).val '' + return + + mapCenterLatLng = new (L.LatLng)(mapCenterLatitude, mapCenterLongitude) + map = L.map(element.id).setView(mapCenterLatLng, zoom) + L.tileLayer(mapTilesProvider, attribution: mapAttribution).addTo map + + if markerLatitude && markerLongitude + marker = createMarker(markerLatitude, markerLongitude) + + if editable + $(removeMarkerSelector).on 'click', removeMarker + map.on 'zoomend', updateFormfields + map.on 'click', moveOrPlaceMarker diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index fab661e8d..52c989f55 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -8,6 +8,7 @@ // 06. Polls // 07. Legislation // 08. CMS +// 09. Map // // 01. Global styles @@ -976,3 +977,51 @@ table { border: 0; } } + +// 09. Map +// -------------- + +.map { + width: 100%; + height: 350px; + + .map-marker { + visibility: visible; + position: absolute; + left: 50%; + top: 50%; + margin-top: -5px; + + .map-icon { + width: 30px; + height: 30px; + border-radius: 50% 50% 50% 0; + background: #00cae9; + transform: rotate(-45deg); + } + + .map-icon::after { + content: ''; + width: 14px; + height: 14px; + margin: 8px 0 0 8px; + background: #fff; + position: absolute; + border-radius: 50%; + } + } + + .map-attributtion { + visibility: visible; + height: auto; + } +} + +.map-marker { + visibility: hidden; +} + +.map-attributtion { + visibility: hidden; + height: 0; +} diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 361dcfde3..aff55c88c 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -18,3 +18,4 @@ @import 'datepicker_overrides'; @import 'jquery-ui/autocomplete'; @import 'autocomplete_overrides'; +@import 'leaflet'; diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb index 70541a3a0..14dfc6658 100644 --- a/app/controllers/admin/settings_controller.rb +++ b/app/controllers/admin/settings_controller.rb @@ -14,6 +14,13 @@ class Admin::SettingsController < Admin::BaseController redirect_to admin_settings_path, notice: t("admin.settings.flash.updated") end + def update_map + Setting["map_latitude"] = params[:latitude].to_f + Setting["map_longitude"] = params[:longitude].to_f + Setting["map_zoom"] = params[:zoom].to_i + redirect_to admin_settings_path, notice: t("admin.settings.index.map.flash.update") + end + private def settings_params diff --git a/app/controllers/budgets/investments_controller.rb b/app/controllers/budgets/investments_controller.rb index fb82c51db..bb00f24af 100644 --- a/app/controllers/budgets/investments_controller.rb +++ b/app/controllers/budgets/investments_controller.rb @@ -107,7 +107,8 @@ module Budgets .permit(:title, :description, :external_url, :heading_id, :tag_list, :organization_name, :location, :terms_of_service, image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy], - documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy]) + documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy], + map_location_attributes: [:latitude, :longitude, :zoom]) end def load_ballot diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index 9e6fc521d..d1db0e4a2 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -77,7 +77,8 @@ class ProposalsController < ApplicationController params.require(:proposal).permit(:title, :question, :summary, :description, :external_url, :video_url, :responsible_name, :tag_list, :terms_of_service, :geozone_id, image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy], - documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy] ) + documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy], + map_location_attributes: [:latitude, :longitude, :zoom]) end def retired_params diff --git a/app/helpers/map_locations_helper.rb b/app/helpers/map_locations_helper.rb new file mode 100644 index 000000000..ae45e7c1d --- /dev/null +++ b/app/helpers/map_locations_helper.rb @@ -0,0 +1,67 @@ +module MapLocationsHelper + + def map_location_available?(map_location) + map_location.present? && map_location.available? + end + + def map_location_latitude(map_location) + map_location.present? && map_location.latitude.present? ? map_location.latitude : Setting["map_latitude"] + end + + def map_location_longitude(map_location) + map_location.present? && map_location.longitude.present? ? map_location.longitude : Setting["map_longitude"] + end + + def map_location_zoom(map_location) + map_location.present? && map_location.zoom.present? ? map_location.zoom : Setting["map_zoom"] + end + + def map_location_input_id(prefix, attribute) + "#{prefix}_map_location_attributes_#{attribute}" + end + + def map_location_remove_marker_link_id(map_location) + "remove-marker-link-#{dom_id(map_location)}" + end + + def render_map(map_location, parent_class, editable, remove_marker_label) + map = content_tag_for :div, + map_location, + class: "map", + data: prepare_map_settings(map_location, editable, parent_class) + map += map_location_remove_marker(map_location, remove_marker_label) if editable + map + end + + def map_location_remove_marker(map_location, text) + content_tag :div, class: "text-right" do + content_tag :a, + id: map_location_remove_marker_link_id(map_location), + href: "#", + class: "location-map-remove-marker-button delete" do + text + end + end + end + + private + + def prepare_map_settings(map_location, editable, parent_class) + options = { + map: "", + map_center_latitude: map_location_latitude(map_location), + map_center_longitude: map_location_longitude(map_location), + map_zoom: map_location_zoom(map_location), + map_tiles_provider: Rails.application.secrets.map_tiles_provider, + map_tiles_provider_attribution: Rails.application.secrets.map_tiles_provider_attribution, + marker_editable: editable, + marker_latitude: map_location.latitude, + marker_longitude: map_location.longitude, + marker_remove_selector: "##{map_location_remove_marker_link_id(map_location)}", + latitude_input_selector: "##{map_location_input_id(parent_class, 'latitude')}", + longitude_input_selector: "##{map_location_input_id(parent_class, 'longitude')}", + zoom_input_selector: "##{map_location_input_id(parent_class, 'zoom')}" + } + end + +end diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index 857025375..ddf6e819c 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -8,6 +8,7 @@ class Budget include Followable include Communitable include Imageable + include Mappable include Documentable documentable max_documents_allowed: 3, max_file_size: 3.megabytes, diff --git a/app/models/concerns/mappable.rb b/app/models/concerns/mappable.rb new file mode 100644 index 000000000..c108bdca7 --- /dev/null +++ b/app/models/concerns/mappable.rb @@ -0,0 +1,9 @@ +module Mappable + extend ActiveSupport::Concern + + included do + has_one :map_location, dependent: :destroy + accepts_nested_attributes_for :map_location, allow_destroy: true + end + +end diff --git a/app/models/map_location.rb b/app/models/map_location.rb new file mode 100644 index 000000000..14e91d92c --- /dev/null +++ b/app/models/map_location.rb @@ -0,0 +1,10 @@ +class MapLocation < ActiveRecord::Base + + belongs_to :proposal + belongs_to :investment, class_name: Budget::Investment + + def available? + latitude.present? && longitude.present? && zoom.present? + end + +end diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 7a0abcb6e..da0fc109f 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -11,10 +11,11 @@ class Proposal < ActiveRecord::Base include Followable include Communitable include Imageable + include Mappable include Documentable documentable max_documents_allowed: 3, max_file_size: 3.megabytes, - accepted_content_types: [ "application/pdf" ] + accepted_content_types: [ "application/pdf" ] include EmbedVideosHelper acts_as_votable diff --git a/app/views/admin/settings/_map_form.html.erb b/app/views/admin/settings/_map_form.html.erb new file mode 100644 index 000000000..9c411bb12 --- /dev/null +++ b/app/views/admin/settings/_map_form.html.erb @@ -0,0 +1,31 @@ +<%= t("admin.settings.index.map.help") %>
+ + <%= render "map_form" %> + +<% end %> diff --git a/app/views/budgets/investments/_form.html.erb b/app/views/budgets/investments/_form.html.erb index 0d0bdd7f1..8a19c188a 100644 --- a/app/views/budgets/investments/_form.html.erb +++ b/app/views/budgets/investments/_form.html.erb @@ -29,6 +29,20 @@ <%= render 'documents/nested_documents', documentable: @investment, f: f %> + <% if feature?(:map) %> +<%= help %>
+ +<%= render_map(map_location, parent_class, editable = true, remove_marker_label) %> + +<%= form.fields_for :map_location, map_location do |m_l_fields| %> + <%= m_l_fields.hidden_field :id, + value: map_location.id, + id: map_location_input_id(parent_class, 'id') %> + <%= m_l_fields.hidden_field :latitude, + value: map_location.latitude, + id: map_location_input_id(parent_class, 'latitude') %> + <%= m_l_fields.hidden_field :longitude, + value: map_location.longitude, + id: map_location_input_id(parent_class, 'longitude') %> + <%= m_l_fields.hidden_field :zoom, + value: map_location.zoom, + id: map_location_input_id(parent_class, 'zoom') %> +<% end %> diff --git a/app/views/proposals/_form.html.erb b/app/views/proposals/_form.html.erb index df1e096e9..2390b62f6 100644 --- a/app/views/proposals/_form.html.erb +++ b/app/views/proposals/_form.html.erb @@ -59,6 +59,20 @@ <%= f.select :geozone_id, geozone_select_options, {include_blank: t("geozones.none"), label: false} %><%= t("proposals.form.tags_instructions") %>
diff --git a/app/views/proposals/show.html.erb b/app/views/proposals/show.html.erb index 96999ca7d..e25bdf9f7 100644 --- a/app/views/proposals/show.html.erb +++ b/app/views/proposals/show.html.erb @@ -68,6 +68,10 @@ <%= safe_html_with_links @proposal.description %> + <% if feature?(:map) && map_location_available?(@proposal.map_location) %> + <%= render_map(@proposal.map_location, "proposal", false, nil) %> + <% end %> + <% if @proposal.external_url.present? %>diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index fc0d279de..f5fa020bd 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -40,8 +40,8 @@ en: errors: form: error: - one: "prevented this banner from being saved" - other: 'prevented this banner from being saved' + one: "error prevented this banner from being saved" + other: 'errors prevented this banner from being saved' new: creating: Create banner activity: @@ -698,6 +698,13 @@ en: disabled: "Feature disabled" enable: "Enable" disable: "Disable" + map: + title: Map configuration + help: Here you can customize the way the map is displayed to users. Drag map marker or click anywhere over the map, set desired zoom and click button "Update". + flash: + update: Map configuration updated succesfully. + form: + submit: Update shared: booths_search: button: Search @@ -789,8 +796,8 @@ en: errors: form: error: - one: "prevented this geozone from being saved" - other: 'prevented this geozone from being saved' + one: "error prevented this geozone from being saved" + other: 'errors prevented this geozone from being saved' edit: form: submit_button: Save changes diff --git a/config/locales/en/budgets.yml b/config/locales/en/budgets.yml index c1879df16..1f29370bb 100644 --- a/config/locales/en/budgets.yml +++ b/config/locales/en/budgets.yml @@ -55,6 +55,9 @@ en: tags_instructions: "Tag this proposal. You can choose from proposed categories or add your own" tags_label: Tags tags_placeholder: "Enter the tags you would like to use, separated by commas (',')" + map_location: "Map location" + map_location_instructions: "Navigate the map to the location and place the marker." + map_remove_marker: "Remove map marker" index: title: Participatory budgeting unfeasible: Unfeasible investment projects diff --git a/config/locales/en/general.yml b/config/locales/en/general.yml index 6312a9a5c..290e1a278 100644 --- a/config/locales/en/general.yml +++ b/config/locales/en/general.yml @@ -333,6 +333,9 @@ en: tags_instructions: "Tag this proposal. You can choose from proposed categories or add your own" tags_label: Tags tags_placeholder: "Enter the tags you would like to use, separated by commas (',')" + map_location: "Map location" + map_location_instructions: "Navigate the map to the location and place the marker." + map_remove_marker: "Remove map marker" index: featured_proposals: Featured filter_topic: diff --git a/config/locales/en/settings.yml b/config/locales/en/settings.yml index e1d84393f..eaf610f4d 100644 --- a/config/locales/en/settings.yml +++ b/config/locales/en/settings.yml @@ -40,6 +40,10 @@ en: voting_allowed: Voting on investment projects legislation: Legislation community: Community on proposals and investments + map: Proposals and budget investments geolocation + map_latitude: Latitude + map_longitude: Longitude + map_zoom: Zoom mailer_from_name: Origin email name mailer_from_address: Origin email address meta_description: "Site description (SEO)" diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index 3482ad022..2e44ea7b4 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -698,6 +698,13 @@ es: disabled: "Funcionalidad desactivada" enable: "Activar" disable: "Desactivar" + map: + title: Configuración del mapa + help: Aquí puedes personalizar la manera en la que se muestra el mapa a los usuarios. Arrastra el marcador o pulsa sobre cualquier parte del mapa, ajusta el zoom y pulsa el botón 'Actualizar'. + flash: + update: La configuración del mapa se ha guardado correctamente. + form: + submit: Actualizar shared: booths_search: button: Buscar diff --git a/config/locales/es/budgets.yml b/config/locales/es/budgets.yml index 78b81fbd3..5c76c8abe 100644 --- a/config/locales/es/budgets.yml +++ b/config/locales/es/budgets.yml @@ -55,6 +55,9 @@ es: tags_label: Temas tag_category_label: "Categorías" tags_placeholder: "Escribe las etiquetas que desees separadas por una coma (',')" + map_location: "Ubicación en el mapa" + map_location_instructions: "Navega por el mapa hasta la ubicación y coloca el marcador." + map_remove_marker: "Eliminar el marcador" index: title: Presupuestos participativos unfeasible: Propuestas de inversión no viables diff --git a/config/locales/es/general.yml b/config/locales/es/general.yml index 4b1ebe2ba..722c0dabb 100644 --- a/config/locales/es/general.yml +++ b/config/locales/es/general.yml @@ -333,6 +333,9 @@ es: tags_label: Temas tag_category_label: "Categorías" tags_placeholder: "Escribe las etiquetas que desees separadas por una coma (',')" + map_location: "Ubicación en el mapa" + map_location_instructions: "Navega por el mapa hasta la ubicación y coloca el marcador." + map_remove_marker: "Eliminar el marcador" index: featured_proposals: Destacadas filter_topic: diff --git a/config/locales/es/settings.yml b/config/locales/es/settings.yml index f0a7b53a2..b23c6e688 100644 --- a/config/locales/es/settings.yml +++ b/config/locales/es/settings.yml @@ -40,6 +40,10 @@ es: voting_allowed: Votaciones sobre propuestas de inversión legislation: Legislación community: Comunidad en propuestas y proyectos de inversión + map: Geolocalización de propuestas y proyectos de inversión + map_latitude: Latitud + map_longitude: Longitud + map_zoom: Zoom mailer_from_name: Nombre email remitente mailer_from_address: Dirección email remitente meta_description: "Descripción del sitio (SEO)" diff --git a/config/routes.rb b/config/routes.rb index 1cb2d45b9..410f2dced 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -250,6 +250,8 @@ Rails.application.routes.draw do end resources :settings, only: [:index, :update] + put :update_map, to: "settings#update_map" + resources :moderators, only: [:index, :create, :destroy] do get :search, on: :collection end diff --git a/config/secrets.yml.example b/config/secrets.yml.example index f67ea65c0..6f01d715c 100644 --- a/config/secrets.yml.example +++ b/config/secrets.yml.example @@ -1,5 +1,7 @@ default: &default secret_key_base: "56792feef405a59b18ea7db57b4777e855103882b926413d4afdfb8c0ea8aa86ea6649da4e729c5f5ae324c0ab9338f789174cf48c544173bc18fdc3b14262e4" + map_tiles_provider: "//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" + map_tiles_provider_attribution: "© OpenStreetMap contributors" development: <<: *default @@ -36,6 +38,9 @@ production: &production rollbar_server_token: "" server_name: "" + map_tiles_provider: "//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" + map_tiles_provider_attribution: "© OpenStreetMap contributors" + preproduction: server_name: "" <<: *production diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index 1223d94a4..fcd86c44a 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -37,6 +37,7 @@ Setting.create(key: 'feature.google_login', value: "true") Setting.create(key: 'feature.signature_sheets', value: "true") Setting.create(key: 'feature.legislation', value: "true") Setting.create(key: 'feature.community', value: "true") +Setting.create(key: 'feature.map', value: "true") Setting.create(key: 'per_page_code_head', value: "") Setting.create(key: 'per_page_code_body', value: "") Setting.create(key: 'comments_body_max_length', value: '1000') @@ -47,6 +48,9 @@ Setting.create(key: 'meta_keywords', value: 'citizen participation, open governm Setting.create(key: 'verification_offices_url', value: 'http://oficinas-atencion-ciudadano.url/') Setting.create(key: 'min_age_to_participate', value: '16') Setting.create(key: 'proposal_improvement_path', value: nil) +Setting.create(key: 'map_latitude', value: 51.48) +Setting.create(key: 'map_longitude', value: 0.0) +Setting.create(key: 'map_zoom', value: 10) puts " ✅" print "Creating Geozones" diff --git a/db/migrate/20170805132736_create_map_locations.rb b/db/migrate/20170805132736_create_map_locations.rb new file mode 100644 index 000000000..a75820d54 --- /dev/null +++ b/db/migrate/20170805132736_create_map_locations.rb @@ -0,0 +1,11 @@ +class CreateMapLocations < ActiveRecord::Migration + def change + create_table :map_locations do |t| + t.float :latitude + t.float :longitude + t.integer :zoom + t.integer :proposal_id, index: true + t.integer :investment_id, index: true + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 41063df03..86d22f3b2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -549,6 +549,17 @@ ActiveRecord::Schema.define(version: 20170918231410) do add_index "managers", ["user_id"], name: "index_managers_on_user_id", using: :btree + create_table "map_locations", force: :cascade do |t| + t.float "latitude" + t.float "longitude" + t.integer "zoom" + t.integer "proposal_id" + t.integer "investment_id" + end + + add_index "map_locations", ["investment_id"], name: "index_map_locations_on_investment_id", using: :btree + add_index "map_locations", ["proposal_id"], name: "index_map_locations_on_proposal_id", using: :btree + create_table "moderators", force: :cascade do |t| t.integer "user_id" end diff --git a/db/seeds.rb b/db/seeds.rb index 96916f29a..ee444bd91 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -80,6 +80,7 @@ Setting['feature.budgets'] = true Setting['feature.signature_sheets'] = true Setting['feature.legislation'] = true Setting['feature.community'] = true +Setting['feature.map'] = nil # Spending proposals feature flags Setting['feature.spending_proposal_features.voting_allowed'] = nil @@ -108,3 +109,8 @@ Setting['min_age_to_participate'] = 16 # Proposal improvement url path ('/more-information/proposal-improvement') Setting['proposal_improvement_path'] = nil + +# City map feature default configuration (Greenwich) +Setting['map_latitude'] = 51.48 +Setting['map_longitude'] = 0.0 +Setting['map_zoom'] = 10 diff --git a/spec/factories.rb b/spec/factories.rb index 160b81664..b0a287aea 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -821,7 +821,7 @@ LOREM_IPSUM factory :direct_upload do user - + trait :proposal do resource_type "Proposal" end @@ -840,4 +840,18 @@ LOREM_IPSUM initialize_with { new(attributes) } end + factory :map_location do + latitude 51.48 + longitude 0.0 + zoom 10 + + trait :proposal_map_location do + proposal + end + + trait :budget_investment_map_location do + association :investment, factory: :budget_investment + end + end + end diff --git a/spec/features/admin/settings_spec.rb b/spec/features/admin/settings_spec.rb index c92b4b525..73d190cb9 100644 --- a/spec/features/admin/settings_spec.rb +++ b/spec/features/admin/settings_spec.rb @@ -28,4 +28,66 @@ feature 'Admin settings' do expect(page).to have_content 'Value updated' end -end \ No newline at end of file + describe "Update map" do + + scenario "Should not be able when map feature deactivated" do + Setting['feature.map'] = false + admin = create(:administrator).user + login_as(admin) + visit admin_settings_path + + expect(page).not_to have_content "Map configuration" + end + + scenario "Should be able when map feature activated" do + Setting['feature.map'] = true + admin = create(:administrator).user + login_as(admin) + visit admin_settings_path + + expect(page).to have_content "Map configuration" + end + + scenario "Should show successful notice" do + Setting['feature.map'] = true + admin = create(:administrator).user + login_as(admin) + visit admin_settings_path + + within "#map-form" do + click_on "Update" + end + + expect(page).to have_content "Map configuration updated succesfully" + end + + scenario "Should display marker by default", :js do + Setting['feature.map'] = true + admin = create(:administrator).user + login_as(admin) + + visit admin_settings_path + + expect(find("#latitude", visible: false).value).to eq "51.48" + expect(find("#longitude", visible: false).value).to eq "0.0" + end + + scenario "Should update marker", :js do + Setting['feature.map'] = true + admin = create(:administrator).user + login_as(admin) + + visit admin_settings_path + find("#admin-map").click + within "#map-form" do + click_on "Update" + end + + expect(find("#latitude", visible: false).value).not_to eq "51.48" + expect(page).to have_content "Map configuration updated succesfully" + end + + + end + +end diff --git a/spec/features/budgets/investments_spec.rb b/spec/features/budgets/investments_spec.rb index f54b2e3c4..d17bd9c91 100644 --- a/spec/features/budgets/investments_spec.rb +++ b/spec/features/budgets/investments_spec.rb @@ -485,6 +485,14 @@ feature 'Budget Investments' do "Create Investment", "Budget Investment created successfully." + it_behaves_like "mappable", + "budget_investment", + "investment", + "new_budget_investment_path", + "", + "budget_investment_path", + { "budget_id": "budget_id" } + context "Destroy" do scenario "Admin cannot destroy budget investments" do diff --git a/spec/features/proposals_spec.rb b/spec/features/proposals_spec.rb index 0c3481fa1..f0e50c7eb 100644 --- a/spec/features/proposals_spec.rb +++ b/spec/features/proposals_spec.rb @@ -1328,6 +1328,14 @@ feature 'Proposals' do "Save changes", "Proposal updated successfully" + it_behaves_like "mappable", + "proposal", + "proposal", + "new_proposal_path", + "edit_proposal_path", + "proposal_path", + { } + scenario 'Erased author' do user = create(:user) proposal = create(:proposal, author: user) diff --git a/spec/models/map_location_spec.rb b/spec/models/map_location_spec.rb new file mode 100644 index 000000000..bd093a71e --- /dev/null +++ b/spec/models/map_location_spec.rb @@ -0,0 +1,36 @@ +require 'rails_helper' + +describe MapLocation do + + let(:map_location) { build(:map_location, :proposal_map_location ) } + + it "should be valid" do + expect(map_location).to be_valid + end + + context "#available?" do + + it "should return true when latitude, longitude and zoom defined" do + expect(map_location.available?).to be(true) + end + + it "should return false when longitude is nil" do + map_location.longitude = nil + + expect(map_location.available?).to be(false) + end + + it "should return false when latitude is nil" do + map_location.latitude = nil + + expect(map_location.available?).to be(false) + end + + it "should return false when zoom is nil" do + map_location.zoom = nil + + expect(map_location.available?).to be(false) + end + end + +end diff --git a/spec/shared/features/documentable.rb b/spec/shared/features/documentable.rb index 126573348..2bd9d8f86 100644 --- a/spec/shared/features/documentable.rb +++ b/spec/shared/features/documentable.rb @@ -5,7 +5,6 @@ shared_examples "documentable" do |documentable_factory_name, documentable_path, let!(:user) { create(:user) } let!(:arguments) { {} } let!(:documentable) { create(documentable_factory_name, author: user) } - let!(:documentable_dom_name) { documentable_factory_name.parameterize } before do create(:administrator, user: administrator) diff --git a/spec/shared/features/mappable.rb b/spec/shared/features/mappable.rb new file mode 100644 index 000000000..ccbcd5625 --- /dev/null +++ b/spec/shared/features/mappable.rb @@ -0,0 +1,220 @@ +shared_examples "mappable" do |mappable_factory_name, mappable_association_name, mappable_new_path, mappable_edit_path, mappable_show_path, mappable_path_arguments| + + include ActionView::Helpers + + let!(:user) { create(:user, :level_two) } + + before do + Setting['feature.map'] = true + end + + describe "At #{mappable_new_path}" do + + let!(:arguments) { {} } + let!(:mappable) { create("#{mappable_factory_name}".to_sym) } + let!(:map_location) { create(:map_location, "#{mappable_factory_name}_map_location".to_sym, "#{mappable_association_name}": mappable) } + + before { set_arguments(arguments, mappable, mappable_path_arguments) } + + scenario "Should not show marker by default on create #{mappable_factory_name}", :js do + login_as user + visit send(mappable_new_path, arguments) + + send("fill_in_#{mappable_factory_name}_form") + + within ".map_location" do + expect(page).not_to have_css(".map-icon") + end + end + + scenario "Should show marker on create #{mappable_factory_name} when click on map", :js do + login_as user + visit send(mappable_new_path, arguments) + + send("fill_in_#{mappable_factory_name}_form") + find("#new_map_location").click + + within ".map_location" do + expect(page).to have_css(".map-icon") + end + end + + scenario "Should create #{mappable_factory_name} with map", :js do + login_as user + visit send(mappable_new_path, arguments) + + send("fill_in_#{mappable_factory_name}_form") + find("#new_map_location").click + send("submit_#{mappable_factory_name}_form") + + expect(page).to have_css(".map_location") + end + + scenario "Can not display map on #{mappable_factory_name} when not fill marker on map", :js do + login_as user + visit send(mappable_new_path, arguments) + + send("fill_in_#{mappable_factory_name}_form") + expect(page).to have_css ".map_location" + send("submit_#{mappable_factory_name}_form") + + expect(page).not_to have_css(".map_location") + end + + scenario "Can not display map on #{mappable_factory_name} when feature.map is disabled", :js do + Setting['feature.map'] = false + login_as user + visit send(mappable_new_path, arguments) + + send("fill_in_#{mappable_factory_name}_form") + expect(page).not_to have_css ".map_location" + send("submit_#{mappable_factory_name}_form") + + expect(page).not_to have_css(".map_location") + end + + end + + describe "At #{mappable_edit_path}" do + + let!(:mappable) { create("#{mappable_factory_name}".to_sym) } + let!(:map_location) { create(:map_location, "#{mappable_factory_name}_map_location".to_sym, "#{mappable_association_name}": mappable) } + + before { skip } unless mappable_edit_path.present? + + scenario "Should edit map on #{mappable_factory_name} and contain default values", :js do + login_as mappable.author + + visit send(mappable_edit_path, id: mappable.id) + + expect(page).to have_content "Navigate the map to the location and place the marker." + validate_latitude_longitude(mappable_factory_name) + end + + scenario "Should edit default values from map on #{mappable_factory_name} edit page", :js do + login_as mappable.author + + visit send(mappable_edit_path, id: mappable.id) + find(".map_location").click + click_on("Save changes") + mappable.reload + + expect(page).to have_css(".map_location") + expect(page).not_to have_selector(".map_location[data-marker-latitude='#{map_location.latitude}']") + expect(page).to have_selector(".map_location[data-marker-latitude='#{mappable.map_location.latitude}']") + end + + scenario "Should edit mappable on #{mappable_factory_name} without change map", :js do + login_as mappable.author + + visit send(mappable_edit_path, id: mappable.id) + fill_in "#{mappable_factory_name}_title", with: "New title" + click_on("Save changes") + mappable.reload + + expect(page).to have_css(".map_location") + expect(page).to have_selector(".map_location[data-marker-latitude='#{map_location.latitude}']") + expect(page).to have_selector(".map_location[data-marker-latitude='#{mappable.map_location.latitude}']") + end + + scenario "Can not display map on #{mappable_factory_name} edit when remove map marker", :js do + login_as mappable.author + + visit send(mappable_edit_path, id: mappable.id) + click_link "Remove map marker" + click_on "Save changes" + + expect(page).not_to have_css(".map_location") + end + + scenario "Can not display map on #{mappable_factory_name} edit when feature.map is disabled", :js do + Setting['feature.map'] = false + login_as mappable.author + + visit send(mappable_edit_path, id: mappable.id) + fill_in "#{mappable_factory_name}_title", with: "New title" + click_on("Save changes") + + expect(page).not_to have_css(".map_location") + end + + end + + describe "At #{mappable_show_path}" do + + let!(:arguments) { {} } + let!(:mappable) { create("#{mappable_factory_name}".to_sym) } + let!(:map_location) { create(:map_location, "#{mappable_factory_name}_map_location".to_sym, "#{mappable_association_name}": mappable) } + + before { set_arguments(arguments, mappable, mappable_path_arguments) } + + scenario "Should display map on #{mappable_factory_name} show page", :js do + arguments.merge!("id": mappable.id) + + visit send(mappable_show_path, arguments) + + expect(page).to have_css(".map_location") + end + + scenario "Should not display map on #{mappable_factory_name} show when marker is not defined", :js do + mappable_without_map = create("#{mappable_factory_name}".to_sym) + set_arguments(arguments, mappable_without_map, mappable_path_arguments) + arguments.merge!("id": mappable_without_map.id) + + visit send(mappable_show_path, arguments) + + expect(page).not_to have_css(".map_location") + end + + scenario "Should not display map on #{mappable_factory_name} show page when feature.map is disable", :js do + Setting['feature.map'] = false + arguments.merge!("id": mappable.id) + + visit send(mappable_show_path, arguments) + + expect(page).not_to have_css(".map_location") + end + + end + +end + +def fill_in_proposal_form + fill_in 'proposal_title', with: 'Help refugees' + fill_in 'proposal_question', with: '¿Would you like to give assistance to war refugees?' + fill_in 'proposal_summary', with: 'In summary, what we want is...' +end + +def submit_proposal_form + check :proposal_terms_of_service + click_button 'Create proposal' + + click_link 'Not now, go to my proposal' +end + +def validate_latitude_longitude(mappable_factory_name) + expect(find("##{mappable_factory_name}_map_location_attributes_latitude", visible: false).value).to eq "51.48" + expect(find("##{mappable_factory_name}_map_location_attributes_longitude", visible: false).value).to eq "0.0" + expect(mappable.map_location.latitude).to eq 51.48 + expect(mappable.map_location.longitude).to eq 0.0 +end + +def fill_in_budget_investment_form + page.select mappable.heading.name_scoped_by_group, from: :budget_investment_heading_id + fill_in :budget_investment_title, with: "Budget investment title" + fill_in_ckeditor "budget_investment_description", with: "Budget investment description" + check :budget_investment_terms_of_service +end + +def submit_budget_investment_form + check :budget_investment_terms_of_service + click_button 'Create Investment' +end + +def set_arguments(arguments, mappable, mappable_path_arguments) + if mappable_path_arguments + mappable_path_arguments.each do |argument_name, path_to_value| + arguments.merge!("#{argument_name}": mappable.send(path_to_value)) + end + end +end