From 0c0618aa797ab24d73a4ed8be8a378ab2e86dcdb Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 16 Nov 2022 11:50:19 +0100 Subject: [PATCH] feat(export): add GeoJSON export --- app/components/dossiers/export_component.rb | 22 +++++++++++++++++- .../export_component/export_component.en.yml | 15 ++++++------ .../export_component/export_component.fr.yml | 1 + app/helpers/dossier_helper.rb | 20 ---------------- app/models/champ.rb | 1 + app/models/dossier.rb | 7 ++++++ app/models/export.rb | 11 +++++++-- app/models/geo_area.rb | 3 +++ app/models/procedure_revision.rb | 4 ++++ app/services/procedure_export_service.rb | 7 ++++++ spec/models/dossier_spec.rb | 2 ++ spec/models/export_spec.rb | 9 ++++---- .../services/procedure_export_service_spec.rb | 23 +++++++++++++++++++ 13 files changed, 91 insertions(+), 34 deletions(-) diff --git a/app/components/dossiers/export_component.rb b/app/components/dossiers/export_component.rb index 2780f668d..6853b83ad 100644 --- a/app/components/dossiers/export_component.rb +++ b/app/components/dossiers/export_component.rb @@ -8,7 +8,27 @@ class Dossiers::ExportComponent < ApplicationComponent end def exports - helpers.exports_list(@exports, @statut) + if @statut + Export::FORMATS.filter(&method(:allowed_format?)).map do |item| + export = @exports + .fetch(item.fetch(:format)) + .fetch(:statut) + .fetch(@statut, nil) + item.merge(export: export) + end + else + Export::FORMATS_WITH_TIME_SPAN.map do |item| + export = @exports + .fetch(item.fetch(:format)) + .fetch(:time_span_type) + .fetch(item.fetch(:time_span_type), nil) + item.merge(export: export) + end + end + end + + def allowed_format?(item) + item.fetch(:format) != :json || @procedure.active_revision.carte? end def download_export_path(export_format:, force_export: false, no_progress_notification: nil) diff --git a/app/components/dossiers/export_component/export_component.en.yml b/app/components/dossiers/export_component/export_component.en.yml index 0da205bd2..67b504324 100644 --- a/app/components/dossiers/export_component/export_component.en.yml +++ b/app/components/dossiers/export_component/export_component.en.yml @@ -1,12 +1,13 @@ --- en: - everything_csv_html: Ask an export in format .csv
(only folders, without repeatable fields) - everything_xlsx_html: Ask an export in format .xlsx - everything_ods_html: Ask an export in format .ods - everything_zip_html: Ask an export in format .zip
(does not contains timestamp nor operation logs ) - everything_short: Ask an export in format%{export_format} - everything_pending_html: Ask an export in format %{export_format} is being generated
(ask %{export_time} ago) - everything_ready_html: Download the export in format %{export_format}
(generated %{export_time} ago) + everything_csv_html: Request an export in .csv format
(only files, without repeatable fields) + everything_xlsx_html: Request an export in .xlsx format + everything_ods_html: Request an export in .ods format + everything_zip_html: Request an export in .zip format
(does not contains timestamp nor operation logs) + everything_json_html: Request an export in .json format (GeoJSON) + everything_short: Request an export in %{export_format} format + everything_pending_html: An export in %{export_format} format is being generated
(ask %{export_time} ago) + everything_ready_html: Download the export in %{export_format} format
(generated %{export_time} ago) download_all: Download all files download: one: Download a file diff --git a/app/components/dossiers/export_component/export_component.fr.yml b/app/components/dossiers/export_component/export_component.fr.yml index 930d1598a..7895c00f3 100644 --- a/app/components/dossiers/export_component/export_component.fr.yml +++ b/app/components/dossiers/export_component/export_component.fr.yml @@ -4,6 +4,7 @@ fr: everything_xlsx_html: Demander un export au format .xlsx everything_ods_html: Demander un export au format .ods everything_zip_html: Demander un export au format .zip
(ne contient pas l'horodatage ni le journal de log) + everything_json_html: Demander un export au format .json (GeoJSON) everything_short: Demander un export au format %{export_format} everything_pending_html: Un export au format %{export_format} est en train d’être généré
(demandé il y a %{export_time}) everything_ready_html: Télécharger l’export au format %{export_format}
(généré il y a %{export_time}) diff --git a/app/helpers/dossier_helper.rb b/app/helpers/dossier_helper.rb index fc18c15d2..c51f4b5f8 100644 --- a/app/helpers/dossier_helper.rb +++ b/app/helpers/dossier_helper.rb @@ -96,26 +96,6 @@ module DossierHelper "#{base_url}/rechercher?terme=#{siren_or_siret}" end - def exports_list(exports, statut = nil) - if statut - Export::FORMATS.map do |item| - export = exports - .fetch(item.fetch(:format)) - .fetch(:statut) - .fetch(statut, nil) - item.merge(export: export) - end - else - Export::FORMATS_WITH_TIME_SPAN.map do |item| - export = exports - .fetch(item.fetch(:format)) - .fetch(:time_span_type) - .fetch(item.fetch(:time_span_type), nil) - item.merge(export: export) - end - end - end - def france_connect_informations(user_information) if user_information.full_name.empty? t("shared.dossiers.france_connect_informations.details_no_name") diff --git a/app/models/champ.rb b/app/models/champ.rb index 7db57ccb9..3fc3b2bb4 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -61,6 +61,7 @@ class Champ < ApplicationRecord :mesri?, :rna?, :siret?, + :carte?, :stable_id, :mandatory?, to: :type_de_champ diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 7599582e4..fea101a0f 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -1144,6 +1144,13 @@ class Dossier < ApplicationRecord } end + def self.to_feature_collection + { + type: 'FeatureCollection', + features: GeoArea.joins(:champ).where(champ: { dossier: ids }).map(&:to_feature) + } + end + def log_api_entreprise_job_exception(exception) exceptions = self.api_entreprise_job_exceptions ||= [] exceptions << exception.inspect diff --git a/app/models/export.rb b/app/models/export.rb index 80fccbe1b..4f21b176d 100644 --- a/app/models/export.rb +++ b/app/models/export.rb @@ -23,7 +23,8 @@ class Export < ApplicationRecord csv: 'csv', ods: 'ods', xlsx: 'xlsx', - zip: 'zip' + zip: 'zip', + json: 'json' }, _prefix: true enum time_span_type: { @@ -53,7 +54,7 @@ class Export < ApplicationRecord FORMATS_WITH_TIME_SPAN = [:xlsx, :ods, :csv].flat_map do |format| [{ format: format, time_span_type: 'everything' }] end - FORMATS = [:xlsx, :ods, :csv, :zip].map do |format| + FORMATS = [:xlsx, :ods, :csv, :zip, :json].map do |format| { format: format } end @@ -127,6 +128,10 @@ class Export < ApplicationRecord zip: { time_span_type: {}, statut: filtered.filter(&:format_zip?).index_by(&:statut) + }, + json: { + time_span_type: {}, + statut: filtered.filter(&:format_json?).index_by(&:statut) } } end @@ -195,6 +200,8 @@ class Export < ApplicationRecord service.to_ods when :zip service.to_zip + when :json + service.to_geo_json end end diff --git a/app/models/geo_area.rb b/app/models/geo_area.rb index bb94da325..99e163400 100644 --- a/app/models/geo_area.rb +++ b/app/models/geo_area.rb @@ -64,7 +64,10 @@ class GeoArea < ApplicationRecord description: description, filename: filename, id: id, + champ_label: champ.libelle, champ_id: champ.stable_id, + champ_row: champ.row, + champ_private: champ.private?, dossier_id: champ.dossier_id ).compact } diff --git a/app/models/procedure_revision.rb b/app/models/procedure_revision.rb index aea71e8d4..8c550594b 100644 --- a/app/models/procedure_revision.rb +++ b/app/models/procedure_revision.rb @@ -200,6 +200,10 @@ class ProcedureRevision < ApplicationRecord revision_types_de_champ.find_by!(type_de_champ: tdc) end + def carte? + types_de_champ_public.any?(&:carte?) + end + private def compute_estimated_fill_duration diff --git a/app/services/procedure_export_service.rb b/app/services/procedure_export_service.rb index 68a35a16c..def935f06 100644 --- a/app/services/procedure_export_service.rb +++ b/app/services/procedure_export_service.rb @@ -42,6 +42,11 @@ class ProcedureExportService end end + def to_geo_json + io = StringIO.new(dossiers.to_feature_collection.to_json) + create_blob(io, :json) + end + private def create_blob(io, format) @@ -77,6 +82,8 @@ class ProcedureExportService 'application/vnd.oasis.opendocument.spreadsheet' when :zip 'application/zip' + when :json + 'application/json' end end diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 68f5739d8..956add7a1 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -1444,7 +1444,9 @@ describe Dossier do }, properties: { area: 103.6, + champ_label: champ_carte.libelle, champ_id: champ_carte.stable_id, + champ_private: false, dossier_id: dossier.id, id: geo_area.id, source: 'selection_utilisateur' diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb index fb7f0831a..37a0508c8 100644 --- a/spec/models/export_spec.rb +++ b/spec/models/export_spec.rb @@ -61,9 +61,9 @@ RSpec.describe Export, type: :model do context 'when an export is made for one groupe instructeur' do let!(:export) { create(:export, groupe_instructeurs: [gi_1, gi_2]) } - it { expect(Export.find_for_groupe_instructeurs([gi_1.id], nil)).to eq({ csv: { statut: {}, time_span_type: {} }, xlsx: { statut: {}, time_span_type: {} }, ods: { statut: {}, time_span_type: {} }, zip: { statut: {}, time_span_type: {} } }) } - it { expect(Export.find_for_groupe_instructeurs([gi_2.id, gi_1.id], nil)).to eq({ csv: { statut: {}, time_span_type: { 'everything' => export } }, xlsx: { statut: {}, time_span_type: {} }, ods: { statut: {}, time_span_type: {} }, zip: { statut: {}, time_span_type: {} } }) } - it { expect(Export.find_for_groupe_instructeurs([gi_1.id, gi_2.id, gi_3.id], nil)).to eq({ csv: { statut: {}, time_span_type: {} }, xlsx: { statut: {}, time_span_type: {} }, ods: { statut: {}, time_span_type: {} }, zip: { statut: {}, time_span_type: {} } }) } + it { expect(Export.find_for_groupe_instructeurs([gi_1.id], nil)).to eq({ csv: { statut: {}, time_span_type: {} }, xlsx: { statut: {}, time_span_type: {} }, ods: { statut: {}, time_span_type: {} }, zip: { statut: {}, time_span_type: {} }, json: { statut: {}, time_span_type: {} } }) } + it { expect(Export.find_for_groupe_instructeurs([gi_2.id, gi_1.id], nil)).to eq({ csv: { statut: {}, time_span_type: { 'everything' => export } }, xlsx: { statut: {}, time_span_type: {} }, ods: { statut: {}, time_span_type: {} }, zip: { statut: {}, time_span_type: {} }, json: { statut: {}, time_span_type: {} } }) } + it { expect(Export.find_for_groupe_instructeurs([gi_1.id, gi_2.id, gi_3.id], nil)).to eq({ csv: { statut: {}, time_span_type: {} }, xlsx: { statut: {}, time_span_type: {} }, ods: { statut: {}, time_span_type: {} }, zip: { statut: {}, time_span_type: {} }, json: { statut: {}, time_span_type: {} } }) } end end @@ -78,7 +78,8 @@ RSpec.describe Export, type: :model do csv: { statut: { Export.statuts.fetch(:suivis) => export_with_filter }, time_span_type: { 'everything' => export_global } }, xlsx: { statut: {}, time_span_type: {} }, ods: { statut: {}, time_span_type: {} }, - zip: { statut: {}, time_span_type: {} } + zip: { statut: {}, time_span_type: {} }, + json: { statut: {}, time_span_type: {} } }) end end diff --git a/spec/services/procedure_export_service_spec.rb b/spec/services/procedure_export_service_spec.rb index 040a7f84e..a084e3410 100644 --- a/spec/services/procedure_export_service_spec.rb +++ b/spec/services/procedure_export_service_spec.rb @@ -467,4 +467,27 @@ describe ProcedureExportService do end end end + + describe 'to_geo_json' do + subject do + service + .to_geo_json + .open { |f| JSON.parse(f.read) } + end + + let(:dossier) { create(:dossier, :en_instruction, :with_populated_champs, :with_individual, procedure: procedure) } + let(:champ_carte) { dossier.champs_public.find(&:carte?) } + let(:properties) { subject['features'].first['properties'] } + + before do + create(:geo_area, :polygon, champ: champ_carte) + end + + it 'should have features' do + expect(subject['features'].size).to eq(1) + expect(properties['dossier_id']).to eq(dossier.id) + expect(properties['champ_id']).to eq(champ_carte.stable_id) + expect(properties['champ_label']).to eq(champ_carte.libelle) + end + end end