Merge pull request #7489 from betagouv/opendata-export-demarches-desc
Permet la génération json du descriptif des démarches publiques Une prochaine PR permettra la publication de ce fichier json dans data.gouv.fr
This commit is contained in:
commit
8788ee70b9
9 changed files with 194 additions and 5 deletions
|
@ -72,7 +72,7 @@ class API::V2::Schema < GraphQL::Schema
|
||||||
|
|
||||||
use GraphQL::Execution::Interpreter
|
use GraphQL::Execution::Interpreter
|
||||||
use GraphQL::Analysis::AST
|
use GraphQL::Analysis::AST
|
||||||
use GraphQL::Schema::Timeout, max_seconds: 10
|
use GraphQL::Schema::Timeout, max_seconds: 30
|
||||||
use GraphQL::Batch
|
use GraphQL::Batch
|
||||||
use GraphQL::Backtrace
|
use GraphQL::Backtrace
|
||||||
|
|
||||||
|
|
|
@ -581,7 +581,7 @@ type Demarche {
|
||||||
number: Int!
|
number: Int!
|
||||||
publishedRevision: Revision
|
publishedRevision: Revision
|
||||||
revisions: [Revision!]!
|
revisions: [Revision!]!
|
||||||
service: Service!
|
service: Service
|
||||||
|
|
||||||
"""
|
"""
|
||||||
État de la démarche.
|
État de la démarche.
|
||||||
|
@ -600,6 +600,8 @@ Ceci est une version abrégée du type `Demarche`, qui n’expose que les métad
|
||||||
Cela évite l’accès récursif aux dossiers.
|
Cela évite l’accès récursif aux dossiers.
|
||||||
"""
|
"""
|
||||||
type DemarcheDescriptor {
|
type DemarcheDescriptor {
|
||||||
|
cadreJuridique: String
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Date de la création.
|
Date de la création.
|
||||||
"""
|
"""
|
||||||
|
@ -629,6 +631,7 @@ type DemarcheDescriptor {
|
||||||
Pour une démarche déclarative, état cible des dossiers à valider automatiquement
|
Pour une démarche déclarative, état cible des dossiers à valider automatiquement
|
||||||
"""
|
"""
|
||||||
declarative: DossierDeclarativeState
|
declarative: DossierDeclarativeState
|
||||||
|
deliberation: String
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Description de la démarche.
|
Description de la démarche.
|
||||||
|
@ -641,7 +644,7 @@ type DemarcheDescriptor {
|
||||||
"""
|
"""
|
||||||
number: Int!
|
number: Int!
|
||||||
revision: Revision!
|
revision: Revision!
|
||||||
service: Service!
|
service: Service
|
||||||
|
|
||||||
"""
|
"""
|
||||||
État de la démarche.
|
État de la démarche.
|
||||||
|
|
12
app/graphql/types/base_field.rb
Normal file
12
app/graphql/types/base_field.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
module Types
|
||||||
|
class BaseField < GraphQL::Schema::Field
|
||||||
|
def initialize(*args, internal: false, **kwargs, &block)
|
||||||
|
@internal = internal
|
||||||
|
super(*args, **kwargs, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def visible?(ctx)
|
||||||
|
super && (@internal ? ctx[:internal_use] : true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,5 +1,6 @@
|
||||||
module Types
|
module Types
|
||||||
class DemarcheDescriptorType < Types::BaseObject
|
class DemarcheDescriptorType < Types::BaseObject
|
||||||
|
field_class BaseField
|
||||||
description "Une démarche (métadonnées)
|
description "Une démarche (métadonnées)
|
||||||
Ceci est une version abrégée du type `Demarche`, qui n’expose que les métadonnées.
|
Ceci est une version abrégée du type `Demarche`, qui n’expose que les métadonnées.
|
||||||
Cela évite l’accès récursif aux dossiers."
|
Cela évite l’accès récursif aux dossiers."
|
||||||
|
@ -18,7 +19,12 @@ Cela évite l’accès récursif aux dossiers."
|
||||||
field :date_fermeture, GraphQL::Types::ISO8601DateTime, "Date de la fermeture.", null: true
|
field :date_fermeture, GraphQL::Types::ISO8601DateTime, "Date de la fermeture.", null: true
|
||||||
|
|
||||||
field :revision, Types::RevisionType, null: false
|
field :revision, Types::RevisionType, null: false
|
||||||
field :service, Types::ServiceType, null: false
|
field :service, Types::ServiceType, null: true
|
||||||
|
|
||||||
|
field :cadre_juridique, String, null: true
|
||||||
|
field :deliberation, String, null: true
|
||||||
|
|
||||||
|
field :dossiers_count, Int, null: false, internal: true
|
||||||
|
|
||||||
def service
|
def service
|
||||||
Loaders::Record.for(Service).load(procedure.service_id)
|
Loaders::Record.for(Service).load(procedure.service_id)
|
||||||
|
@ -28,10 +34,22 @@ Cela évite l’accès récursif aux dossiers."
|
||||||
object.is_a?(ProcedureRevision) ? object : object.active_revision
|
object.is_a?(ProcedureRevision) ? object : object.active_revision
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def dossiers_count
|
||||||
|
object.dossiers.count
|
||||||
|
end
|
||||||
|
|
||||||
|
def deliberation
|
||||||
|
Rails.application.routes.url_helpers.url_for(procedure.deliberation) if procedure.deliberation.attached?
|
||||||
|
end
|
||||||
|
|
||||||
def state
|
def state
|
||||||
procedure.aasm.current_state
|
procedure.aasm.current_state
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cadre_juridique
|
||||||
|
procedure.cadre_juridique
|
||||||
|
end
|
||||||
|
|
||||||
def number
|
def number
|
||||||
procedure.id
|
procedure.id
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,7 +30,7 @@ module Types
|
||||||
field :date_fermeture, GraphQL::Types::ISO8601DateTime, "Date de la fermeture.", null: true, method: :closed_at
|
field :date_fermeture, GraphQL::Types::ISO8601DateTime, "Date de la fermeture.", null: true, method: :closed_at
|
||||||
|
|
||||||
field :groupe_instructeurs, [Types::GroupeInstructeurType], null: false
|
field :groupe_instructeurs, [Types::GroupeInstructeurType], null: false
|
||||||
field :service, Types::ServiceType, null: false
|
field :service, Types::ServiceType, null: true
|
||||||
|
|
||||||
field :dossiers, Types::DossierType.connection_type, "Liste de tous les dossiers d’une démarche.", null: false do
|
field :dossiers, Types::DossierType.connection_type, "Liste de tous les dossiers d’une démarche.", null: false do
|
||||||
argument :order, Types::Order, default_value: :asc, required: false, description: "L’ordre des dossiers."
|
argument :order, Types::Order, default_value: :asc, required: false, description: "L’ordre des dossiers."
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
module Types
|
module Types
|
||||||
class QueryType < Types::BaseObject
|
class QueryType < Types::BaseObject
|
||||||
|
field_class BaseField
|
||||||
|
|
||||||
field :demarche, DemarcheType, null: false, description: "Informations concernant une démarche." do
|
field :demarche, DemarcheType, null: false, description: "Informations concernant une démarche." do
|
||||||
argument :number, Int, "Numéro de la démarche.", required: true
|
argument :number, Int, "Numéro de la démarche.", required: true
|
||||||
end
|
end
|
||||||
|
@ -12,6 +14,12 @@ module Types
|
||||||
argument :number, Int, "Numéro du groupe instructeur.", required: true
|
argument :number, Int, "Numéro du groupe instructeur.", required: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
field :demarches_publiques, DemarcheDescriptorType.connection_type, null: false, internal: true, max_page_size: 30
|
||||||
|
|
||||||
|
def demarches_publiques
|
||||||
|
Procedure.opendata
|
||||||
|
end
|
||||||
|
|
||||||
def demarche(number:)
|
def demarche(number:)
|
||||||
Procedure.for_api_v2.find(number)
|
Procedure.for_api_v2.find(number)
|
||||||
rescue => e
|
rescue => e
|
||||||
|
|
|
@ -219,6 +219,7 @@ class Procedure < ApplicationRecord
|
||||||
scope :brouillons, -> { where(aasm_state: :brouillon) }
|
scope :brouillons, -> { where(aasm_state: :brouillon) }
|
||||||
scope :publiees, -> { where(aasm_state: :publiee) }
|
scope :publiees, -> { where(aasm_state: :publiee) }
|
||||||
scope :closes, -> { where(aasm_state: [:close, :depubliee]) }
|
scope :closes, -> { where(aasm_state: [:close, :depubliee]) }
|
||||||
|
scope :opendata, -> { where(opendata: true) }
|
||||||
scope :publiees_ou_closes, -> { where(aasm_state: [:publiee, :close, :depubliee]) }
|
scope :publiees_ou_closes, -> { where(aasm_state: [:publiee, :close, :depubliee]) }
|
||||||
scope :by_libelle, -> { order(libelle: :asc) }
|
scope :by_libelle, -> { order(libelle: :asc) }
|
||||||
scope :created_during, -> (range) { where(created_at: range) }
|
scope :created_during, -> (range) { where(created_at: range) }
|
||||||
|
|
100
app/services/demarches_publiques_export_service.rb
Normal file
100
app/services/demarches_publiques_export_service.rb
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
class DemarchesPubliquesExportService
|
||||||
|
attr_reader :io
|
||||||
|
def initialize(io)
|
||||||
|
@io = io
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
end_cursor = nil
|
||||||
|
first = true
|
||||||
|
write_array_opening
|
||||||
|
loop do
|
||||||
|
write_demarches_separator if !first
|
||||||
|
execute_query(cursor: end_cursor)
|
||||||
|
end_cursor = last_cursor
|
||||||
|
io.write(jsonify(demarches))
|
||||||
|
first = false
|
||||||
|
break if !has_next_page?
|
||||||
|
end
|
||||||
|
write_array_closing
|
||||||
|
io.close
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def execute_query(cursor: nil)
|
||||||
|
result = API::V2::Schema.execute(query, variables: { cursor: cursor }, context: { internal_use: true })
|
||||||
|
raise DemarchesPubliquesExportService::Error.new(result["errors"]) if result["errors"]
|
||||||
|
@graphql_data = result["data"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def query
|
||||||
|
"query($cursor: String) {
|
||||||
|
demarchesPubliques(after: $cursor) {
|
||||||
|
pageInfo {
|
||||||
|
endCursor
|
||||||
|
hasNextPage
|
||||||
|
}
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
number
|
||||||
|
title
|
||||||
|
description
|
||||||
|
datePublication
|
||||||
|
service { nom organisme typeOrganisme }
|
||||||
|
cadreJuridique
|
||||||
|
deliberation
|
||||||
|
dossiersCount
|
||||||
|
revision {
|
||||||
|
champDescriptors {
|
||||||
|
type
|
||||||
|
label
|
||||||
|
description
|
||||||
|
required
|
||||||
|
options
|
||||||
|
champDescriptors {
|
||||||
|
type
|
||||||
|
label
|
||||||
|
description
|
||||||
|
required
|
||||||
|
options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def last_cursor
|
||||||
|
@graphql_data["demarchesPubliques"]["pageInfo"]["endCursor"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_next_page?
|
||||||
|
@graphql_data["demarchesPubliques"]["pageInfo"]["hasNextPage"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def demarches
|
||||||
|
@graphql_data["demarchesPubliques"]["edges"].map { |edge| edge["node"] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def jsonify(demarches)
|
||||||
|
demarches.map(&:to_json).join(',')
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_array_opening
|
||||||
|
io.write('[')
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_array_closing
|
||||||
|
io.write(']')
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_demarches_separator
|
||||||
|
io.write(',')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class DemarchesPubliquesExportService::Error < StandardError
|
||||||
|
end
|
47
spec/services/demarches_publiques_export_service_spec.rb
Normal file
47
spec/services/demarches_publiques_export_service_spec.rb
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
describe DemarchesPubliquesExportService do
|
||||||
|
let(:procedure) { create(:procedure, :published, :with_service, :with_type_de_champ) }
|
||||||
|
let!(:dossier) { create(:dossier, procedure: procedure) }
|
||||||
|
let(:io) { StringIO.new }
|
||||||
|
|
||||||
|
describe 'call' do
|
||||||
|
it 'generate json for all closed procedures' do
|
||||||
|
expected_result = {
|
||||||
|
number: procedure.id,
|
||||||
|
title: procedure.libelle,
|
||||||
|
description: "Demande de subvention à l'intention des associations",
|
||||||
|
service: {
|
||||||
|
nom: procedure.service.nom,
|
||||||
|
organisme: "organisme",
|
||||||
|
typeOrganisme: "association"
|
||||||
|
},
|
||||||
|
cadreJuridique: "un cadre juridique important",
|
||||||
|
deliberation: nil,
|
||||||
|
datePublication: procedure.published_at.iso8601,
|
||||||
|
dossiersCount: 1,
|
||||||
|
revision: {
|
||||||
|
champDescriptors: [
|
||||||
|
{
|
||||||
|
description: procedure.types_de_champ.first.description,
|
||||||
|
label: procedure.types_de_champ.first.libelle,
|
||||||
|
options: nil,
|
||||||
|
required: false,
|
||||||
|
type: "text",
|
||||||
|
champDescriptors: nil
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DemarchesPubliquesExportService.new(io).call
|
||||||
|
expect(JSON.parse(io.string)[0]
|
||||||
|
.deep_symbolize_keys)
|
||||||
|
.to eq(expected_result)
|
||||||
|
end
|
||||||
|
it 'raises exception when procedure with bad data' do
|
||||||
|
procedure.libelle = nil
|
||||||
|
procedure.save(validate: false)
|
||||||
|
|
||||||
|
expect { DemarchesPubliquesExportService.new(io).call }.to raise_error(DemarchesPubliquesExportService::Error)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue