Merge pull request #10487 from tchak/feat-graphql-discard-message

feat(graphql): messages can be discarded through api
This commit is contained in:
Paul Chavard 2024-06-04 16:26:45 +00:00 committed by GitHub
commit f55cadc806
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 169 additions and 1 deletions

View file

@ -819,6 +819,19 @@ class API::V2::StoredQuery
} }
} }
mutation dossierSupprimerMessage($input: DossierSupprimerMessageInput!) {
dossierSupprimerMessage(input: $input) {
message {
id
createdAt
discardedAt
}
errors {
message
}
}
}
mutation dossierModifierAnnotationText( mutation dossierModifierAnnotationText(
$input: DossierModifierAnnotationTextInput! $input: DossierModifierAnnotationTextInput!
) { ) {

View file

@ -21,6 +21,9 @@ module Mutations
if !dossier.en_construction? if !dossier.en_construction?
return false, { errors: ["Le dossier est déjà #{dossier_display_state(dossier, lower: true)}"] } return false, { errors: ["Le dossier est déjà #{dossier_display_state(dossier, lower: true)}"] }
end end
if dossier.blocked_with_pending_correction?
return false, { errors: ["Le dossier est en attente de correction"] }
end
dossier_authorized_for?(dossier, instructeur) dossier_authorized_for?(dossier, instructeur)
end end
end end

View file

@ -0,0 +1,24 @@
module Mutations
class DossierSupprimerMessage < Mutations::BaseMutation
description "Supprimer un message."
argument :message_id, ID, required: true, loads: Types::MessageType
argument :instructeur_id, ID, required: true, loads: Types::ProfileType
field :message, Types::MessageType, null: true
field :errors, [Types::ValidationErrorType], null: true
def resolve(message:, **args)
message.soft_delete!
{ message: }
end
def authorized?(message:, instructeur:, **args)
if !message.soft_deletable?(instructeur)
return false, { errors: ["Le message ne peut pas être supprimé"] }
end
dossier_authorized_for?(message.dossier, instructeur)
end
end
end

View file

@ -2192,6 +2192,30 @@ enum DossierState {
sans_suite sans_suite
} }
"""
Autogenerated input type of DossierSupprimerMessage
"""
input DossierSupprimerMessageInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
instructeurId: ID!
messageId: ID!
}
"""
Autogenerated return type of DossierSupprimerMessage.
"""
type DossierSupprimerMessagePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
errors: [ValidationError!]
message: Message
}
type DropDownListChampDescriptor implements ChampDescriptor { type DropDownListChampDescriptor implements ChampDescriptor {
""" """
Description des champs dun bloc répétable. Description des champs dun bloc répétable.
@ -3084,6 +3108,7 @@ type Message {
body: String! body: String!
correction: Correction correction: Correction
createdAt: ISO8601DateTime! createdAt: ISO8601DateTime!
discardedAt: ISO8601DateTime
email: String! email: String!
id: ID! id: ID!
} }
@ -3313,6 +3338,16 @@ type Mutation {
input: DossierRepasserEnInstructionInput! input: DossierRepasserEnInstructionInput!
): DossierRepasserEnInstructionPayload ): DossierRepasserEnInstructionPayload
"""
Supprimer un message.
"""
dossierSupprimerMessage(
"""
Parameters for DossierSupprimerMessage
"""
input: DossierSupprimerMessageInput!
): DossierSupprimerMessagePayload
""" """
Ajouter des instructeurs à un groupe instructeur. Ajouter des instructeurs à un groupe instructeur.
""" """

View file

@ -4,6 +4,7 @@ module Types
field :email, String, null: false field :email, String, null: false
field :body, String, null: false field :body, String, null: false
field :created_at, GraphQL::Types::ISO8601DateTime, null: false field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :discarded_at, GraphQL::Types::ISO8601DateTime, null: true
field :attachment, Types::File, null: true, deprecation_reason: "Utilisez le champ `attachments` à la place.", extensions: [ field :attachment, Types::File, null: true, deprecation_reason: "Utilisez le champ `attachments` à la place.", extensions: [
{ Extensions::Attachment => { attachments: :piece_jointe, as: :single } } { Extensions::Attachment => { attachments: :piece_jointe, as: :single } }
] ]
@ -19,5 +20,9 @@ module Types
def correction def correction
Loaders::Association.for(object.class, :dossier_correction).load(object) Loaders::Association.for(object.class, :dossier_correction).load(object)
end end
def self.authorized?(object, context)
context.authorized_demarche?(object.dossier.revision.procedure)
end
end end
end end

View file

@ -3,6 +3,7 @@ module Types
field :create_direct_upload, mutation: Mutations::CreateDirectUpload field :create_direct_upload, mutation: Mutations::CreateDirectUpload
field :dossier_envoyer_message, mutation: Mutations::DossierEnvoyerMessage field :dossier_envoyer_message, mutation: Mutations::DossierEnvoyerMessage
field :dossier_supprimer_message, mutation: Mutations::DossierSupprimerMessage
field :dossier_passer_en_instruction, mutation: Mutations::DossierPasserEnInstruction field :dossier_passer_en_instruction, mutation: Mutations::DossierPasserEnInstruction
field :dossier_classer_sans_suite, mutation: Mutations::DossierClasserSansSuite field :dossier_classer_sans_suite, mutation: Mutations::DossierClasserSansSuite
field :dossier_refuser, mutation: Mutations::DossierRefuser field :dossier_refuser, mutation: Mutations::DossierRefuser

View file

@ -558,8 +558,12 @@ class Dossier < ApplicationRecord
false false
end end
def blocked_with_pending_correction?
procedure.feature_enabled?(:blocking_pending_correction) && pending_correction?
end
def can_passer_en_instruction? def can_passer_en_instruction?
return false if procedure.feature_enabled?(:blocking_pending_correction) && pending_correction? return false if blocked_with_pending_correction?
true true
end end

View file

@ -912,6 +912,17 @@ describe API::V2::GraphqlController do
expect(ActionMailer::Base.deliveries.size).to eq(0) expect(ActionMailer::Base.deliveries.size).to eq(0)
} }
end end
context 'with pending corrections' do
before { Flipper.enable(:blocking_pending_correction, dossier.procedure) }
let!(:dossier_correction) { create(:dossier_correction, dossier:) }
it {
expect(dossier.pending_correction?).to be_truthy
expect(gql_errors).to be_nil
expect(gql_data[:dossierPasserEnInstruction][:errors]).to eq([{ message: "Le dossier est en attente de correction" }])
}
end
end end
context 'dossierRepasserEnConstruction' do context 'dossierRepasserEnConstruction' do
@ -1332,5 +1343,77 @@ describe API::V2::GraphqlController do
} }
end end
end end
context 'dossierEnvoyerMessage' do
let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure:) }
let(:variables) { { input: { dossierId: dossier.to_typed_id, instructeurId: instructeur.to_typed_id, body: 'Hello World!' } } }
let(:operation_name) { 'dossierEnvoyerMessage' }
it {
expect(gql_errors).to be_nil
expect(gql_data[:dossierEnvoyerMessage][:errors]).to be_nil
expect(gql_data[:dossierEnvoyerMessage][:message][:id]).to eq(dossier.commentaires.first.to_typed_id)
perform_enqueued_jobs
expect(ActionMailer::Base.deliveries.size).to eq(1)
}
end
context 'dossierSupprimerMessage' do
let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure:) }
let(:message) { create(:commentaire, dossier:, instructeur:) }
let(:dossier_correction) { create(:dossier_correction, dossier:, commentaire: message) }
let(:variables) { { input: { messageId: message.to_typed_id, instructeurId: instructeur.to_typed_id } } }
let(:operation_name) { 'dossierSupprimerMessage' }
it {
expect(message.discarded?).to be_falsey
expect(gql_errors).to be_nil
expect(gql_data[:dossierSupprimerMessage][:errors]).to be_nil
expect(gql_data[:dossierSupprimerMessage][:message][:id]).to eq(message.to_typed_id)
expect(gql_data[:dossierSupprimerMessage][:message][:discardedAt]).not_to be_nil
expect(message.reload.discarded?).to be_truthy
}
it {
expect(dossier_correction.commentaire.discarded?).to be_falsey
expect(dossier.pending_correction?).to be_truthy
expect(gql_errors).to be_nil
expect(gql_data[:dossierSupprimerMessage][:errors]).to be_nil
expect(gql_data[:dossierSupprimerMessage][:message][:id]).to eq(message.to_typed_id)
expect(gql_data[:dossierSupprimerMessage][:message][:discardedAt]).not_to be_nil
expect(message.reload.discarded?).to be_truthy
expect(dossier.pending_correction?).to be_falsey
}
context 'when unauthorized' do
let(:dossier) { create(:dossier, :en_construction, :with_individual) }
it {
expect(message.discarded?).to be_falsey
expect(gql_errors.first[:message]).to eq("An object of type Message was hidden due to permissions")
}
end
context 'when from not the same instructeur' do
let(:other_instructeur) { create(:instructeur, followed_dossiers: dossiers) }
let(:variables) { { input: { messageId: message.to_typed_id, instructeurId: other_instructeur.to_typed_id } } }
it {
expect(message.discarded?).to be_falsey
expect(gql_errors).to be_nil
expect(gql_data[:dossierSupprimerMessage][:errors]).to eq([{ message: "Le message ne peut pas être supprimé" }])
}
end
context 'when from usager' do
let(:message) { create(:commentaire, dossier:) }
it {
expect(message.discarded?).to be_falsey
expect(gql_errors).to be_nil
expect(gql_data[:dossierSupprimerMessage][:errors]).to eq([{ message: "Le message ne peut pas être supprimé" }])
}
end
end
end end
end end