Merge pull request #5055 from betagouv/dev

2020-04-17-01
This commit is contained in:
Keirua 2020-04-17 14:43:54 +02:00 committed by GitHub
commit 337f934fad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 101 additions and 103 deletions

View file

@ -1,16 +1,15 @@
class Champs::CarteController < ApplicationController class Champs::CarteController < ApplicationController
before_action :authenticate_logged_user! before_action :authenticate_logged_user!
EMPTY_GEO_JSON = '[]'
ERROR_GEO_JSON = '' ERROR_GEO_JSON = ''
def show def show
@selector = ".carte-#{params[:position]}" @selector = ".carte-#{params[:position]}"
if params[:dossier].key?(:champs_attributes) feature_collection = if params[:dossier].key?(:champs_attributes)
coordinates = params[:dossier][:champs_attributes][params[:position]][:value] params[:dossier][:champs_attributes][params[:position]][:value]
else else
coordinates = params[:dossier][:champs_private_attributes][params[:position]][:value] params[:dossier][:champs_private_attributes][params[:position]][:value]
end end
@champ = if params[:champ_id].present? @champ = if params[:champ_id].present?
@ -21,40 +20,21 @@ class Champs::CarteController < ApplicationController
geo_areas = [] geo_areas = []
if coordinates == EMPTY_GEO_JSON if feature_collection == ERROR_GEO_JSON
@champ.value = nil
@champ.geo_areas = []
elsif coordinates == ERROR_GEO_JSON
@error = true @error = true
@champ.value = nil
@champ.geo_areas = []
else else
coordinates = JSON.parse(coordinates) feature_collection = JSON.parse(feature_collection, symbolize_names: true)
if @champ.cadastres? if @champ.cadastres?
cadastres = ApiCartoService.generate_cadastre(coordinates) populate_cadastres(feature_collection)
geo_areas += cadastres.map do |cadastre|
cadastre[:source] = GeoArea.sources.fetch(:cadastre)
cadastre
end
end end
selections_utilisateur = legacy_selections_utilisateur_to_polygons(coordinates) geo_areas = GeoArea.from_feature_collection(feature_collection)
geo_areas += selections_utilisateur.map do |selection_utilisateur|
selection_utilisateur.merge(source: GeoArea.sources.fetch(:selection_utilisateur))
end
@champ.geo_areas = geo_areas.map do |geo_area|
GeoArea.new(geo_area)
end
@champ.value = coordinates.to_json
end end
if @champ.persisted? if @champ.persisted?
@champ.save @champ.update(value: nil, geo_areas: geo_areas)
end end
rescue ApiCarto::API::ResourceNotFound rescue ApiCarto::API::ResourceNotFound
flash.alert = 'Les données cartographiques sont temporairement indisponibles. Réessayez dans un instant.' flash.alert = 'Les données cartographiques sont temporairement indisponibles. Réessayez dans un instant.'
response.status = 503 response.status = 503
@ -62,14 +42,23 @@ class Champs::CarteController < ApplicationController
private private
def legacy_selections_utilisateur_to_polygons(coordinates) def populate_cadastres(feature_collection)
coordinates.map do |lat_longs| coordinates = feature_collection[:features].filter do |feature|
{ feature[:geometry][:type] == 'Polygon'
geometry: { end.map do |feature|
type: 'Polygon', feature[:geometry][:coordinates][0].map { |(lng, lat)| { 'lng' => lng, 'lat' => lat } }
coordinates: [lat_longs.map { |lat_long| [lat_long['lng'], lat_long['lat']] }] end
if coordinates.present?
cadastres = ApiCartoService.generate_cadastre(coordinates)
feature_collection[:features] += cadastres.map do |cadastre|
{
type: 'Feature',
geometry: cadastre.delete(:geometry),
properties: cadastre.merge(source: GeoArea.sources.fetch(:cadastre))
} }
} end
end end
end end
end end

View file

@ -1,9 +1,10 @@
import L from 'leaflet'; import L from 'leaflet';
import FreeDraw from 'leaflet-freedraw'; import FreeDraw from 'leaflet-freedraw';
import area from '@turf/area';
import { fire, delegate } from '@utils'; import { fire, delegate } from '@utils';
import $ from 'jquery'; import $ from 'jquery';
import polygonArea from './polygon_area'; import createFeatureCollection from './create-feature-collection';
const MAPS = new WeakMap(); const MAPS = new WeakMap();
@ -81,13 +82,18 @@ function drawUserSelectionEditor(map, { selection }) {
export function addFreeDrawEvents(map, selector) { export function addFreeDrawEvents(map, selector) {
const input = findInput(selector); const input = findInput(selector);
map.freeDraw.on('markers', ({ latLngs }) => { map.freeDraw.on('markers', ({ latLngs }) => {
if (latLngs.length === 0) { if (latLngs.length === 0) {
input.value = EMPTY_GEO_JSON; input.value = EMPTY_GEO_JSON;
} else if (polygonArea(latLngs) < 300000) {
input.value = JSON.stringify(latLngs);
} else { } else {
input.value = ERROR_GEO_JSON; const featureCollection = createFeatureCollection(latLngs);
if (area(featureCollection) < 300000) {
input.value = JSON.stringify(featureCollection);
} else {
input.value = ERROR_GEO_JSON;
}
} }
fire(input, 'change'); fire(input, 'change');
@ -121,7 +127,7 @@ function getCurrentMap(element) {
} }
} }
const EMPTY_GEO_JSON = '[]'; const EMPTY_GEO_JSON = '{ "type": "FeatureCollection", "features": [] }';
const ERROR_GEO_JSON = ''; const ERROR_GEO_JSON = '';
function findInput(selector) { function findInput(selector) {

View file

@ -1,16 +1,16 @@
import area from '@turf/area'; export default function createFeatureCollection(latLngs) {
return {
export default function polygonArea(latLngs) {
return area({
type: 'FeatureCollection', type: 'FeatureCollection',
features: latLngs.map(featurePolygonLatLngs) features: latLngs.map(featurePolygonLatLngs)
}); };
} }
function featurePolygonLatLngs(latLngs) { function featurePolygonLatLngs(latLngs) {
return { return {
type: 'Feature', type: 'Feature',
properties: {}, properties: {
source: 'selection_utilisateur'
},
geometry: { geometry: {
type: 'Polygon', type: 'Polygon',
coordinates: [latLngs.map(({ lng, lat }) => [lng, lat])] coordinates: [latLngs.map(({ lng, lat }) => [lng, lat])]

View file

@ -74,6 +74,7 @@ class Champs::CarteChamp < Champ
def to_feature_collection def to_feature_collection
{ {
type: 'FeatureCollection', type: 'FeatureCollection',
id: type_de_champ.stable_id,
bbox: bounding_box, bbox: bounding_box,
features: (legacy_selections_utilisateur + except_selections_utilisateur).map(&:to_feature) features: (legacy_selections_utilisateur + except_selections_utilisateur).map(&:to_feature)
} }

View file

@ -43,4 +43,14 @@ class GeoArea < ApplicationRecord
def rgeo_geometry def rgeo_geometry
RGeo::GeoJSON.decode(geometry.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory) RGeo::GeoJSON.decode(geometry.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory)
end end
def self.from_feature_collection(feature_collection)
feature_collection[:features].map do |feature|
GeoArea.new(
source: feature[:properties].delete(:source),
properties: feature[:properties],
geometry: feature[:geometry]
)
end
end
end end

View file

@ -85,11 +85,8 @@ class Procedure < ApplicationRecord
validate :validate_for_publication, on: :publication validate :validate_for_publication, on: :publication
validate :check_juridique validate :check_juridique
validates :path, presence: true, format: { with: /\A[a-z0-9_\-]{3,50}\z/ }, uniqueness: { scope: [:path, :closed_at, :hidden_at, :unpublished_at], case_sensitive: false } validates :path, presence: true, format: { with: /\A[a-z0-9_\-]{3,50}\z/ }, uniqueness: { scope: [:path, :closed_at, :hidden_at, :unpublished_at], case_sensitive: false }
# FIXME: remove duree_conservation_required flag once all procedures are converted to the new style validates :duree_conservation_dossiers_dans_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DUREE_CONSERVATION }
validates :duree_conservation_dossiers_dans_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DUREE_CONSERVATION }, if: :durees_conservation_required validates :duree_conservation_dossiers_hors_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :duree_conservation_dossiers_hors_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, if: :durees_conservation_required
validates :duree_conservation_dossiers_dans_ds, allow_nil: true, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DUREE_CONSERVATION }, unless: :durees_conservation_required
validates :duree_conservation_dossiers_hors_ds, allow_nil: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, unless: :durees_conservation_required
validates_with MonAvisEmbedValidator validates_with MonAvisEmbedValidator
validates :notice, content_type: [ validates :notice, content_type: [
"application/msword", "application/msword",
@ -112,7 +109,6 @@ class Procedure < ApplicationRecord
validates :logo, content_type: ['image/png', 'image/jpg', 'image/jpeg'], size: { less_than: 5.megabytes } validates :logo, content_type: ['image/png', 'image/jpg', 'image/jpeg'], size: { less_than: 5.megabytes }
before_save :update_juridique_required before_save :update_juridique_required
before_save :update_durees_conservation_required
after_initialize :ensure_path_exists after_initialize :ensure_path_exists
before_save :ensure_path_exists before_save :ensure_path_exists
after_create :ensure_default_groupe_instructeur after_create :ensure_default_groupe_instructeur
@ -597,11 +593,6 @@ class Procedure < ApplicationRecord
end end
end end
def update_durees_conservation_required
self.durees_conservation_required ||= duree_conservation_dossiers_hors_ds.present? && duree_conservation_dossiers_dans_ds.present?
true
end
def percentile_time(start_attribute, end_attribute, p) def percentile_time(start_attribute, end_attribute, p)
times = dossiers times = dossiers
.where.not(start_attribute => nil, end_attribute => nil) .where.not(start_attribute => nil, end_attribute => nil)

View file

@ -38,7 +38,7 @@ class DossierSerializer < ActiveModel::Serializer
end end
if champ_carte.present? if champ_carte.present?
champs_geo_areas = geo_areas.filter do |geo_area| champs_geo_areas = champ_carte.geo_areas.filter do |geo_area|
geo_area.source != GeoArea.sources.fetch(:selection_utilisateur) geo_area.source != GeoArea.sources.fetch(:selection_utilisateur)
end end
champs_geo_areas << champ_carte.selection_utilisateur_legacy_geo_area champs_geo_areas << champ_carte.selection_utilisateur_legacy_geo_area

View file

@ -5,3 +5,4 @@
locals: { champ: @champ, error: @error }) %> locals: { champ: @champ, error: @error }) %>
<%= fire_event('carte:update', { selector: @selector, data: @champ.to_render_data }.to_json) %> <%= fire_event('carte:update', { selector: @selector, data: @champ.to_render_data }.to_json) %>
<%= fire_event('map:update', { featureCollection: @champ.to_feature_collection }.to_json) %>

View file

@ -36,7 +36,12 @@ describe Champs::CarteController, type: :controller do
end end
context 'when coordinates are empty' do context 'when coordinates are empty' do
let(:value) { '[]' } let(:value) do
{
type: 'FeatureCollection',
features: []
}.to_json
end
it { it {
expect(assigns(:error)).to eq(nil) expect(assigns(:error)).to eq(nil)
@ -47,11 +52,26 @@ describe Champs::CarteController, type: :controller do
end end
context 'when coordinates are informed' do context 'when coordinates are informed' do
let(:value) { [[{ "lat": 48.87442541960633, "lng": 2.3859214782714844 }, { "lat": 48.87273183590832, "lng": 2.3850631713867183 }, { "lat": 48.87081237174292, "lng": 2.3809432983398438 }, { "lat": 48.8712640169951, "lng": 2.377510070800781 }, { "lat": 48.87510283703279, "lng": 2.3778533935546875 }, { "lat": 48.87544154230615, "lng": 2.382831573486328 }, { "lat": 48.87442541960633, "lng": 2.3859214782714844 }]].to_json } let(:value) do
{
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {
source: 'selection_utilisateur'
},
geometry: { type: 'Polygon', coordinates: [[[2.3859214782714844, 48.87442541960633], [2.3850631713867183, 48.87273183590832], [2.3809432983398438, 48.87081237174292], [2.377510070800781, 48.8712640169951], [2.3859214782714844, 48.87442541960633]]] }
}
]
}.to_json
end
it { expect(response.body).not_to be_nil } it {
it { expect(response.body).to include('MultiPolygon') } expect(response.body).not_to be_nil
it { expect(response.body).to include('[2.38715792094576,48.8723062632126]') } expect(response.body).to include('MultiPolygon')
expect(response.body).to include('[2.38715792094576,48.8723062632126]')
}
end end
context 'when error' do context 'when error' do
@ -76,10 +96,25 @@ describe Champs::CarteController, type: :controller do
post :show, params: params, format: 'js' post :show, params: params, format: 'js'
end end
let(:value) { [[{ "lat": 48.87442541960633, "lng": 2.3859214782714844 }, { "lat": 48.87273183590832, "lng": 2.3850631713867183 }, { "lat": 48.87081237174292, "lng": 2.3809432983398438 }, { "lat": 48.8712640169951, "lng": 2.377510070800781 }, { "lat": 48.87510283703279, "lng": 2.3778533935546875 }, { "lat": 48.87544154230615, "lng": 2.382831573486328 }, { "lat": 48.87442541960633, "lng": 2.3859214782714844 }]].to_json } let(:value) do
{
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {
source: 'selection_utilisateur'
},
geometry: { type: 'Polygon', coordinates: [[[2.3859214782714844, 48.87442541960633], [2.3850631713867183, 48.87273183590832], [2.3809432983398438, 48.87081237174292], [2.377510070800781, 48.8712640169951], [2.3859214782714844, 48.87442541960633]]] }
}
]
}.to_json
end
it { expect(response.status).to eq 503 } it {
it { expect(response.body).to include('Les données cartographiques sont temporairement indisponibles') } expect(response.status).to eq 503
expect(response.body).to include('Les données cartographiques sont temporairement indisponibles')
}
end end
end end
end end

View file

@ -1,5 +1,5 @@
describe Champs::CarteChamp do describe Champs::CarteChamp do
let(:champ) { Champs::CarteChamp.new(geo_areas: geo_areas) } let(:champ) { Champs::CarteChamp.new(geo_areas: geo_areas, type_de_champ: create(:type_de_champ_carte)) }
let(:value) { '' } let(:value) { '' }
let(:coordinates) { [[2.3859214782714844, 48.87442541960633], [2.3850631713867183, 48.87273183590832], [2.3809432983398438, 48.87081237174292], [2.3859214782714844, 48.87442541960633]] } let(:coordinates) { [[2.3859214782714844, 48.87442541960633], [2.3850631713867183, 48.87273183590832], [2.3809432983398438, 48.87081237174292], [2.3859214782714844, 48.87442541960633]] }
let(:geo_json) do let(:geo_json) do
@ -49,6 +49,7 @@ describe Champs::CarteChamp do
let(:feature_collection) { let(:feature_collection) {
{ {
type: 'FeatureCollection', type: 'FeatureCollection',
id: champ.type_de_champ.stable_id,
bbox: champ.bounding_box, bbox: champ.bounding_box,
features: features features: features
} }

View file

@ -252,22 +252,11 @@ describe Procedure do
shared_examples 'duree de conservation' do shared_examples 'duree de conservation' do
context 'duree_conservation_required it true, the field gets validated' do context 'duree_conservation_required it true, the field gets validated' do
before { subject.durees_conservation_required = true }
it { is_expected.not_to allow_value(nil).for(field_name) } it { is_expected.not_to allow_value(nil).for(field_name) }
it { is_expected.not_to allow_value('').for(field_name) } it { is_expected.not_to allow_value('').for(field_name) }
it { is_expected.not_to allow_value('trois').for(field_name) } it { is_expected.not_to allow_value('trois').for(field_name) }
it { is_expected.to allow_value(3).for(field_name) } it { is_expected.to allow_value(3).for(field_name) }
end end
context 'duree_conservation_required is false, the field doesnt get validated' do
before { subject.durees_conservation_required = false }
it { is_expected.to allow_value(nil).for(field_name) }
it { is_expected.to allow_value('').for(field_name) }
it { is_expected.not_to allow_value('trois').for(field_name) }
it { is_expected.to allow_value(3).for(field_name) }
end
end end
describe 'duree de conservation dans ds' do describe 'duree de conservation dans ds' do
@ -283,31 +272,6 @@ describe Procedure do
end end
end end
describe '#duree_de_conservation_required' do
it 'automatically jumps to true once both durees de conservation have been set' do
p = build(
:procedure,
durees_conservation_required: false,
duree_conservation_dossiers_dans_ds: nil,
duree_conservation_dossiers_hors_ds: nil
)
p.save
expect(p.durees_conservation_required).to be_falsey
p.duree_conservation_dossiers_hors_ds = 3
p.save
expect(p.durees_conservation_required).to be_falsey
p.duree_conservation_dossiers_dans_ds = 6
p.save
expect(p.durees_conservation_required).to be_truthy
p.duree_conservation_dossiers_dans_ds = nil
p.save
expect(p.durees_conservation_required).to be_truthy
end
end
describe '#types_de_champ (ordered)' do describe '#types_de_champ (ordered)' do
let(:procedure) { create(:procedure) } let(:procedure) { create(:procedure) }
let!(:type_de_champ_0) { create(:type_de_champ, procedure: procedure, order_place: 1) } let!(:type_de_champ_0) { create(:type_de_champ, procedure: procedure, order_place: 1) }