Merge pull request #5835 from tchak/annuaire-education

Annuaire Education
This commit is contained in:
Paul Chavard 2021-01-14 18:10:17 +01:00 committed by GitHub
commit d41cef4c23
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
61 changed files with 646 additions and 4 deletions

View file

@ -46,6 +46,7 @@ gem 'http_accept_language'
gem 'iban-tools' gem 'iban-tools'
gem 'image_processing' gem 'image_processing'
gem 'jquery-rails' # Use jquery as the JavaScript library gem 'jquery-rails' # Use jquery as the JavaScript library
gem 'json_schemer'
gem 'jwt' gem 'jwt'
gem 'kaminari', '1.2.1' # Pagination gem 'kaminari', '1.2.1' # Pagination
gem 'lograge' gem 'lograge'

View file

@ -230,6 +230,8 @@ GEM
railties (>= 3.2) railties (>= 3.2)
dry-inflector (0.2.0) dry-inflector (0.2.0)
dumb_delegator (0.8.1) dumb_delegator (0.8.1)
ecma-re-validator (0.2.1)
regexp_parser (~> 1.2)
em-websocket (0.5.1) em-websocket (0.5.1)
eventmachine (>= 0.12.9) eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0) http_parser.rb (~> 0.6.0)
@ -339,6 +341,7 @@ GEM
rainbow rainbow
rubocop (>= 0.50.0) rubocop (>= 0.50.0)
sysexits (~> 1.1) sysexits (~> 1.1)
hana (1.3.7)
hashdiff (1.0.1) hashdiff (1.0.1)
hashie (4.1.0) hashie (4.1.0)
html2haml (2.2.0) html2haml (2.2.0)
@ -370,6 +373,11 @@ GEM
activesupport (>= 4.2) activesupport (>= 4.2)
aes_key_wrap aes_key_wrap
bindata bindata
json_schemer (0.2.16)
ecma-re-validator (~> 0.2)
hana (~> 1.3)
regexp_parser (~> 1.5)
uri_template (~> 0.7)
jsonapi-renderer (0.2.2) jsonapi-renderer (0.2.2)
jwt (2.2.2) jwt (2.2.2)
kaminari (1.2.1) kaminari (1.2.1)
@ -721,6 +729,7 @@ GEM
unf_ext unf_ext
unf_ext (0.0.7.7) unf_ext (0.0.7.7)
unicode-display_width (1.7.0) unicode-display_width (1.7.0)
uri_template (0.7.0)
validate_email (0.1.6) validate_email (0.1.6)
activemodel (>= 3.0) activemodel (>= 3.0)
mail (>= 2.2.5) mail (>= 2.2.5)
@ -835,6 +844,7 @@ DEPENDENCIES
iban-tools iban-tools
image_processing image_processing
jquery-rails jquery-rails
json_schemer
jwt jwt
kaminari (= 1.2.1) kaminari (= 1.2.1)
launchy launchy

View file

@ -1256,6 +1256,11 @@ enum TypeDeChamp {
""" """
address address
"""
Annuaire de léducation
"""
annuaire_education
""" """
Carte Carte
""" """

View file

@ -0,0 +1,28 @@
import React from 'react';
import { ReactQueryCacheProvider } from 'react-query';
import ComboSearch from './ComboSearch';
import { queryCache } from './shared/queryCache';
function ComboAnnuaireEducationSearch(params) {
return (
<ReactQueryCacheProvider queryCache={queryCache}>
<ComboSearch
required={params.mandatory}
hiddenFieldId={params.hiddenFieldId}
scope="annuaire-education"
minimumInputLength={3}
transformResults={(_, { records }) => records}
transformResult={({
fields: {
identifiant_de_l_etablissement: id,
nom_etablissement,
nom_commune
}
}) => [id, `${nom_etablissement}, ${nom_commune} (${id})`]}
/>
</ReactQueryCacheProvider>
);
}
export default ComboAnnuaireEducationSearch;

View file

@ -10,6 +10,7 @@ import {
ComboboxOption ComboboxOption
} from '@reach/combobox'; } from '@reach/combobox';
import '@reach/combobox/styles.css'; import '@reach/combobox/styles.css';
import { fire } from '@utils';
function defaultTransformResults(_, results) { function defaultTransformResults(_, results) {
return results; return results;
@ -39,6 +40,7 @@ function ComboSearch({
const setExternalValue = useCallback((value) => { const setExternalValue = useCallback((value) => {
if (hiddenField) { if (hiddenField) {
hiddenField.setAttribute('value', value); hiddenField.setAttribute('value', value);
fire(hiddenField, 'autosave:trigger');
} }
if (onChange) { if (onChange) {
const result = resultsMap.current[value]; const result = resultsMap.current[value];

View file

@ -2,7 +2,8 @@ import { QueryCache } from 'react-query';
import { isNumeric } from '@utils'; import { isNumeric } from '@utils';
import matchSorter from 'match-sorter'; import matchSorter from 'match-sorter';
const { api_geo_url, api_adresse_url } = gon.autocomplete || {}; const { api_geo_url, api_adresse_url, api_education_url } =
gon.autocomplete || {};
export const queryCache = new QueryCache({ export const queryCache = new QueryCache({
defaultConfig: { defaultConfig: {
@ -13,8 +14,11 @@ export const queryCache = new QueryCache({
}); });
function buildURL(scope, term) { function buildURL(scope, term) {
term = encodeURIComponent(term);
if (scope === 'adresse') { if (scope === 'adresse') {
return `${api_adresse_url}/search?q=${term}&limit=5`; return `${api_adresse_url}/search?q=${term}&limit=5`;
} else if (scope === 'annuaire-education') {
return `${api_education_url}/search?dataset=fr-en-annuaire-education&q=${term}&rows=5`;
} else if (isNumeric(term)) { } else if (isNumeric(term)) {
const code = term.padStart(2, '0'); const code = term.padStart(2, '0');
return `${api_geo_url}/${scope}?code=${code}&limit=5`; return `${api_geo_url}/${scope}?code=${code}&limit=5`;

View file

@ -0,0 +1,5 @@
import Loadable from '../components/Loadable';
export default Loadable(() =>
import('../components/ComboAnnuaireEducationSearch')
);

View file

@ -0,0 +1,16 @@
class AnnuaireEducationUpdateJob < ApplicationJob
def perform(champ)
search_term = champ.value
if search_term.present?
data = ApiEducation::AnnuaireEducationAdapter.new(search_term).to_params
if data.present?
champ.data = data
else
champ.value = nil
end
champ.save!
end
end
end

View file

@ -0,0 +1,36 @@
require 'json_schemer'
class ApiEducation::AnnuaireEducationAdapter
class InvalidSchemaError < ::StandardError
def initialize(errors)
super(errors.map(&:to_json).join("\n"))
end
end
def initialize(search_term)
@search_term = search_term
end
def to_params
record = data_source[:records].first
if record.present?
properties = record[:fields].merge({ geometry: record[:geometry] }).deep_stringify_keys
if schemer.valid?(properties)
properties
else
errors = schemer.validate(properties).to_a
raise InvalidSchemaError.new(errors)
end
end
end
private
def data_source
@data_source ||= JSON.parse(ApiEducation::API.search_annuaire_education(@search_term), symbolize_names: true)
end
def schemer
@schemer ||= JSONSchemer.schema(Rails.root.join('app/schemas/etablissement-annuaire-education.json'))
end
end

View file

@ -0,0 +1,22 @@
class ApiEducation::API
class ResourceNotFound < StandardError
end
def self.search_annuaire_education(search_term)
call([API_EDUCATION_URL, 'search'].join('/'), 'fr-en-annuaire-education', { q: search_term })
end
private
def self.call(url, dataset, params)
response = Typhoeus.get(url, params: { rows: 1, dataset: dataset }.merge(params))
if response.success?
response.body
else
message = response.code == 0 ? response.return_message : response.code.to_s
Rails.logger.error "[ApiEducation] Error on #{url}: #{message}"
raise ResourceNotFound
end
end
end

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -0,0 +1,35 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null
# row :integer
# type :string
# value :string
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# parent_id :bigint
# type_de_champ_id :integer
#
class Champs::AnnuaireEducationChamp < Champs::TextChamp
before_save :cleanup_if_empty
after_update_commit :fetch_data
private
def cleanup_if_empty
if value_changed?
self.data = nil
end
end
def fetch_data
if value.present? && data.nil?
AnnuaireEducationUpdateJob.perform_later(self)
end
end
end

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -3,6 +3,7 @@
# Table name: champs # Table name: champs
# #
# id :integer not null, primary key # id :integer not null, primary key
# data :jsonb
# private :boolean default(FALSE), not null # private :boolean default(FALSE), not null
# row :integer # row :integer
# type :string # type :string

View file

@ -47,7 +47,8 @@ class TypeDeChamp < ApplicationRecord
carte: 'carte', carte: 'carte',
repetition: 'repetition', repetition: 'repetition',
titre_identite: 'titre_identite', titre_identite: 'titre_identite',
iban: 'iban' iban: 'iban',
annuaire_education: 'annuaire_education'
} }
belongs_to :revision, class_name: 'ProcedureRevision', optional: true belongs_to :revision, class_name: 'ProcedureRevision', optional: true

View file

@ -0,0 +1,2 @@
class TypesDeChamp::AnnuaireEducationTypeDeChamp < TypesDeChamp::TextTypeDeChamp
end

View file

@ -0,0 +1,185 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://demarches-simplifiees.fr/etablissement-annuaire-education.schema.json",
"title": "Établissement annuaire education",
"type": "object",
"properties": {
"date_ouverture": {
"type": "date"
},
"ministere_tutelle": {
"type": "string"
},
"statut_public_prive": {
"type": "string"
},
"libelle_region": {
"type": "string"
},
"telephone": {
"type": "string"
},
"date_maj_ligne": {
"type": "string"
},
"libelle_nature": {
"type": "string"
},
"etat": {
"type": "string"
},
"type_etablissement": {
"type": "string"
},
"identifiant_de_l_etablissement": {
"type": "string"
},
"code_region": {
"type": "string"
},
"siren_siret": {
"type": "string"
},
"mail": {
"type": "string"
},
"nom_circonscription": {
"type": "string"
},
"nom_commune": {
"type": "string"
},
"adresse_3": {
"type": "string"
},
"adresse_1": {
"type": "string"
},
"nombre_d_eleves": {
"type": "integer",
"minimum": 0
},
"code_commune": {
"type": "string"
},
"code_departement": {
"type": "string"
},
"precision_localisation": {
"type": "string"
},
"type_contrat_prive": {
"type": "string"
},
"code_postal": {
"type": "string"
},
"libelle_departement": {
"type": "string"
},
"code_academie": {
"type": "string"
},
"libelle_academie": {
"type": "string"
},
"nom_etablissement": {
"type": "string"
},
"epsg_origine": {
"type": "string"
},
"pial": {
"type": "string"
},
"code_nature": {
"type": "integer",
"minimum": 0
},
"code_type_contrat_prive": {
"type": "integer",
"minimum": 0
},
"ecole_elementaire": {
"enum": [0, 1]
},
"hebergement": {
"enum": [0, 1]
},
"rpi_concentre": {
"enum": [0, 1]
},
"ulis": {
"enum": [0, 1]
},
"restauration": {
"enum": [0, 1]
},
"ecole_maternelle": {
"enum": [0, 1]
},
"multi_uai": {
"enum": [0, 1]
},
"section_theatre": {
"enum": ["0", "1"]
},
"section_internationale": {
"enum": ["0", "1"]
},
"post_bac": {
"enum": ["0", "1"]
},
"section_cinema": {
"enum": ["0", "1"]
},
"section_europeenne": {
"enum": ["0", "1"]
},
"lycee_des_metiers": {
"enum": ["0", "1"]
},
"voie_professionnelle": {
"enum": ["0", "1"]
},
"lycee_militaire": {
"enum": ["0", "1"]
},
"section_sport": {
"enum": ["0", "1"]
},
"voie_technologique": {
"enum": ["0", "1"]
},
"section_arts": {
"enum": ["0", "1"]
},
"lycee_agricole": {
"enum": ["0", "1"]
},
"apprentissage": {
"enum": ["0", "1"]
},
"voie_generale": {
"enum": ["0", "1"]
}
},
"required": [
"identifiant_de_l_etablissement",
"nom_etablissement",
"statut_public_prive",
"type_contrat_prive",
"nom_commune",
"code_commune",
"nombre_d_eleves",
"siren_siret",
"libelle_academie",
"code_academie",
"libelle_nature",
"code_nature",
"adresse_1",
"code_postal",
"libelle_region",
"code_region"
]
}

View file

@ -0,0 +1,54 @@
- if champ.data.blank?
= champ.to_s
- else
%table.table.vertical.dossier-champs
%tbody
%tr
%th.libelle Nom de létablissement :
%td= champ.data['nom_etablissement']
%tr
%th.libelle Lidentifiant de letablissement :
%td= champ.data['identifiant_de_l_etablissement']
%tr
%th.libelle SIREN/SIRET :
%td= champ.data['siren_siret']
%tr
%th.libelle Commune :
%td= "#{champ.data['nom_commune']} (#{champ.data['code_commune']})"
%tr
%th.libelle Academie :
%td= "#{champ.data['libelle_academie']} (#{champ.data['code_academie']})"
%tr
%th.libelle Nature de létablissement :
%td= "#{champ.data['libelle_nature']} (#{champ.data['code_nature']})"
- if champ.data['type_contrat_prive'] != 'SANS OBJET'
%tr
%th.libelle Type de contrat privé :
%td= champ.data['type_contrat_prive']
%tr
%th.libelle Nombre délèves :
%td= champ.data['nombre_d_eleves']
%tr
%th.libelle Adresse :
%td
= champ.data['adresse_1']
%br
= "#{champ.data['code_postal']} #{champ.data['nom_commune']}"
%br
= "#{champ.data['libelle_region']} (#{champ.data['code_region']})"
- if champ.data['telephone'].present?
%tr
%th.libelle Téléphone :
%td= champ.data['telephone']
- if champ.data['mail'].present?
%tr
%th.libelle Email :
%td= champ.data['mail']
- if champ.data['web'].present?
%tr
%th.libelle Site internet :
%td= champ.data['web']

View file

@ -34,6 +34,8 @@
= render partial: "shared/champs/iban/show", locals: { champ: c } = render partial: "shared/champs/iban/show", locals: { champ: c }
- when TypeDeChamp.type_champs.fetch(:textarea) - when TypeDeChamp.type_champs.fetch(:textarea)
= render partial: "shared/champs/textarea/show", locals: { champ: c } = render partial: "shared/champs/textarea/show", locals: { champ: c }
- when TypeDeChamp.type_champs.fetch(:annuaire_education)
= render partial: "shared/champs/annuaire_education/show", locals: { champ: c }
- when TypeDeChamp.type_champs.fetch(:date) - when TypeDeChamp.type_champs.fetch(:date)
= c.to_s = c.to_s
- when TypeDeChamp.type_champs.fetch(:datetime) - when TypeDeChamp.type_champs.fetch(:datetime)

View file

@ -0,0 +1,3 @@
- hidden_field_id = SecureRandom.uuid
= form.hidden_field :value, { data: { uuid: hidden_field_id } }
= react_component("ComboAnnuaireEducationSearch", mandatory: champ.mandatory?, hiddenFieldId: hidden_field_id )

View file

@ -112,6 +112,9 @@ UNIVERSIGN_USERPWD=""
API_ADRESSE_URL="https://api-adresse.data.gouv.fr" API_ADRESSE_URL="https://api-adresse.data.gouv.fr"
API_GEO_URL="https://geo.api.gouv.fr" API_GEO_URL="https://geo.api.gouv.fr"
# API Education
API_EDUCATION_URL="https://data.education.gouv.fr/api/records/1.0"
# Modifier le nb de tentatives de relance de job si echec # Modifier le nb de tentatives de relance de job si echec
# MAX_ATTEMPTS_JOBS=25 # MAX_ATTEMPTS_JOBS=25
# MAX_ATTEMPTS_API_ENTREPRISE_JOBS=5 # MAX_ATTEMPTS_API_ENTREPRISE_JOBS=5

View file

@ -9,7 +9,7 @@ Rails.application.config.content_security_policy do |policy|
# c'est trop compliqué pour être rectifié immédiatement (et sans valeur ajoutée: # c'est trop compliqué pour être rectifié immédiatement (et sans valeur ajoutée:
# c'est hardcodé dans les vues, donc pas injectable). # c'est hardcodé dans les vues, donc pas injectable).
policy.style_src :self, "*.crisp.chat", "crisp.chat", 'cdn.jsdelivr.net', 'maxcdn.bootstrapcdn.com', :unsafe_inline policy.style_src :self, "*.crisp.chat", "crisp.chat", 'cdn.jsdelivr.net', 'maxcdn.bootstrapcdn.com', :unsafe_inline
policy.connect_src :self, "wss://*.crisp.chat", "*.crisp.chat", "*.demarches-simplifiees.fr", "in-automate.sendinblue.com", "app.franceconnect.gouv.fr", "sentry.io", "geo.api.gouv.fr", "api-adresse.data.gouv.fr", "openmaptiles.geo.data.gouv.fr", "openmaptiles.github.io", "tiles.geo.api.gouv.fr", "wxs.ign.fr" policy.connect_src :self, "wss://*.crisp.chat", "*.crisp.chat", "*.demarches-simplifiees.fr", "in-automate.sendinblue.com", "app.franceconnect.gouv.fr", "sentry.io", "geo.api.gouv.fr", "api-adresse.data.gouv.fr", "openmaptiles.geo.data.gouv.fr", "openmaptiles.github.io", "tiles.geo.api.gouv.fr", "wxs.ign.fr", "data.education.gouv.fr"
# Pour tout le reste, par défaut on accepte uniquement ce qui vient de chez nous # Pour tout le reste, par défaut on accepte uniquement ce qui vient de chez nous
# et dans la notification on inclue la source de l'erreur # et dans la notification on inclue la source de l'erreur
policy.default_src :self, :data, :blob, :report_sample, "fonts.gstatic.com", "in-automate.sendinblue.com", "player.vimeo.com", "app.franceconnect.gouv.fr", "sentry.io", "static.demarches-simplifiees.fr", "*.crisp.chat", "crisp.chat", "*.crisp.help", "*.sibautomation.com", "sibautomation.com", "data" policy.default_src :self, :data, :blob, :report_sample, "fonts.gstatic.com", "in-automate.sendinblue.com", "player.vimeo.com", "app.franceconnect.gouv.fr", "sentry.io", "static.demarches-simplifiees.fr", "*.crisp.chat", "crisp.chat", "*.crisp.help", "*.sibautomation.com", "sibautomation.com", "data"

View file

@ -2,6 +2,7 @@
# API URLs # API URLs
API_CARTO_URL = ENV.fetch("API_CARTO_URL", "https://sandbox.geo.api.gouv.fr/apicarto") API_CARTO_URL = ENV.fetch("API_CARTO_URL", "https://sandbox.geo.api.gouv.fr/apicarto")
API_ENTREPRISE_URL = ENV.fetch("API_ENTREPRISE_URL", "https://entreprise.api.gouv.fr/v2") API_ENTREPRISE_URL = ENV.fetch("API_ENTREPRISE_URL", "https://entreprise.api.gouv.fr/v2")
API_EDUCATION_URL = ENV.fetch("API_EDUCATION_URL", "https://data.education.gouv.fr/api/records/1.0")
HELPSCOUT_API_URL = ENV.fetch("HELPSCOUT_API_URL", "https://api.helpscout.net/v2") HELPSCOUT_API_URL = ENV.fetch("HELPSCOUT_API_URL", "https://api.helpscout.net/v2")
PIPEDRIVE_API_URL = ENV.fetch("PIPEDRIVE_API_URL", "https://api.pipedrive.com/v1") PIPEDRIVE_API_URL = ENV.fetch("PIPEDRIVE_API_URL", "https://api.pipedrive.com/v1")
SENDINBLUE_API_URL = ENV.fetch("SENDINBLUE_API_URL", "https://in-automate.sendinblue.com/api/v2") SENDINBLUE_API_URL = ENV.fetch("SENDINBLUE_API_URL", "https://in-automate.sendinblue.com/api/v2")

View file

@ -35,3 +35,4 @@ fr:
repetition: 'Bloc répétable' repetition: 'Bloc répétable'
titre_identite: 'Titre identité' titre_identite: 'Titre identité'
iban: 'Iban' iban: 'Iban'
annuaire_education: 'Annuaire de léducation'

View file

@ -60,6 +60,7 @@ defaults: &defaults
autocomplete: autocomplete:
api_geo_url: <%= ENV['API_GEO_URL'] %> api_geo_url: <%= ENV['API_GEO_URL'] %>
api_adresse_url: <%= ENV['API_ADRESSE_URL'] %> api_adresse_url: <%= ENV['API_ADRESSE_URL'] %>
api_education_url: <%= ENV['API_EDUCATION_URL'] %>

View file

@ -0,0 +1,5 @@
class AddDataToChamps < ActiveRecord::Migration[6.0]
def change
add_column :champs, :data, :jsonb
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_11_17_122923) do ActiveRecord::Schema.define(version: 2021_01_13_150013) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -143,6 +143,7 @@ ActiveRecord::Schema.define(version: 2020_11_17_122923) do
t.integer "etablissement_id" t.integer "etablissement_id"
t.bigint "parent_id" t.bigint "parent_id"
t.integer "row" t.integer "row"
t.jsonb "data"
t.index ["dossier_id"], name: "index_champs_on_dossier_id" t.index ["dossier_id"], name: "index_champs_on_dossier_id"
t.index ["parent_id"], name: "index_champs_on_parent_id" t.index ["parent_id"], name: "index_champs_on_parent_id"
t.index ["private"], name: "index_champs_on_private" t.index ["private"], name: "index_champs_on_private"

View file

@ -159,6 +159,10 @@ FactoryBot.define do
type_de_champ { association :type_de_champ_iban, procedure: dossier.procedure } type_de_champ { association :type_de_champ_iban, procedure: dossier.procedure }
end end
factory :champ_annuaire_education, class: 'Champs::AnnuaireEducationChamp' do
type_de_champ { association :type_de_champ_annuaire_education, procedure: dossier.procedure }
end
factory :champ_siret, class: 'Champs::SiretChamp' do factory :champ_siret, class: 'Champs::SiretChamp' do
association :type_de_champ, factory: [:type_de_champ_siret] association :type_de_champ, factory: [:type_de_champ_siret]
association :etablissement, factory: [:etablissement] association :etablissement, factory: [:etablissement]

View file

@ -142,6 +142,9 @@ FactoryBot.define do
factory :type_de_champ_iban do factory :type_de_champ_iban do
type_champ { TypeDeChamp.type_champs.fetch(:iban) } type_champ { TypeDeChamp.type_champs.fetch(:iban) }
end end
factory :type_de_champ_annuaire_education do
type_champ { TypeDeChamp.type_champs.fetch(:annuaire_education) }
end
factory :type_de_champ_carte do factory :type_de_champ_carte do
type_champ { TypeDeChamp.type_champs.fetch(:carte) } type_champ { TypeDeChamp.type_champs.fetch(:carte) }
end end

View file

@ -0,0 +1,91 @@
{
"nhits": 1,
"parameters": {
"dataset": "fr-en-annuaire-education",
"q": "0050009H",
"timezone": "UTC",
"rows": 1,
"start": 0,
"format": "json"
},
"records": [
{
"datasetid": "fr-en-annuaire-education",
"recordid": "0fd6a58dc6c7e5346e3bd62ada6216f476ebeeac",
"fields": {
"section_arts": "0",
"lycee_agricole": "0",
"apprentissage": "1",
"voie_generale": "0",
"ministere_tutelle": "MINISTERE DE L'EDUCATION NATIONALE",
"statut_public_prive": "Public",
"libelle_region": "Provence-Alpes-Côte d'Azur",
"telephone": "04 92 56 56 10",
"date_maj_ligne": "2021-01-12",
"hebergement": 1,
"libelle_nature": "LYCEE PROFESSIONNEL",
"lycee_militaire": "0",
"section_sport": "0",
"voie_technologique": "0",
"fiche_onisep": "http://geolocalisation.onisep.fr/05-hautes-alpes/gap/lycee/lycee-professionnel-sevigne.html",
"etat": "OUVERT",
"web": "www.lyc-sevigne.ac-aix-marseille.fr",
"rpi_concentre": 0,
"identifiant_de_l_etablissement": "0050009H",
"code_region": "93",
"ulis": 1,
"restauration": 1,
"code_departement": "05",
"date_ouverture": "1965-05-01",
"voie_professionnelle": "1",
"greta": "1",
"coordy_origine": 6389542.2,
"siren_siret": "19050009000013",
"mail": "ce.0050009H@ac-aix-marseille.fr",
"type_contrat_prive": "SANS OBJET",
"nom_commune": "Gap",
"segpa": "0",
"adresse_3": "05000 GAP",
"fax": "04 92 56 56 30",
"type_etablissement": "Lycée",
"nombre_d_eleves": 451,
"libelle_zone_animation_pedagogique": "GAP",
"code_commune": "05061",
"latitude": 44.562179100043984,
"section_theatre": "0",
"section_internationale": "0",
"post_bac": "1",
"precision_localisation": "Numéro de rue",
"multi_uai": 0,
"code_postal": "05000",
"libelle_departement": "Hautes-Alpes",
"section_cinema": "0",
"section_europeenne": "1",
"libelle_academie": "Aix-Marseille",
"longitude": 6.0746303437466915,
"code_zone_animation_pedagogique": "02104",
"code_academie": "02",
"lycee_des_metiers": "1",
"epsg_origine": "EPSG:2154",
"adresse_1": "6 rue Jean Macé",
"nom_etablissement": "Lycée professionnel Sévigné",
"pial": "0050009H",
"code_nature": 320,
"position": [
44.562179100043984,
6.0746303437466915
],
"code_type_contrat_prive": 99,
"coordx_origine": 944110.8
},
"geometry": {
"type": "Point",
"coordinates": [
6.0746303437466915,
44.562179100043984
]
},
"record_timestamp": "2021-01-12T18:03:00+00:00"
}
]
}

View file

@ -0,0 +1,58 @@
{
"nhits": 1,
"parameters": {
"dataset": "fr-en-annuaire-education",
"q": "0050009H",
"timezone": "UTC",
"rows": 1,
"start": 0,
"format": "json"
},
"records": [
{
"datasetid": "fr-en-annuaire-education",
"recordid": "0fd6a58dc6c7e5346e3bd62ada6216f476ebeeac",
"fields": {
"section_arts": "0",
"lycee_agricole": "0",
"apprentissage": "1",
"voie_generale": "0",
"ministere_tutelle": "MINISTERE DE L'EDUCATION NATIONALE",
"statut_public_prive": "Public",
"libelle_region": "Provence-Alpes-Côte d'Azur",
"telephone": "04 92 56 56 10",
"date_maj_ligne": "2021-01-12",
"hebergement": 1,
"libelle_nature": "LYCEE PROFESSIONNEL",
"lycee_militaire": "0",
"section_sport": "0",
"voie_technologique": "0",
"fiche_onisep": "http://geolocalisation.onisep.fr/05-hautes-alpes/gap/lycee/lycee-professionnel-sevigne.html",
"etat": "OUVERT",
"web": "www.lyc-sevigne.ac-aix-marseille.fr",
"rpi_concentre": 0,
"code_region": "93",
"ulis": 1,
"restauration": 1,
"code_departement": "05",
"date_ouverture": "1965-05-01",
"voie_professionnelle": "1",
"greta": "1",
"coordy_origine": 6389542.2,
"siren_siret": "19050009000013",
"mail": "ce.0050009H@ac-aix-marseille.fr",
"type_contrat_prive": "SANS OBJET",
"nom_commune": "Gap",
"segpa": "0"
},
"geometry": {
"type": "Point",
"coordinates": [
6.0746303437466915,
44.562179100043984
]
},
"record_timestamp": "2021-01-12T18:03:00+00:00"
}
]
}

View file

@ -0,0 +1,29 @@
describe ApiEducation::AnnuaireEducationAdapter do
let(:search_term) { '0050009H' }
let(:adapter) { described_class.new(search_term) }
subject { adapter.to_params }
before do
stub_request(:get, /https:\/\/data.education.gouv.fr\/api\/records\/1.0/)
.to_return(body: body, status: status)
end
context "when responds with valid schema" do
let(:body) { File.read('spec/fixtures/files/api_education/annuaire_education.json') }
let(:status) { 200 }
it '#to_params return vaid hash' do
expect(subject).to be_an_instance_of(Hash)
expect(subject['identifiant_de_l_etablissement']).to eq(search_term)
end
end
context "when responds with invalid schema" do
let(:body) { File.read('spec/fixtures/files/api_education/annuaire_education_invalid.json') }
let(:status) { 200 }
it '#to_params raise exception' do
expect { subject }.to raise_exception(ApiEducation::AnnuaireEducationAdapter::InvalidSchemaError)
end
end
end

View file

@ -75,6 +75,7 @@ describe ProcedureExportService do
"carte", "carte",
"titre_identite", "titre_identite",
"iban", "iban",
"annuaire_education",
"text" "text"
] ]
end end
@ -160,6 +161,7 @@ describe ProcedureExportService do
"carte", "carte",
"titre_identite", "titre_identite",
"iban", "iban",
"annuaire_education",
"text" "text"
] ]
end end
@ -241,6 +243,7 @@ describe ProcedureExportService do
"carte", "carte",
"titre_identite", "titre_identite",
"iban", "iban",
"annuaire_education",
"text" "text"
] ]
end end