From cb7e8d8a6e6115c52fefe193732d4be4e7a53120 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 14 Jun 2022 15:58:25 +0200 Subject: [PATCH 01/10] [graphql] Fix service field which can be nil In `app/models/procedure.rb`, belongs_to service relation is optional. To be consistent, service field in graphql has can be nil. --- app/graphql/types/demarche_descriptor_type.rb | 2 +- app/graphql/types/demarche_type.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/graphql/types/demarche_descriptor_type.rb b/app/graphql/types/demarche_descriptor_type.rb index b29d0a771..600be78cf 100644 --- a/app/graphql/types/demarche_descriptor_type.rb +++ b/app/graphql/types/demarche_descriptor_type.rb @@ -18,7 +18,7 @@ Cela évite l’accès récursif aux dossiers." field :date_fermeture, GraphQL::Types::ISO8601DateTime, "Date de la fermeture.", null: true field :revision, Types::RevisionType, null: false - field :service, Types::ServiceType, null: false + field :service, Types::ServiceType, null: true def service Loaders::Record.for(Service).load(procedure.service_id) diff --git a/app/graphql/types/demarche_type.rb b/app/graphql/types/demarche_type.rb index 01acbb7fa..3c891787b 100644 --- a/app/graphql/types/demarche_type.rb +++ b/app/graphql/types/demarche_type.rb @@ -30,7 +30,7 @@ module Types field :date_fermeture, GraphQL::Types::ISO8601DateTime, "Date de la fermeture.", null: true, method: :closed_at 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 argument :order, Types::Order, default_value: :asc, required: false, description: "L’ordre des dossiers." From 7a06230912c7a663293999c68d0bc52343474836 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 14 Jun 2022 16:01:38 +0200 Subject: [PATCH 02/10] [graphql] add cadre juridique and deliberation fields --- app/graphql/schema.graphql | 7 +++++-- app/graphql/types/demarche_descriptor_type.rb | 11 +++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 7814fa7f1..b04715cbe 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -581,7 +581,7 @@ type Demarche { number: Int! publishedRevision: Revision revisions: [Revision!]! - service: Service! + service: Service """ É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. """ type DemarcheDescriptor { + cadreJuridique: String + """ Date de la création. """ @@ -629,6 +631,7 @@ type DemarcheDescriptor { Pour une démarche déclarative, état cible des dossiers à valider automatiquement """ declarative: DossierDeclarativeState + deliberation: String """ Description de la démarche. @@ -641,7 +644,7 @@ type DemarcheDescriptor { """ number: Int! revision: Revision! - service: Service! + service: Service """ État de la démarche. diff --git a/app/graphql/types/demarche_descriptor_type.rb b/app/graphql/types/demarche_descriptor_type.rb index 600be78cf..30342d625 100644 --- a/app/graphql/types/demarche_descriptor_type.rb +++ b/app/graphql/types/demarche_descriptor_type.rb @@ -20,6 +20,9 @@ Cela évite l’accès récursif aux dossiers." field :revision, Types::RevisionType, null: false field :service, Types::ServiceType, null: true + field :cadre_juridique, String, null: true + field :deliberation, String, null: true + def service Loaders::Record.for(Service).load(procedure.service_id) end @@ -28,10 +31,18 @@ Cela évite l’accès récursif aux dossiers." object.is_a?(ProcedureRevision) ? object : object.active_revision end + def deliberation + Rails.application.routes.url_helpers.url_for(procedure.deliberation) if procedure.deliberation.attached? + end + def state procedure.aasm.current_state end + def cadre_juridique + procedure.cadre_juridique + end + def number procedure.id end From b56706b6b83301624468e53041349a1d0e41cae8 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 14 Jun 2022 13:04:36 +0200 Subject: [PATCH 03/10] add scope publiques for procedures --- app/models/procedure.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/procedure.rb b/app/models/procedure.rb index e36a929c4..7f01dadcb 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -219,6 +219,7 @@ class Procedure < ApplicationRecord scope :brouillons, -> { where(aasm_state: :brouillon) } scope :publiees, -> { where(aasm_state: :publiee) } scope :closes, -> { where(aasm_state: [:close, :depubliee]) } + scope :publiques, -> { where(opendata: true) } scope :publiees_ou_closes, -> { where(aasm_state: [:publiee, :close, :depubliee]) } scope :by_libelle, -> { order(libelle: :asc) } scope :created_during, -> (range) { where(created_at: range) } From 78d772441b8557103bc2ee4c0568c6c9708a7594 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 14 Jun 2022 16:25:20 +0200 Subject: [PATCH 04/10] [graphql] add demarchesPubliques to query --- app/graphql/types/query_type.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index d6c6405b6..39a256de4 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -12,6 +12,12 @@ module Types argument :number, Int, "Numéro du groupe instructeur.", required: true end + field :demarches_publiques, DemarcheDescriptorType.connection_type, null: false + + def demarches_publiques + Procedure.publiques + end + def demarche(number:) Procedure.for_api_v2.find(number) rescue => e From fa1cbdc848c4fbe0d2e5a7054cd07fbaac4d2584 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Fri, 10 Jun 2022 10:51:09 +0200 Subject: [PATCH 05/10] require admin context for demarchesPubliques field --- app/graphql/types/base_field.rb | 12 ++++++++++++ app/graphql/types/query_type.rb | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 app/graphql/types/base_field.rb diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb new file mode 100644 index 000000000..fcb2d1097 --- /dev/null +++ b/app/graphql/types/base_field.rb @@ -0,0 +1,12 @@ +module Types + class BaseField < GraphQL::Schema::Field + def initialize(*args, require_admin: false, **kwargs, &block) + @require_admin = require_admin + super(*args, **kwargs, &block) + end + + def visible?(ctx) + super && (@require_admin ? ctx[:admin] : true) + end + end +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 39a256de4..741a7cb17 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -1,5 +1,7 @@ module Types class QueryType < Types::BaseObject + field_class BaseField + field :demarche, DemarcheType, null: false, description: "Informations concernant une démarche." do argument :number, Int, "Numéro de la démarche.", required: true end @@ -12,7 +14,7 @@ module Types argument :number, Int, "Numéro du groupe instructeur.", required: true end - field :demarches_publiques, DemarcheDescriptorType.connection_type, null: false + field :demarches_publiques, DemarcheDescriptorType.connection_type, null: false, require_admin: true def demarches_publiques Procedure.publiques From 7684e974943bf82a885453cd3e03c21c4c40bce8 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 14 Jun 2022 13:05:19 +0200 Subject: [PATCH 06/10] export demarches publiques --- .../demarches_publiques_export_service.rb | 99 +++++++++++++++++++ ...demarches_publiques_export_service_spec.rb | 45 +++++++++ 2 files changed, 144 insertions(+) create mode 100644 app/services/demarches_publiques_export_service.rb create mode 100644 spec/services/demarches_publiques_export_service_spec.rb diff --git a/app/services/demarches_publiques_export_service.rb b/app/services/demarches_publiques_export_service.rb new file mode 100644 index 000000000..1d1dfbfda --- /dev/null +++ b/app/services/demarches_publiques_export_service.rb @@ -0,0 +1,99 @@ +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, admin: 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 + 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 diff --git a/spec/services/demarches_publiques_export_service_spec.rb b/spec/services/demarches_publiques_export_service_spec.rb new file mode 100644 index 000000000..cbce8b9bd --- /dev/null +++ b/spec/services/demarches_publiques_export_service_spec.rb @@ -0,0 +1,45 @@ +describe DemarchesPubliquesExportService do + let(:procedure) { create(:procedure, :published, :with_service, :with_type_de_champ) } + 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, + 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 From 886f59535076ade05208f23f472c556a20f6488e Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Fri, 17 Jun 2022 19:14:04 +0200 Subject: [PATCH 07/10] [graphql] add dossiersCount field visible only for admin --- app/graphql/types/demarche_descriptor_type.rb | 7 +++++++ app/services/demarches_publiques_export_service.rb | 1 + spec/services/demarches_publiques_export_service_spec.rb | 2 ++ 3 files changed, 10 insertions(+) diff --git a/app/graphql/types/demarche_descriptor_type.rb b/app/graphql/types/demarche_descriptor_type.rb index 30342d625..b351ea4b4 100644 --- a/app/graphql/types/demarche_descriptor_type.rb +++ b/app/graphql/types/demarche_descriptor_type.rb @@ -1,5 +1,6 @@ module Types class DemarcheDescriptorType < Types::BaseObject + field_class BaseField 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. Cela évite l’accès récursif aux dossiers." @@ -23,6 +24,8 @@ Cela évite l’accès récursif aux dossiers." field :cadre_juridique, String, null: true field :deliberation, String, null: true + field :dossiers_count, Int, null: false, require_admin: true + def service Loaders::Record.for(Service).load(procedure.service_id) end @@ -31,6 +34,10 @@ Cela évite l’accès récursif aux dossiers." object.is_a?(ProcedureRevision) ? object : object.active_revision end + def dossiers_count + object.dossiers.count + end + def deliberation Rails.application.routes.url_helpers.url_for(procedure.deliberation) if procedure.deliberation.attached? end diff --git a/app/services/demarches_publiques_export_service.rb b/app/services/demarches_publiques_export_service.rb index 1d1dfbfda..7dbdafc58 100644 --- a/app/services/demarches_publiques_export_service.rb +++ b/app/services/demarches_publiques_export_service.rb @@ -44,6 +44,7 @@ class DemarchesPubliquesExportService service { nom organisme typeOrganisme } cadreJuridique deliberation + dossiersCount revision { champDescriptors { type diff --git a/spec/services/demarches_publiques_export_service_spec.rb b/spec/services/demarches_publiques_export_service_spec.rb index cbce8b9bd..7d039729d 100644 --- a/spec/services/demarches_publiques_export_service_spec.rb +++ b/spec/services/demarches_publiques_export_service_spec.rb @@ -1,5 +1,6 @@ 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 @@ -16,6 +17,7 @@ describe DemarchesPubliquesExportService do cadreJuridique: "un cadre juridique important", deliberation: nil, datePublication: procedure.published_at.iso8601, + dossiersCount: 1, revision: { champDescriptors: [ { From 49a77ddffe0088d4c6ec9461ee58e95307c505f8 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Fri, 17 Jun 2022 11:56:08 +0200 Subject: [PATCH 08/10] [graphql] fine tune config to avoid timeout error --- app/graphql/api/v2/schema.rb | 2 +- app/graphql/types/query_type.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/graphql/api/v2/schema.rb b/app/graphql/api/v2/schema.rb index 697a7bdd9..7e0198f50 100644 --- a/app/graphql/api/v2/schema.rb +++ b/app/graphql/api/v2/schema.rb @@ -72,7 +72,7 @@ class API::V2::Schema < GraphQL::Schema use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST - use GraphQL::Schema::Timeout, max_seconds: 10 + use GraphQL::Schema::Timeout, max_seconds: 30 use GraphQL::Batch use GraphQL::Backtrace diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 741a7cb17..55b50b79e 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -14,7 +14,7 @@ module Types argument :number, Int, "Numéro du groupe instructeur.", required: true end - field :demarches_publiques, DemarcheDescriptorType.connection_type, null: false, require_admin: true + field :demarches_publiques, DemarcheDescriptorType.connection_type, null: false, require_admin: true, max_page_size: 30 def demarches_publiques Procedure.publiques From e2348aa8f1a7302ff10e6cae2a8e189c945275a5 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 5 Jul 2022 14:29:17 +0200 Subject: [PATCH 09/10] rename publiques scope to opendata --- app/graphql/types/query_type.rb | 2 +- app/models/procedure.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 55b50b79e..e6f871e46 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -17,7 +17,7 @@ module Types field :demarches_publiques, DemarcheDescriptorType.connection_type, null: false, require_admin: true, max_page_size: 30 def demarches_publiques - Procedure.publiques + Procedure.opendata end def demarche(number:) diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 7f01dadcb..305f8b329 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -219,7 +219,7 @@ class Procedure < ApplicationRecord scope :brouillons, -> { where(aasm_state: :brouillon) } scope :publiees, -> { where(aasm_state: :publiee) } scope :closes, -> { where(aasm_state: [:close, :depubliee]) } - scope :publiques, -> { where(opendata: true) } + scope :opendata, -> { where(opendata: true) } scope :publiees_ou_closes, -> { where(aasm_state: [:publiee, :close, :depubliee]) } scope :by_libelle, -> { order(libelle: :asc) } scope :created_during, -> (range) { where(created_at: range) } From 25a1129ae445acc7c2e08bdb15913503f2d946c7 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 5 Jul 2022 15:01:02 +0200 Subject: [PATCH 10/10] use internal_use instead of require_admin for graphql context --- app/graphql/types/base_field.rb | 6 +++--- app/graphql/types/demarche_descriptor_type.rb | 2 +- app/graphql/types/query_type.rb | 2 +- app/services/demarches_publiques_export_service.rb | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb index fcb2d1097..a1a6c1365 100644 --- a/app/graphql/types/base_field.rb +++ b/app/graphql/types/base_field.rb @@ -1,12 +1,12 @@ module Types class BaseField < GraphQL::Schema::Field - def initialize(*args, require_admin: false, **kwargs, &block) - @require_admin = require_admin + def initialize(*args, internal: false, **kwargs, &block) + @internal = internal super(*args, **kwargs, &block) end def visible?(ctx) - super && (@require_admin ? ctx[:admin] : true) + super && (@internal ? ctx[:internal_use] : true) end end end diff --git a/app/graphql/types/demarche_descriptor_type.rb b/app/graphql/types/demarche_descriptor_type.rb index b351ea4b4..0a8a28d0a 100644 --- a/app/graphql/types/demarche_descriptor_type.rb +++ b/app/graphql/types/demarche_descriptor_type.rb @@ -24,7 +24,7 @@ Cela évite l’accès récursif aux dossiers." field :cadre_juridique, String, null: true field :deliberation, String, null: true - field :dossiers_count, Int, null: false, require_admin: true + field :dossiers_count, Int, null: false, internal: true def service Loaders::Record.for(Service).load(procedure.service_id) diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index e6f871e46..6e8c3ce14 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -14,7 +14,7 @@ module Types argument :number, Int, "Numéro du groupe instructeur.", required: true end - field :demarches_publiques, DemarcheDescriptorType.connection_type, null: false, require_admin: true, max_page_size: 30 + field :demarches_publiques, DemarcheDescriptorType.connection_type, null: false, internal: true, max_page_size: 30 def demarches_publiques Procedure.opendata diff --git a/app/services/demarches_publiques_export_service.rb b/app/services/demarches_publiques_export_service.rb index 7dbdafc58..1afcb01af 100644 --- a/app/services/demarches_publiques_export_service.rb +++ b/app/services/demarches_publiques_export_service.rb @@ -23,7 +23,7 @@ class DemarchesPubliquesExportService private def execute_query(cursor: nil) - result = API::V2::Schema.execute(query, variables: { cursor: cursor }, context: { internal_use: true, admin: true }) + 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