Merge pull request #3376 from betagouv/dev

2019-02-05-01
This commit is contained in:
LeSim 2019-02-05 20:17:41 +01:00 committed by GitHub
commit d19bccb935
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 180 additions and 107 deletions

View file

@ -89,6 +89,9 @@ module ProcedureHelper
.merge(include: TYPES_DE_CHAMP_INCLUDE.merge(types_de_champ: TYPES_DE_CHAMP_BASE)) .merge(include: TYPES_DE_CHAMP_INCLUDE.merge(types_de_champ: TYPES_DE_CHAMP_BASE))
def types_de_champ_as_json(types_de_champ) def types_de_champ_as_json(types_de_champ)
types_de_champ.as_json(TYPES_DE_CHAMP) types_de_champ.includes(:drop_down_list,
piece_justificative_template_attachment: :blob,
types_de_champ: [:drop_down_list, piece_justificative_template_attachment: :blob])
.as_json(TYPES_DE_CHAMP)
end end
end end

View file

@ -130,9 +130,7 @@ export default {
addChamp() { addChamp() {
this.typesDeChamp.push({ this.typesDeChamp.push({
type_champ: 'text', type_champ: 'text',
drop_down_list: {}, types_de_champ: []
types_de_champ: [],
options: {}
}); });
} }
} }

View file

@ -53,6 +53,14 @@ function initEditor(el) {
this.update = update; this.update = update;
this.updateAll = updateAll; this.updateAll = updateAll;
// We add an initial type de champ here if form is empty
if (this.state.typesDeChamp.length === 0) {
this.state.typesDeChamp.push({
type_champ: 'text',
types_de_champ: []
});
}
} }
}); });
} }

View file

@ -1,29 +1,28 @@
addEventListener('turbolinks:load', () => { import { delegate } from '@utils';
const primaries = document.querySelectorAll('select[data-secondary-options]');
for (let primary of primaries) { const PRIMARY_SELECTOR = 'select[data-secondary-options]';
let secondary = document.querySelector( const SECONDARY_SELECTOR = 'select[data-secondary]';
`select[data-secondary-id="${primary.dataset.primaryId}"]` const CHAMP_SELECTOR = '.editable-champ';
);
let secondaryOptions = JSON.parse(primary.dataset.secondaryOptions);
primary.addEventListener('change', e => { delegate('change', PRIMARY_SELECTOR, evt => {
let option, options, element; const primary = evt.target;
const secondary = primary
.closest(CHAMP_SELECTOR)
.querySelector(SECONDARY_SELECTOR);
const options = JSON.parse(primary.dataset.secondaryOptions);
while ((option = secondary.firstChild)) { selectOptions(secondary, options[primary.value]);
secondary.removeChild(option); });
}
options = secondaryOptions[e.target.value]; function selectOptions(selectElement, options) {
selectElement.innerHTML = '';
for (let option of options) { for (let option of options) {
element = document.createElement('option'); let element = document.createElement('option');
element.textContent = option; element.textContent = option;
element.value = option; element.value = option;
secondary.appendChild(element); selectElement.appendChild(element);
} }
secondary.selectedIndex = 0; selectElement.selectedIndex = 0;
}); }
}
});

View file

@ -4,8 +4,7 @@ const BUTTON_SELECTOR = '.button.remove-row';
const DESTROY_INPUT_SELECTOR = 'input[type=hidden][name*=_destroy]'; const DESTROY_INPUT_SELECTOR = 'input[type=hidden][name*=_destroy]';
const CHAMP_SELECTOR = '.editable-champ'; const CHAMP_SELECTOR = '.editable-champ';
addEventListener('turbolinks:load', () => { delegate('click', BUTTON_SELECTOR, evt => {
delegate('click', BUTTON_SELECTOR, evt => {
evt.preventDefault(); evt.preventDefault();
const row = evt.target.closest('.row'); const row = evt.target.closest('.row');
@ -20,5 +19,4 @@ addEventListener('turbolinks:load', () => {
evt.target.remove(); evt.target.remove();
row.classList.remove('row'); row.classList.remove('row');
});
}); });

View file

@ -30,7 +30,7 @@ class Champs::LinkedDropDownListChamp < Champ
end end
def to_s def to_s
value.present? ? [primary_value, secondary_value].compact.join(' / ') : "" value.present? ? [primary_value, secondary_value].select(&:present?).join(' / ') : ""
end end
def for_export def for_export

View file

@ -167,13 +167,11 @@ module TagsSubstitutionConcern
end end
def types_de_champ_tags(types_de_champ, available_for_states) def types_de_champ_tags(types_de_champ, available_for_states)
types_de_champ.map do |tdc| tags = types_de_champ.flat_map(&:tags_for_template)
{ tags.each do |tag|
libelle: tdc.libelle, tag[:available_for_states] = available_for_states
description: tdc.description,
available_for_states: available_for_states
}
end end
tags
end end
def replace_tags(text, dossier) def replace_tags(text, dossier)
@ -181,10 +179,9 @@ module TagsSubstitutionConcern
return '' return ''
end end
text = replace_type_de_champ_tags(text, filter_tags(champ_public_tags), dossier.champs)
text = replace_type_de_champ_tags(text, filter_tags(champ_private_tags), dossier.champs_private)
tags_and_datas = [ tags_and_datas = [
[champ_public_tags, dossier.champs],
[champ_private_tags, dossier.champs_private],
[dossier_tags, dossier], [dossier_tags, dossier],
[INDIVIDUAL_TAGS, dossier.individual], [INDIVIDUAL_TAGS, dossier.individual],
[ENTREPRISE_TAGS, dossier.etablissement&.entreprise] [ENTREPRISE_TAGS, dossier.etablissement&.entreprise]
@ -195,38 +192,29 @@ module TagsSubstitutionConcern
.inject(text) { |acc, (tags, data)| replace_tags_with_values_from_data(acc, tags, data) } .inject(text) { |acc, (tags, data)| replace_tags_with_values_from_data(acc, tags, data) }
end end
def replace_type_de_champ_tags(text, types_de_champ, dossier_champs)
types_de_champ.inject(text) do |acc, tag|
champ = dossier_champs
.select { |dossier_champ| dossier_champ.libelle == tag[:libelle] }
.first
replace_tag(acc, tag, champ)
end
end
def replace_tags_with_values_from_data(text, tags, data) def replace_tags_with_values_from_data(text, tags, data)
if data.present? if data.present?
tags.inject(text) do |acc, tag| tags.inject(text) do |acc, tag|
if tag.key?(:target) replace_tag(acc, tag, data)
value = data.send(tag[:target])
else
value = instance_exec(data, &tag[:lambda])
end
replace_tag(acc, tag, value)
end end
else else
text text
end end
end end
def replace_tag(text, tag, value) def replace_tag(text, tag, data)
libelle = Regexp.quote(tag[:libelle]) libelle = Regexp.quote(tag[:libelle])
# allow any kind of space (non-breaking or other) in the tags libellé to match any kind of space in the template # allow any kind of space (non-breaking or other) in the tags libellé to match any kind of space in the template
# (the '\\ |' is there because plain ASCII spaces were escaped by preceding Regexp.quote) # (the '\\ |' is there because plain ASCII spaces were escaped by preceding Regexp.quote)
libelle.gsub!(/\\ |[[:blank:]]/, "[[:blank:]]") libelle.gsub!(/\\ |[[:blank:]]/, "[[:blank:]]")
if tag.key?(:target)
value = data.send(tag[:target])
else
value = instance_exec(data, &tag[:lambda])
end
text.gsub(/--#{libelle}--/, value.to_s) text.gsub(/--#{libelle}--/, value.to_s)
end end
end end

View file

@ -186,7 +186,7 @@ class Dossier < ApplicationRecord
"Dossier en brouillon répondant à la démarche ", "Dossier en brouillon répondant à la démarche ",
procedure.libelle, procedure.libelle,
" gérée par l'organisme ", " gérée par l'organisme ",
procedure.organisation procedure.organisation_name
] ]
else else
parts = [ parts = [
@ -195,7 +195,7 @@ class Dossier < ApplicationRecord
" sur la démarche ", " sur la démarche ",
procedure.libelle, procedure.libelle,
" gérée par l'organisme ", " gérée par l'organisme ",
procedure.organisation procedure.organisation_name
] ]
end end

View file

@ -38,6 +38,7 @@ class TypeDeChamp < ApplicationRecord
has_many :types_de_champ, -> { ordered }, foreign_key: :parent_id, class_name: 'TypeDeChamp', dependent: :destroy has_many :types_de_champ, -> { ordered }, foreign_key: :parent_id, class_name: 'TypeDeChamp', dependent: :destroy
store_accessor :options, :cadastres, :quartiers_prioritaires, :parcelles_agricoles, :old_pj store_accessor :options, :cadastres, :quartiers_prioritaires, :parcelles_agricoles, :old_pj
delegate :tags_for_template, to: :dynamic_type
# TODO simplify after migrating `options` column to (non YAML encoded) JSON # TODO simplify after migrating `options` column to (non YAML encoded) JSON
class MaybeYaml class MaybeYaml

View file

@ -21,6 +21,34 @@ class TypesDeChamp::LinkedDropDownListTypeDeChamp < TypesDeChamp::TypeDeChampBas
secondary_options secondary_options
end end
def tags_for_template
tags = super
l = libelle
tags.push(
{
libelle: "#{l}/primaire",
description: "#{description} (menu primaire)",
lambda: -> (champs) {
champs
.detect { |champ| champ.libelle == l }
&.primary_value
}
}
)
tags.push(
{
libelle: "#{l}/secondaire",
description: "#{description} (menu secondaire)",
lambda: -> (champs) {
champs
.detect { |champ| champ.libelle == l }
&.secondary_value
}
}
)
tags
end
private private
def check_presence_of_primary_options def check_presence_of_primary_options

View file

@ -1,9 +1,22 @@
class TypesDeChamp::TypeDeChampBase class TypesDeChamp::TypeDeChampBase
include ActiveModel::Validations include ActiveModel::Validations
delegate :libelle, to: :@type_de_champ delegate :description, :libelle, to: :@type_de_champ
def initialize(type_de_champ) def initialize(type_de_champ)
@type_de_champ = type_de_champ @type_de_champ = type_de_champ
end end
def tags_for_template
l = libelle
[
{
libelle: l,
description: description,
lambda: -> (champs) {
champs.detect { |champ| champ.libelle == l }
}
}
]
end
end end

View file

@ -1,15 +1,21 @@
class ClamavService class ClamavService
def self.safe_file?(file_path) def self.safe_file?(file_path)
if Rails.env.development? if Rails.env.development?
Rails.logger.info("Rails.env = development => fake scan") # FIXME : remove me
return true return true
end end
FileUtils.chmod(0666, file_path) FileUtils.chmod(0666, file_path)
client = ClamAV::Client.new client = ClamAV::Client.new
response = client.execute(ClamAV::Commands::ScanCommand.new(file_path)) response = client.execute(ClamAV::Commands::ScanCommand.new(file_path)).first
Rails.logger.info("ClamAV response for #{file_path} : #{response.first.class.name}") # FIXME : remove me if response.class == ClamAV::SuccessResponse
response.first.class != ClamAV::VirusResponse true
elsif response.class == ClamAV::VirusResponse
false
elsif response.class == ClamAV::ErrorResponse
raise "ClamAV ErrorResponse : #{response.error_str}"
else
raise "ClamAV unkown response #{response.class.name}"
end
end end
end end

View file

@ -1,10 +1,9 @@
- if champ.drop_down_list && champ.drop_down_list.options.any? - if champ.drop_down_list && champ.drop_down_list.options.any?
- champ_id = champ.object_id
= form.select :primary_value, = form.select :primary_value,
champ.primary_options, champ.primary_options,
{ required: champ.mandatory? }, { required: champ.mandatory? },
{ data: { "secondary-options" => champ.secondary_options, "primary-id" => champ_id } } { data: { secondary_options: champ.secondary_options } }
= form.select :secondary_value, = form.select :secondary_value,
champ.secondary_options[champ.primary_value], champ.secondary_options[champ.primary_value],
{ required: champ.mandatory? }, { required: champ.mandatory? },
{ data: { "secondary-id" => champ_id } } { data: { secondary: true } }

View file

@ -100,9 +100,6 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
page.refresh page.refresh
expect(page).to have_current_path(champs_procedure_path(Procedure.last)) expect(page).to have_current_path(champs_procedure_path(Procedure.last))
within '.footer' do
click_on 'Ajouter un champ'
end
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle') expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ' fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
expect(page).to have_content('Formulaire mis à jour') expect(page).to have_content('Formulaire mis à jour')
@ -131,9 +128,6 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
scenario 'After adding champ and file, make publication' do scenario 'After adding champ and file, make publication' do
page.refresh page.refresh
within '.footer' do
click_on 'Ajouter un champ'
end
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ' fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
expect(page).to have_content('Formulaire mis à jour') expect(page).to have_content('Formulaire mis à jour')

View file

@ -11,6 +11,8 @@ feature 'As an administrateur I edit procedure', js: true do
end end
it "Add a new champ" do it "Add a new champ" do
click_on 'Supprimer'
within '.footer' do within '.footer' do
click_on 'Ajouter un champ' click_on 'Ajouter un champ'
end end
@ -31,7 +33,6 @@ feature 'As an administrateur I edit procedure', js: true do
click_on 'Ajouter un champ' click_on 'Ajouter un champ'
click_on 'Ajouter un champ' click_on 'Ajouter un champ'
click_on 'Ajouter un champ' click_on 'Ajouter un champ'
click_on 'Ajouter un champ'
end end
expect(page).not_to have_content('Le libellé doit être rempli.') expect(page).not_to have_content('Le libellé doit être rempli.')
expect(page).not_to have_content('Modifications non sauvegardées.') expect(page).not_to have_content('Modifications non sauvegardées.')
@ -66,9 +67,6 @@ feature 'As an administrateur I edit procedure', js: true do
end end
it "Remove champs" do it "Remove champs" do
within '.footer' do
click_on 'Ajouter un champ'
end
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ' fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
expect(page).to have_content('Formulaire mis à jour') expect(page).to have_content('Formulaire mis à jour')
page.refresh page.refresh
@ -78,13 +76,10 @@ feature 'As an administrateur I edit procedure', js: true do
expect(page).not_to have_content('Supprimer') expect(page).not_to have_content('Supprimer')
page.refresh page.refresh
expect(page).not_to have_content('Supprimer') expect(page).to have_content('Supprimer', count: 1)
end end
it "Only add valid champs" do it "Only add valid champs" do
within '.footer' do
click_on 'Ajouter un champ'
end
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_description') expect(page).to have_selector('#procedure_types_de_champ_attributes_0_description')
fill_in 'procedure_types_de_champ_attributes_0_description', with: 'déscription du champ' fill_in 'procedure_types_de_champ_attributes_0_description', with: 'déscription du champ'
expect(page).to have_content('Le libellé doit être rempli.') expect(page).to have_content('Le libellé doit être rempli.')
@ -95,9 +90,6 @@ feature 'As an administrateur I edit procedure', js: true do
end end
it "Add repetition champ" do it "Add repetition champ" do
within '.footer' do
click_on 'Ajouter un champ'
end
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle') expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
select('Bloc répétable', from: 'procedure_types_de_champ_attributes_0_type_champ') select('Bloc répétable', from: 'procedure_types_de_champ_attributes_0_type_champ')
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ' fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'

View file

@ -74,7 +74,8 @@ feature 'linked dropdown lists' do
def secondary_id_for(libelle) def secondary_id_for(libelle)
primary_id = primary_id_for(libelle) primary_id = primary_id_for(libelle)
link = find("\##{primary_id}")['data-primary-id'] find("\##{primary_id}")
find("[data-secondary-id=\"#{link}\"]")['id'] .ancestor('.editable-champ')
.find("[data-secondary]")['id']
end end
end end

View file

@ -110,6 +110,40 @@ describe TagsSubstitutionConcern, type: :model do
end end
end end
context 'when the procedure has a linked drop down menus type de champ' do
let(:types_de_champ) do
[
create(:type_de_champ_linked_drop_down_list, libelle: 'libelle')
]
end
let(:template) { 'tout : --libelle--, primaire : --libelle/primaire--, secondaire : --libelle/secondaire--' }
context 'and the champ has no value' do
it { is_expected.to eq('tout : , primaire : , secondaire : ') }
end
context 'and the champ has a primary value' do
before do
c = dossier.champs.detect { |champ| champ.libelle == 'libelle' }
c.primary_value = 'primo'
c.save
end
it { is_expected.to eq('tout : primo, primaire : primo, secondaire : ') }
end
context 'and the champ has a primary and secondary value' do
before do
c = dossier.champs.detect { |champ| champ.libelle == 'libelle' }
c.primary_value = 'primo'
c.secondary_value = 'secundo'
c.save
end
it { is_expected.to eq('tout : primo / secundo, primaire : primo, secondaire : secundo') }
end
end
context 'when the dossier has a motivation' do context 'when the dossier has a motivation' do
let(:dossier) { create(:dossier, motivation: 'motivation') } let(:dossier) { create(:dossier, motivation: 'motivation') }

View file

@ -236,14 +236,15 @@ describe Dossier do
end end
describe "#text_summary" do describe "#text_summary" do
let(:procedure) { create(:procedure, libelle: "Démarche", organisation: "Organisme") } let(:service) { create(:service, nom: 'nom du service') }
let(:procedure) { create(:procedure, libelle: "Démarche", organisation: "Organisme", service: service) }
context 'when the dossier has been en_construction' do context 'when the dossier has been en_construction' do
let(:dossier) { create :dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction), en_construction_at: "31/12/2010".to_date } let(:dossier) { create :dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction), en_construction_at: "31/12/2010".to_date }
subject { dossier.text_summary } subject { dossier.text_summary }
it { is_expected.to eq("Dossier déposé le 31/12/2010 sur la démarche Démarche gérée par l'organisme Organisme") } it { is_expected.to eq("Dossier déposé le 31/12/2010 sur la démarche Démarche gérée par l'organisme nom du service") }
end end
context 'when the dossier has not been en_construction' do context 'when the dossier has not been en_construction' do
@ -251,7 +252,7 @@ describe Dossier do
subject { dossier.text_summary } subject { dossier.text_summary }
it { is_expected.to eq("Dossier en brouillon répondant à la démarche Démarche gérée par l'organisme Organisme") } it { is_expected.to eq("Dossier en brouillon répondant à la démarche Démarche gérée par l'organisme nom du service") }
end end
end end

View file

@ -4,17 +4,27 @@ describe ClamavService do
describe '.safe_file?' do describe '.safe_file?' do
let(:path_file) { '/tmp/plop.txt' } let(:path_file) { '/tmp/plop.txt' }
subject { ClamavService.safe_file? path_file } subject { ClamavService.safe_file?(path_file) }
before do before do
client = instance_double("ClamAV::Client", :execute => [ClamAV::SuccessResponse]) client = double("ClamAV::Client", execute: [response])
allow(ClamAV::Client).to receive(:new).and_return(client) allow(ClamAV::Client).to receive(:new).and_return(client)
allow(FileUtils).to receive(:chmod).with(0666, path_file).and_return(true)
end end
it 'change permission of file path' do context 'When response type is ClamAV::SuccessResponse' do
allow(FileUtils).to receive(:chmod).with(0666, path_file).and_return(true) let(:response) { ClamAV::SuccessResponse.new("OK") }
it { expect(subject).to eq(true) }
end
subject context 'When response type is ClamAV::VirusResponse' do
let(:response) { ClamAV::VirusResponse.new("KO", "VirusN4ame") }
it { expect(subject).to eq(false) }
end
context 'When response type is ClamAV::ErrorResponse' do
let(:response) { ClamAV::ErrorResponse.new("File not found") }
it { expect { subject }.to raise_error("ClamAV ErrorResponse : File not found") }
end end
end end
end end