Merge pull request #10487 from tchak/feat-graphql-discard-message
feat(graphql): messages can be discarded through api
This commit is contained in:
commit
f55cadc806
8 changed files with 169 additions and 1 deletions
|
@ -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!
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
24
app/graphql/mutations/dossier_supprimer_message.rb
Normal file
24
app/graphql/mutations/dossier_supprimer_message.rb
Normal 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
|
|
@ -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 d’un bloc répétable.
|
Description des champs d’un 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.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue