fix(graphql): implement real cursor pagination

This commit is contained in:
Paul Chavard 2023-07-21 16:20:31 +02:00
parent 563c47c88c
commit 71d5470100
11 changed files with 722 additions and 87 deletions

View file

@ -278,7 +278,7 @@ GEM
i18n (>= 0.7) i18n (>= 0.7)
multi_json multi_json
request_store (>= 1.0) request_store (>= 1.0)
graphql (2.0.15) graphql (2.0.24)
graphql-batch (0.5.1) graphql-batch (0.5.1)
graphql (>= 1.10, < 3) graphql (>= 1.10, < 3)
promise.rb (~> 0.7.2) promise.rb (~> 0.7.2)

View file

@ -18,17 +18,21 @@ class API::V2::StoredQuery
$state: DossierState $state: DossierState
$order: Order $order: Order
$first: Int $first: Int
$last: Int
$before: String
$after: String $after: String
$archived: Boolean $archived: Boolean
$revision: ID $revision: ID
$createdSince: ISO8601DateTime $createdSince: ISO8601DateTime
$updatedSince: ISO8601DateTime $updatedSince: ISO8601DateTime
$pendingDeletedOrder: Order
$pendingDeletedFirst: Int $pendingDeletedFirst: Int
$pendingDeletedLast: Int
$pendingDeletedBefore: String
$pendingDeletedAfter: String $pendingDeletedAfter: String
$pendingDeletedSince: ISO8601DateTime $pendingDeletedSince: ISO8601DateTime
$deletedOrder: Order
$deletedFirst: Int $deletedFirst: Int
$deletedLast: Int
$deletedBefore: String
$deletedAfter: String $deletedAfter: String
$deletedSince: ISO8601DateTime $deletedSince: ISO8601DateTime
$includeGroupeInstructeurs: Boolean = false $includeGroupeInstructeurs: Boolean = false
@ -67,6 +71,8 @@ class API::V2::StoredQuery
state: $state state: $state
order: $order order: $order
first: $first first: $first
last: $last
before: $before
after: $after after: $after
archived: $archived archived: $archived
createdSince: $createdSince createdSince: $createdSince
@ -81,8 +87,9 @@ class API::V2::StoredQuery
} }
} }
pendingDeletedDossiers( pendingDeletedDossiers(
order: $pendingDeletedOrder
first: $pendingDeletedFirst first: $pendingDeletedFirst
last: $pendingDeletedLast
before: $pendingDeletedBefore
after: $pendingDeletedAfter after: $pendingDeletedAfter
deletedSince: $pendingDeletedSince deletedSince: $pendingDeletedSince
) @include(if: $includePendingDeletedDossiers) { ) @include(if: $includePendingDeletedDossiers) {
@ -94,8 +101,9 @@ class API::V2::StoredQuery
} }
} }
deletedDossiers( deletedDossiers(
order: $deletedOrder
first: $deletedFirst first: $deletedFirst
last: $deletedLast
before: $deletedBefore
after: $deletedAfter after: $deletedAfter
deletedSince: $deletedSince deletedSince: $deletedSince
) @include(if: $includeDeletedDossiers) { ) @include(if: $includeDeletedDossiers) {
@ -114,6 +122,8 @@ class API::V2::StoredQuery
$state: DossierState $state: DossierState
$order: Order $order: Order
$first: Int $first: Int
$last: Int
$before: String
$after: String $after: String
$archived: Boolean $archived: Boolean
$revision: ID $revision: ID
@ -121,10 +131,14 @@ class API::V2::StoredQuery
$updatedSince: ISO8601DateTime $updatedSince: ISO8601DateTime
$pendingDeletedOrder: Order $pendingDeletedOrder: Order
$pendingDeletedFirst: Int $pendingDeletedFirst: Int
$pendingDeletedLast: Int
$pendingDeletedBefore: String
$pendingDeletedAfter: String $pendingDeletedAfter: String
$pendingDeletedSince: ISO8601DateTime $pendingDeletedSince: ISO8601DateTime
$deletedOrder: Order $deletedOrder: Order
$deletedFirst: Int $deletedFirst: Int
$deletedLast: Int
$deletedBefore: String
$deletedAfter: String $deletedAfter: String
$deletedSince: ISO8601DateTime $deletedSince: ISO8601DateTime
$includeDossiers: Boolean = false $includeDossiers: Boolean = false
@ -151,6 +165,8 @@ class API::V2::StoredQuery
state: $state state: $state
order: $order order: $order
first: $first first: $first
last: $last
before: $before
after: $after after: $after
archived: $archived archived: $archived
createdSince: $createdSince createdSince: $createdSince
@ -167,6 +183,8 @@ class API::V2::StoredQuery
pendingDeletedDossiers( pendingDeletedDossiers(
order: $pendingDeletedOrder order: $pendingDeletedOrder
first: $pendingDeletedFirst first: $pendingDeletedFirst
last: $pendingDeletedLast
before: $pendingDeletedBefore
after: $pendingDeletedAfter after: $pendingDeletedAfter
deletedSince: $pendingDeletedSince deletedSince: $pendingDeletedSince
) @include(if: $includePendingDeletedDossiers) { ) @include(if: $includePendingDeletedDossiers) {
@ -180,6 +198,8 @@ class API::V2::StoredQuery
deletedDossiers( deletedDossiers(
order: $deletedOrder order: $deletedOrder
first: $deletedFirst first: $deletedFirst
last: $deletedLast
before: $deletedBefore
after: $deletedAfter after: $deletedAfter
deletedSince: $deletedSince deletedSince: $deletedSince
) @include(if: $includeDeletedDossiers) { ) @include(if: $includeDeletedDossiers) {

View file

@ -0,0 +1,116 @@
module Connections
class CursorConnection < GraphQL::Pagination::Connection
def initialize(items, deprecated_order: nil, **kwargs)
super(items, **kwargs)
@deprecated_order = deprecated_order
end
def nodes
load_nodes
end
def has_previous_page
load_nodes
@has_previous_page
end
def has_next_page
load_nodes
@has_next_page
end
def cursor_for(item)
cursor_from_column(item, order_column)
end
private
def load_nodes
@nodes ||= begin
page_info = compute_page_info(before:, after:, first:, last:)
nodes = resolve_nodes(**page_info.slice(:before, :after, :limit, :inverted))
result_size = nodes.size
@has_previous_page = page_info[:has_previous_page].(result_size)
@has_next_page = page_info[:has_next_page].(result_size)
trimmed_nodes = nodes.first(page_info[:expected_size])
trimmed_nodes.reverse! if page_info[:inverted]
trimmed_nodes
end
end
def timestamp_and_id_from_cursor(cursor)
timestamp, id = decode(cursor).split(';')
[Time.zone.parse(timestamp), id.to_i]
end
def cursor_from_column(item, column)
encode([item.read_attribute(column).utc.strftime("%Y-%m-%dT%H:%M:%S.%NZ"), item.id].join(';'))
end
def order_column
:updated_at
end
def order_table
raise StandardError, 'Not implemented'
end
def resolve_nodes(before:, after:, limit:, inverted:)
order = inverted ? :desc : :asc
nodes = items.order(order_column => order, id: order)
nodes = nodes.limit(limit)
if before.present?
timestamp, id = timestamp_and_id_from_cursor(before)
nodes.where("(#{order_table}.#{order_column}, #{order_table}.id) < (?, ?)", timestamp, id)
elsif after.present?
timestamp, id = timestamp_and_id_from_cursor(after)
nodes.where("(#{order_table}.#{order_column}, #{order_table}.id) > (?, ?)", timestamp, id)
else
nodes
end
end
def compute_page_info(before: nil, after: nil, first: nil, last: nil)
if first.present? && last.present?
raise GraphQL::ExecutionError.new('Arguments "first" and "last" are exclusive', extensions: { code: :bad_request })
end
if before.present? && after.present?
raise GraphQL::ExecutionError.new('Arguments "before" and "after" are exclusive', extensions: { code: :bad_request })
end
if first.present? && first < 0
raise GraphQL::ExecutionError.new('Argument "first" must be a non-negative integer', extensions: { code: :bad_request })
end
if last.present? && last < 0
raise GraphQL::ExecutionError.new('Argument "last" must be a non-negative integer', extensions: { code: :bad_request })
end
if @deprecated_order == :desc
if last.present?
first = [last, max_page_size].min
last = nil
else
last = [first || default_page_size, max_page_size].min
first = nil
end
end
limit = [first || last || default_page_size, max_page_size].min + 1
inverted = last.present? || before.present?
{
before:,
after:,
limit:,
inverted:,
expected_size: limit - 1,
has_previous_page: -> (result_size) { after.present? || (result_size >= limit && inverted) },
has_next_page: -> (result_size) { before.present? || (result_size >= limit && !inverted) }
}
end
end
end

View file

@ -0,0 +1,13 @@
module Connections
class DeletedDossiersConnection < CursorConnection
private
def order_column
:deleted_at
end
def order_table
:deleted_dossiers
end
end
end

View file

@ -1,5 +1,5 @@
module Connections module Connections
class DossiersConnection < GraphQL::Pagination::ActiveRecordRelationConnection class DossiersConnection < CursorConnection
def initialize(items, lookahead: nil, **kwargs) def initialize(items, lookahead: nil, **kwargs)
super(items, **kwargs) super(items, **kwargs)
@lookahead = lookahead @lookahead = lookahead
@ -15,6 +15,14 @@ module Connections
private private
def order_column
arguments[:updated_since].present? ? :updated_at : :depose_at
end
def order_table
:dossiers
end
# We check if the query selects champs form dossier. If it's the case we preload the dossier. # We check if the query selects champs form dossier. If it's the case we preload the dossier.
def preload? def preload?
@lookahead.selection(:nodes).selects?(:champs) || @lookahead.selection(:nodes).selects?(:annotations) @lookahead.selection(:nodes).selects?(:champs) || @lookahead.selection(:nodes).selects?(:annotations)

View file

@ -0,0 +1,36 @@
module Connections
class PendingDeletedDossiersConnection < CursorConnection
def cursor_for(item)
if item.en_construction?
cursor_from_column(item, :hidden_by_user_at)
else
cursor_from_column(item, :hidden_by_administration_at)
end
end
private
def resolve_nodes(before:, after:, limit:, inverted:)
order = inverted ? :desc : :asc
dossiers_table = Dossier.arel_table
case_statement = dossiers_table[:state]
.when(:en_construction)
.then(dossiers_table[:hidden_by_user_at])
.else(dossiers_table[:hidden_by_administration_at])
nodes = items.order(case_statement.public_send(order)).order(dossiers_table[:id].public_send(order))
nodes = nodes.limit(limit)
if before.present?
timestamp, id = timestamp_and_id_from_cursor(before)
nodes.where("(#{case_statement.to_sql}, dossiers.id) < (?, ?)", timestamp, id)
elsif after.present?
timestamp, id = timestamp_and_id_from_cursor(after)
nodes.where("(#{case_statement.to_sql}, dossiers.id) > (?, ?)", timestamp, id)
else
nodes
end
end
end
end

View file

@ -1,3 +1,8 @@
"""
Requires that exactly one field must be supplied and that field must not be `null`.
"""
directive @oneOf on INPUT_OBJECT
type Address { type Address {
""" """
code INSEE de la commune code INSEE de la commune
@ -895,7 +900,7 @@ type Demarche {
""" """
Lordre des dossiers supprimés. Lordre des dossiers supprimés.
""" """
order: Order = ASC order: Order = ASC @deprecated(reason: "Utilisez largument `last` à la place.")
): DeletedDossierConnection! ): DeletedDossierConnection!
""" """
@ -950,7 +955,7 @@ type Demarche {
""" """
Lordre des dossiers. Lordre des dossiers.
""" """
order: Order = ASC order: Order = ASC @deprecated(reason: "Utilisez largument `last` à la place.")
""" """
Seulement les dossiers pour la révision donnée. Seulement les dossiers pour la révision donnée.
@ -1008,7 +1013,7 @@ type Demarche {
""" """
Lordre des dossiers en attente de suppression. Lordre des dossiers en attente de suppression.
""" """
order: Order = ASC order: Order = ASC @deprecated(reason: "Utilisez largument `last` à la place.")
): DeletedDossierConnection! ): DeletedDossierConnection!
publishedRevision: Revision publishedRevision: Revision
revisions: [Revision!]! revisions: [Revision!]!
@ -2580,7 +2585,7 @@ type GroupeInstructeurWithDossiers {
""" """
Lordre des dossiers supprimés. Lordre des dossiers supprimés.
""" """
order: Order = ASC order: Order = ASC @deprecated(reason: "Utilisez largument `last` à la place.")
): DeletedDossierConnection! ): DeletedDossierConnection!
""" """
@ -2630,7 +2635,7 @@ type GroupeInstructeurWithDossiers {
""" """
Lordre des dossiers. Lordre des dossiers.
""" """
order: Order = ASC order: Order = ASC @deprecated(reason: "Utilisez largument `last` à la place.")
""" """
Seulement les dossiers pour la révision donnée. Seulement les dossiers pour la révision donnée.
@ -2692,7 +2697,7 @@ type GroupeInstructeurWithDossiers {
""" """
Lordre des dossiers en attente de suppression. Lordre des dossiers en attente de suppression.
""" """
order: Order = ASC order: Order = ASC @deprecated(reason: "Utilisez largument `last` à la place.")
): DeletedDossierConnection! ): DeletedDossierConnection!
} }

View file

@ -35,7 +35,7 @@ module Types
field :service, Types::ServiceType, null: true field :service, Types::ServiceType, null: true
field :dossiers, Types::DossierType.connection_type, "Liste de tous les dossiers dune démarche.", null: false, extras: [:lookahead] do field :dossiers, Types::DossierType.connection_type, "Liste de tous les dossiers dune démarche.", null: false, extras: [:lookahead] do
argument :order, Types::Order, default_value: :asc, required: false, description: "Lordre des dossiers." argument :order, Types::Order, default_value: :asc, required: false, description: "Lordre des dossiers.", deprecation_reason: 'Utilisez largument `last` à la place.'
argument :created_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers déposés depuis la date." argument :created_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers déposés depuis la date."
argument :updated_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers mis à jour depuis la date." argument :updated_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers mis à jour depuis la date."
argument :state, Types::DossierType::DossierState, required: false, description: "Dossiers avec statut." argument :state, Types::DossierType::DossierState, required: false, description: "Dossiers avec statut."
@ -46,11 +46,11 @@ module Types
end end
field :deleted_dossiers, Types::DeletedDossierType.connection_type, "Liste de tous les dossiers supprimés dune démarche.", null: false do field :deleted_dossiers, Types::DeletedDossierType.connection_type, "Liste de tous les dossiers supprimés dune démarche.", null: false do
argument :order, Types::Order, default_value: :asc, required: false, description: "Lordre des dossiers supprimés." argument :order, Types::Order, default_value: :asc, required: false, description: "Lordre des dossiers supprimés.", deprecation_reason: 'Utilisez largument `last` à la place.'
argument :deleted_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers supprimés depuis la date." argument :deleted_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers supprimés depuis la date."
end end
field :pending_deleted_dossiers, Types::DeletedDossierType.connection_type, "Liste de tous les dossiers en attente de suppression définitive dune démarche.", null: false do field :pending_deleted_dossiers, Types::DeletedDossierType.connection_type, "Liste de tous les dossiers en attente de suppression définitive dune démarche.", null: false do
argument :order, Types::Order, default_value: :asc, required: false, description: "Lordre des dossiers en attente de suppression." argument :order, Types::Order, default_value: :asc, required: false, description: "Lordre des dossiers en attente de suppression.", deprecation_reason: 'Utilisez largument `last` à la place.'
argument :deleted_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers en attente de suppression depuis la date." argument :deleted_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers en attente de suppression depuis la date."
end end
@ -111,20 +111,18 @@ module Types
end end
if updated_since.present? if updated_since.present?
dossiers = dossiers.updated_since(updated_since).order_by_updated_at(order) dossiers = dossiers.updated_since(updated_since)
else else
if created_since.present? if created_since.present?
dossiers = dossiers.created_since(created_since) dossiers = dossiers.created_since(created_since)
end end
dossiers = dossiers.order_by_created_at(order)
end end
# We wrap dossiers in a custom connection alongsite the lookahead for the query. # We wrap dossiers in a custom connection alongsite the lookahead for the query.
# The custom connection is responsible for preloading paginated dossiers. # The custom connection is responsible for preloading paginated dossiers.
# https://graphql-ruby.org/pagination/custom_connections.html#using-a-custom-connection # https://graphql-ruby.org/pagination/custom_connections.html#using-a-custom-connection
# https://graphql-ruby.org/queries/lookahead.html # https://graphql-ruby.org/queries/lookahead.html
Connections::DossiersConnection.new(dossiers, lookahead: lookahead) Connections::DossiersConnection.new(dossiers, lookahead: lookahead, deprecated_order: order)
end end
def deleted_dossiers(deleted_since: nil, order:) def deleted_dossiers(deleted_since: nil, order:)
@ -134,7 +132,7 @@ module Types
dossiers = dossiers.deleted_since(deleted_since) dossiers = dossiers.deleted_since(deleted_since)
end end
dossiers.order(deleted_at: order) Connections::DeletedDossiersConnection.new(dossiers, deprecated_order: order)
end end
def pending_deleted_dossiers(deleted_since: nil, order:) def pending_deleted_dossiers(deleted_since: nil, order:)
@ -144,19 +142,7 @@ module Types
dossiers = dossiers.hidden_since(deleted_since) dossiers = dossiers.hidden_since(deleted_since)
end end
dossiers_table = Dossier.arel_table Connections::PendingDeletedDossiersConnection.new(dossiers, deprecated_order: order)
case_statement = dossiers_table[:state]
.when(:en_construction)
.then(dossiers_table[:hidden_by_user_at])
.else(dossiers_table[:hidden_by_administration_at])
dossiers = dossiers.order(case_statement)
if order == :desc
dossiers = dossiers.reverse_order
end
dossiers
end end
def champ_descriptors def champ_descriptors

View file

@ -3,7 +3,7 @@ module Types
description "Un groupe instructeur avec ses dossiers" description "Un groupe instructeur avec ses dossiers"
field :dossiers, Types::DossierType.connection_type, "Liste de tous les dossiers dun groupe instructeur.", null: false, extras: [:lookahead] do field :dossiers, Types::DossierType.connection_type, "Liste de tous les dossiers dun groupe instructeur.", null: false, extras: [:lookahead] do
argument :order, Types::Order, default_value: :asc, required: false, description: "Lordre des dossiers." argument :order, Types::Order, default_value: :asc, required: false, description: "Lordre des dossiers.", deprecation_reason: 'Utilisez largument `last` à la place.'
argument :created_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers déposés depuis la date." argument :created_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers déposés depuis la date."
argument :updated_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers mis à jour depuis la date." argument :updated_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers mis à jour depuis la date."
argument :state, Types::DossierType::DossierState, required: false, description: "Dossiers avec statut." argument :state, Types::DossierType::DossierState, required: false, description: "Dossiers avec statut."
@ -14,12 +14,12 @@ module Types
end end
field :deleted_dossiers, Types::DeletedDossierType.connection_type, "Liste de tous les dossiers supprimés dun groupe instructeur.", null: false do field :deleted_dossiers, Types::DeletedDossierType.connection_type, "Liste de tous les dossiers supprimés dun groupe instructeur.", null: false do
argument :order, Types::Order, default_value: :asc, required: false, description: "Lordre des dossiers supprimés." argument :order, Types::Order, default_value: :asc, required: false, description: "Lordre des dossiers supprimés.", deprecation_reason: 'Utilisez largument `last` à la place.'
argument :deleted_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers supprimés depuis la date." argument :deleted_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers supprimés depuis la date."
end end
field :pending_deleted_dossiers, Types::DeletedDossierType.connection_type, "Liste de tous les dossiers en attente de suppression définitive dun groupe instructeur.", null: false do field :pending_deleted_dossiers, Types::DeletedDossierType.connection_type, "Liste de tous les dossiers en attente de suppression définitive dun groupe instructeur.", null: false do
argument :order, Types::Order, default_value: :asc, required: false, description: "Lordre des dossiers en attente de suppression." argument :order, Types::Order, default_value: :asc, required: false, description: "Lordre des dossiers en attente de suppression.", deprecation_reason: 'Utilisez largument `last` à la place.'
argument :deleted_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers en attente de suppression depuis la date." argument :deleted_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers en attente de suppression depuis la date."
end end
@ -50,20 +50,18 @@ module Types
end end
if updated_since.present? if updated_since.present?
dossiers = dossiers.updated_since(updated_since).order_by_updated_at(order) dossiers = dossiers.updated_since(updated_since)
else else
if created_since.present? if created_since.present?
dossiers = dossiers.created_since(created_since) dossiers = dossiers.created_since(created_since)
end end
dossiers = dossiers.order_by_created_at(order)
end end
# We wrap dossiers in a custom connection alongsite the lookahead for the query. # We wrap dossiers in a custom connection alongsite the lookahead for the query.
# The custom connection is responsible for preloading paginated dossiers. # The custom connection is responsible for preloading paginated dossiers.
# https://graphql-ruby.org/pagination/custom_connections.html#using-a-custom-connection # https://graphql-ruby.org/pagination/custom_connections.html#using-a-custom-connection
# https://graphql-ruby.org/queries/lookahead.html # https://graphql-ruby.org/queries/lookahead.html
Connections::DossiersConnection.new(dossiers, lookahead: lookahead) Connections::DossiersConnection.new(dossiers, lookahead: lookahead, deprecated_order: order)
end end
def deleted_dossiers(deleted_since: nil, order:) def deleted_dossiers(deleted_since: nil, order:)
@ -73,7 +71,7 @@ module Types
dossiers = dossiers.deleted_since(deleted_since) dossiers = dossiers.deleted_since(deleted_since)
end end
dossiers.order(deleted_at: order) Connections::DeletedDossiersConnection.new(dossiers, deprecated_order: order)
end end
def pending_deleted_dossiers(deleted_since: nil, order:) def pending_deleted_dossiers(deleted_since: nil, order:)
@ -83,19 +81,7 @@ module Types
dossiers = dossiers.hidden_since(deleted_since) dossiers = dossiers.hidden_since(deleted_since)
end end
dossiers_table = Dossier.arel_table Connections::PendingDeletedDossiersConnection.new(dossiers, deprecated_order: order)
case_statement = dossiers_table[:state]
.when(:en_construction)
.then(dossiers_table[:hidden_by_user_at])
.else(dossiers_table[:hidden_by_administration_at])
dossiers = dossiers.order(case_statement)
if order == :desc
dossiers = dossiers.reverse_order
end
dossiers
end end
end end
end end

View file

@ -229,8 +229,8 @@ class Dossier < ApplicationRecord
scope :for_procedure_preview, -> { where(for_procedure_preview: true) } scope :for_procedure_preview, -> { where(for_procedure_preview: true) }
scope :for_editing_fork, -> { where.not(editing_fork_origin_id: nil) } scope :for_editing_fork, -> { where.not(editing_fork_origin_id: nil) }
scope :for_groupe_instructeur, -> (groupe_instructeurs) { where(groupe_instructeur: groupe_instructeurs) } scope :for_groupe_instructeur, -> (groupe_instructeurs) { where(groupe_instructeur: groupe_instructeurs) }
scope :order_by_updated_at, -> (order = :desc) { order(updated_at: order) } scope :order_by_updated_at, -> (order = :desc) { order(updated_at: order, id: order) }
scope :order_by_created_at, -> (order = :asc) { order(depose_at: order, created_at: order, id: order) } scope :order_by_created_at, -> (order = :asc) { order(depose_at: order, id: order) }
scope :updated_since, -> (since) { where('dossiers.updated_at >= ?', since) } scope :updated_since, -> (since) { where('dossiers.updated_at >= ?', since) }
scope :created_since, -> (since) { where('dossiers.depose_at >= ?', since) } scope :created_since, -> (since) { where('dossiers.depose_at >= ?', since) }
scope :hidden_by_user_since, -> (since) { where('dossiers.hidden_by_user_at IS NOT NULL AND dossiers.hidden_by_user_at >= ?', since) } scope :hidden_by_user_since, -> (since) { where('dossiers.hidden_by_user_at IS NOT NULL AND dossiers.hidden_by_user_at >= ?', since) }

View file

@ -6,10 +6,13 @@ describe API::V2::GraphqlController do
let(:legacy_token) { APIToken.send(:unpack, token)[:plain_token] } let(:legacy_token) { APIToken.send(:unpack, token)[:plain_token] }
let(:procedure) { create(:procedure, :published, :for_individual, :with_service, administrateurs: [admin], types_de_champ_public:) } let(:procedure) { create(:procedure, :published, :for_individual, :with_service, administrateurs: [admin], types_de_champ_public:) }
let(:types_de_champ_public) { [] } let(:types_de_champ_public) { [] }
let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure: procedure) } let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure:, depose_at: 4.days.ago) }
let(:dossier1) { create(:dossier, :en_construction, :with_individual, procedure: procedure, en_construction_at: 1.day.ago) } let(:dossier1) { create(:dossier, :en_construction, :with_individual, procedure:, en_construction_at: 1.day.ago, depose_at: 3.days.ago) }
let(:dossier2) { create(:dossier, :en_construction, :with_individual, :archived, procedure: procedure, en_construction_at: 3.days.ago) } let(:dossier2) { create(:dossier, :en_construction, :with_individual, :archived, procedure:, en_construction_at: 3.days.ago, depose_at: 2.days.ago) }
let(:dossier_accepte) { create(:dossier, :accepte, :with_individual, procedure: procedure) } let(:dossier3) { create(:dossier, :accepte, :with_individual, procedure:, depose_at: 1.day.ago) }
let(:dossier_accepte) { create(:dossier, :accepte, :with_individual, procedure:) }
let(:dossier_accepte1) { create(:dossier, :accepte, :with_individual, procedure:) }
let(:dossier_accepte2) { create(:dossier, :accepte, :with_individual, procedure:) }
let(:dossiers) { [dossier] } let(:dossiers) { [dossier] }
let(:instructeur) { create(:instructeur, followed_dossiers: dossiers) } let(:instructeur) { create(:instructeur, followed_dossiers: dossiers) }
let(:authorization_header) { ActionController::HttpAuthentication::Token.encode_credentials(token) } let(:authorization_header) { ActionController::HttpAuthentication::Token.encode_credentials(token) }
@ -45,7 +48,7 @@ describe API::V2::GraphqlController do
end end
describe 'ds-query-v2' do describe 'ds-query-v2' do
let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure: procedure) } let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure:, depose_at: 4.days.ago) }
let(:query_id) { 'ds-query-v2' } let(:query_id) { 'ds-query-v2' }
context 'not found operation id' do context 'not found operation id' do
@ -145,16 +148,6 @@ describe API::V2::GraphqlController do
} }
end end
context 'include Dossiers' do
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true } }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:id]).to eq(procedure.to_typed_id)
expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(1)
}
end
context 'include Revision' do context 'include Revision' do
let(:variables) { { demarcheNumber: procedure.id, includeRevision: true } } let(:variables) { { demarcheNumber: procedure.id, includeRevision: true } }
@ -165,41 +158,513 @@ describe API::V2::GraphqlController do
} }
end end
context 'include deleted Dossiers' do context 'include Dossiers' do
let(:variables) { { demarcheNumber: procedure.id, includeDeletedDossiers: true, deletedSince: 2.weeks.ago.iso8601 } } def cursor_for(item, column)
let(:deleted_dossier) { DeletedDossier.create_from_dossier(dossier_accepte, DeletedDossier.reasons.fetch(:user_request)) } cursor = [item.read_attribute(column).utc.strftime("%Y-%m-%dT%H:%M:%S.%NZ"), item.id].join(';')
API::V2::Schema.cursor_encoder.encode(cursor, nonce: true)
end
before { deleted_dossier } let(:order_column) { :depose_at }
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true } }
let(:start_cursor) { cursor_for(dossier, order_column) }
let(:end_cursor) { cursor_for(dossier3, order_column) }
before { dossier1; dossier2; dossier3 }
context 'depose_at' do
it { it {
expect(gql_errors).to be_nil expect(gql_errors).to be_nil
expect(gql_data[:demarche][:id]).to eq(procedure.to_typed_id) expect(gql_data[:demarche][:id]).to eq(procedure.to_typed_id)
expect(gql_data[:demarche][:deletedDossiers][:nodes].size).to eq(1) expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(4)
expect(gql_data[:demarche][:deletedDossiers][:nodes].first[:id]).to eq(deleted_dossier.to_typed_id) expect(gql_data[:demarche][:dossiers][:pageInfo][:hasNextPage]).to be_falsey
expect(gql_data[:demarche][:deletedDossiers][:nodes].first[:dateSupression]).to eq(deleted_dossier.deleted_at.iso8601) expect(gql_data[:demarche][:dossiers][:pageInfo][:hasPreviousPage]).to be_falsey
expect(gql_data[:demarche][:dossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:dossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
context 'first' do
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true, first: 2 } }
let(:end_cursor) { cursor_for(dossier1, order_column) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasNextPage]).to be_truthy
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasPreviousPage]).to be_falsey
expect(gql_data[:demarche][:dossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:dossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
context 'with deprecated order' do
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true, first: 2, order: 'DESC' } }
let(:start_cursor) { cursor_for(dossier2, order_column) }
let(:end_cursor) { cursor_for(dossier3, order_column) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasNextPage]).to be_falsey
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasPreviousPage]).to be_truthy
expect(gql_data[:demarche][:dossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:dossiers][:pageInfo][:endCursor]).to eq(end_cursor)
} }
end end
context 'include pending deleted Dossiers' do context 'after' do
let(:variables) { { demarcheNumber: procedure.id, includePendingDeletedDossiers: true, pendingDeletedSince: 2.weeks.ago.iso8601 } } let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true, first: 2, after: current_cursor } }
let(:current_cursor) { cursor_for(dossier1, order_column) }
let(:start_cursor) { cursor_for(dossier2, order_column) }
let(:end_cursor) { cursor_for(dossier3, order_column) }
before { it {
dossier.hide_and_keep_track!(dossier.user, DeletedDossier.reasons.fetch(:user_request)) expect(gql_errors).to be_nil
Timecop.travel(3.hours.ago) { expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(2)
dossier_accepte.hide_and_keep_track!(instructeur, DeletedDossier.reasons.fetch(:instructeur_request)) expect(gql_data[:demarche][:dossiers][:pageInfo][:hasNextPage]).to be_falsey
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasPreviousPage]).to be_truthy
expect(gql_data[:demarche][:dossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:dossiers][:pageInfo][:endCursor]).to eq(end_cursor)
} }
context 'with deleted' do
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true } }
before { dossier.hide_and_keep_track!(dossier.user, DeletedDossier.reasons.fetch(:user_request)) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(3)
} }
context 'second page not changed' do
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true, first: 2, after: current_cursor } }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasNextPage]).to be_falsey
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasPreviousPage]).to be_truthy
expect(gql_data[:demarche][:dossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:dossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
end
end
context 'before' do
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true, first: 2, before: current_cursor } }
let(:current_cursor) { cursor_for(dossier2, order_column) }
let(:start_cursor) { cursor_for(dossier, order_column) }
let(:end_cursor) { cursor_for(dossier1, order_column) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasNextPage]).to be_truthy
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasPreviousPage]).to be_falsey
expect(gql_data[:demarche][:dossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:dossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
end
context 'last' do
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true, last: 2 } }
let(:start_cursor) { cursor_for(dossier2, order_column) }
let(:end_cursor) { cursor_for(dossier3, order_column) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasNextPage]).to be_falsey
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasPreviousPage]).to be_truthy
expect(gql_data[:demarche][:dossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:dossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
context 'before' do
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true, last: 2, before: current_cursor } }
let(:current_cursor) { cursor_for(dossier2, order_column) }
let(:start_cursor) { cursor_for(dossier, order_column) }
let(:end_cursor) { cursor_for(dossier1, order_column) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasNextPage]).to be_truthy
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasPreviousPage]).to be_falsey
expect(gql_data[:demarche][:dossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:dossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
context 'after' do
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true, last: 2, after: current_cursor } }
let(:current_cursor) { cursor_for(dossier1, order_column) }
let(:start_cursor) { cursor_for(dossier2, order_column) }
let(:end_cursor) { cursor_for(dossier3, order_column) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasNextPage]).to be_falsey
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasPreviousPage]).to be_truthy
expect(gql_data[:demarche][:dossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:dossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
end
end
context 'updated_at' do
let(:order_column) { :updated_at }
context 'first' do
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true, first: 2, updatedSince: 10.days.ago.iso8601 } }
let(:end_cursor) { cursor_for(dossier1, order_column) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasNextPage]).to be_truthy
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasPreviousPage]).to be_falsey
expect(gql_data[:demarche][:dossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:dossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
context 'after' do
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true, first: 2, after: current_cursor, updatedSince: 10.days.ago.iso8601 } }
let(:current_cursor) { cursor_for(dossier1, order_column) }
let(:start_cursor) { cursor_for(dossier2, order_column) }
let(:end_cursor) { cursor_for(dossier3, order_column) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasNextPage]).to be_falsey
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasPreviousPage]).to be_truthy
expect(gql_data[:demarche][:dossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:dossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
context 'before' do
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true, first: 2, before: current_cursor, updatedSince: 10.days.ago.iso8601 } }
let(:current_cursor) { cursor_for(dossier2, order_column) }
let(:start_cursor) { cursor_for(dossier, order_column) }
let(:end_cursor) { cursor_for(dossier1, order_column) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasNextPage]).to be_truthy
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasPreviousPage]).to be_falsey
expect(gql_data[:demarche][:dossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:dossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
end
context 'last' do
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true, last: 2, updatedSince: 10.days.ago.iso8601 } }
let(:start_cursor) { cursor_for(dossier2, order_column) }
let(:end_cursor) { cursor_for(dossier3, order_column) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasNextPage]).to be_falsey
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasPreviousPage]).to be_truthy
expect(gql_data[:demarche][:dossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:dossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
context 'before' do
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true, last: 2, before: current_cursor, updatedSince: 10.days.ago.iso8601 } }
let(:current_cursor) { cursor_for(dossier2, order_column) }
let(:start_cursor) { cursor_for(dossier, order_column) }
let(:end_cursor) { cursor_for(dossier1, order_column) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasNextPage]).to be_truthy
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasPreviousPage]).to be_falsey
expect(gql_data[:demarche][:dossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:dossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
context 'after' do
let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true, last: 2, after: current_cursor, updatedSince: 10.days.ago.iso8601 } }
let(:current_cursor) { cursor_for(dossier1, order_column) }
let(:start_cursor) { cursor_for(dossier2, order_column) }
let(:end_cursor) { cursor_for(dossier3, order_column) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasNextPage]).to be_falsey
expect(gql_data[:demarche][:dossiers][:pageInfo][:hasPreviousPage]).to be_truthy
expect(gql_data[:demarche][:dossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:dossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
end
end
end
context 'include deleted Dossiers' do
def cursor_for(item)
cursor = [item.deleted_at.utc.strftime("%Y-%m-%dT%H:%M:%S.%NZ"), item.id].join(';')
API::V2::Schema.cursor_encoder.encode(cursor, nonce: true)
end
let(:variables) { { demarcheNumber: procedure.id, includeDeletedDossiers: true, deletedSince: 2.weeks.ago.iso8601 } }
let(:deleted_dossier) { DeletedDossier.create_from_dossier(dossier_accepte, DeletedDossier.reasons.fetch(:user_request)).tap { _1.update(deleted_at: 4.days.ago) } }
let(:deleted_dossier1) { DeletedDossier.create_from_dossier(dossier_accepte1, DeletedDossier.reasons.fetch(:user_request)).tap { _1.update(deleted_at: 3.days.ago) } }
let(:deleted_dossier2) { DeletedDossier.create_from_dossier(dossier_accepte2, DeletedDossier.reasons.fetch(:user_request)).tap { _1.update(deleted_at: 2.days.ago) } }
let(:deleted_dossier3) { DeletedDossier.create_from_dossier(dossier3, DeletedDossier.reasons.fetch(:user_request)).tap { _1.update(deleted_at: 1.day.ago) } }
let(:start_cursor) { cursor_for(deleted_dossier) }
let(:end_cursor) { cursor_for(deleted_dossier3) }
before { deleted_dossier; deleted_dossier1; deleted_dossier2; deleted_dossier3 }
it { it {
expect(gql_errors).to be_nil expect(gql_errors).to be_nil
expect(gql_data[:demarche][:id]).to eq(procedure.to_typed_id) expect(gql_data[:demarche][:id]).to eq(procedure.to_typed_id)
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].size).to eq(2) expect(gql_data[:demarche][:deletedDossiers][:nodes].size).to eq(4)
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].first[:id]).to eq(GraphQL::Schema::UniqueWithinType.encode('DeletedDossier', dossier_accepte.id)) expect(gql_data[:demarche][:deletedDossiers][:nodes].first[:id]).to eq(deleted_dossier.to_typed_id)
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].second[:id]).to eq(GraphQL::Schema::UniqueWithinType.encode('DeletedDossier', dossier.id)) expect(gql_data[:demarche][:deletedDossiers][:nodes].first[:dateSupression]).to eq(deleted_dossier.deleted_at.iso8601)
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].first[:dateSupression]).to eq(dossier_accepte.hidden_by_administration_at.iso8601)
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].second[:dateSupression]).to eq(dossier.hidden_by_user_at.iso8601)
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].first[:dateSupression] < gql_data[:demarche][:pendingDeletedDossiers][:nodes].second[:dateSupression])
} }
context 'first' do
let(:variables) { { demarcheNumber: procedure.id, includeDeletedDossiers: true, deletedFirst: 2 } }
let(:end_cursor) { cursor_for(deleted_dossier1) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:deletedDossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:hasNextPage]).to be_truthy
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:hasPreviousPage]).to be_falsey
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
context 'after' do
let(:variables) { { demarcheNumber: procedure.id, includeDeletedDossiers: true, deletedFirst: 2, deletedAfter: current_cursor } }
let(:current_cursor) { cursor_for(deleted_dossier1) }
let(:start_cursor) { cursor_for(deleted_dossier2) }
let(:end_cursor) { cursor_for(deleted_dossier3) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:deletedDossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:hasNextPage]).to be_falsey
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:hasPreviousPage]).to be_truthy
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
context 'before' do
let(:variables) { { demarcheNumber: procedure.id, includeDeletedDossiers: true, deletedFirst: 2, deletedBefore: current_cursor } }
let(:current_cursor) { cursor_for(deleted_dossier2) }
let(:start_cursor) { cursor_for(deleted_dossier) }
let(:end_cursor) { cursor_for(deleted_dossier1) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:deletedDossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:hasNextPage]).to be_truthy
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:hasPreviousPage]).to be_falsey
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
end
context 'last' do
let(:variables) { { demarcheNumber: procedure.id, includeDeletedDossiers: true, deletedLast: 2 } }
let(:start_cursor) { cursor_for(deleted_dossier2) }
let(:end_cursor) { cursor_for(deleted_dossier3) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:deletedDossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:hasNextPage]).to be_falsey
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:hasPreviousPage]).to be_truthy
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
context 'before' do
let(:variables) { { demarcheNumber: procedure.id, includeDeletedDossiers: true, deletedLast: 2, deletedBefore: current_cursor } }
let(:current_cursor) { cursor_for(deleted_dossier2) }
let(:start_cursor) { cursor_for(deleted_dossier) }
let(:end_cursor) { cursor_for(deleted_dossier1) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:deletedDossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:hasNextPage]).to be_truthy
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:hasPreviousPage]).to be_falsey
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
context 'after' do
let(:variables) { { demarcheNumber: procedure.id, includeDeletedDossiers: true, deletedLast: 2, deletedAfter: current_cursor } }
let(:current_cursor) { cursor_for(deleted_dossier1) }
let(:start_cursor) { cursor_for(deleted_dossier2) }
let(:end_cursor) { cursor_for(deleted_dossier3) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:deletedDossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:hasNextPage]).to be_falsey
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:hasPreviousPage]).to be_truthy
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:deletedDossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
end
end
context 'include pending deleted Dossiers' do
def cursor_for(item)
cursor = [(item.en_construction? ? item.hidden_by_user_at : item.hidden_by_administration_at).utc.strftime("%Y-%m-%dT%H:%M:%S.%NZ"), item.id].join(';')
API::V2::Schema.cursor_encoder.encode(cursor, nonce: true)
end
let(:variables) { { demarcheNumber: procedure.id, includePendingDeletedDossiers: true, pendingDeletedSince: 2.weeks.ago.iso8601 } }
let(:pending_deleted_dossier) do
dossier.hide_and_keep_track!(dossier.user, DeletedDossier.reasons.fetch(:user_request))
dossier.tap { _1.update(hidden_by_user_at: 4.days.ago) }
end
let(:pending_deleted_dossier1) do
dossier_accepte.hide_and_keep_track!(instructeur, DeletedDossier.reasons.fetch(:instructeur_request))
dossier_accepte.tap { _1.update(hidden_by_administration_at: 3.days.ago) }
end
let(:pending_deleted_dossier2) do
dossier1.hide_and_keep_track!(dossier.user, DeletedDossier.reasons.fetch(:user_request))
dossier1.tap { _1.update(hidden_by_user_at: 2.days.ago) }
end
let(:pending_deleted_dossier3) do
dossier_accepte1.hide_and_keep_track!(instructeur, DeletedDossier.reasons.fetch(:instructeur_request))
dossier_accepte1.tap { _1.update(hidden_by_administration_at: 1.day.ago) }
end
let(:start_cursor) { cursor_for(pending_deleted_dossier) }
let(:end_cursor) { cursor_for(pending_deleted_dossier3) }
before { pending_deleted_dossier; pending_deleted_dossier1; pending_deleted_dossier2; pending_deleted_dossier3 }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:id]).to eq(procedure.to_typed_id)
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].size).to eq(4)
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].first[:id]).to eq(GraphQL::Schema::UniqueWithinType.encode('DeletedDossier', dossier.id))
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].second[:id]).to eq(GraphQL::Schema::UniqueWithinType.encode('DeletedDossier', dossier_accepte.id))
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].first[:dateSupression]).to eq(pending_deleted_dossier.hidden_by_user_at.iso8601)
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].second[:dateSupression]).to eq(pending_deleted_dossier1.hidden_by_administration_at.iso8601)
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].first[:dateSupression] < gql_data[:demarche][:pendingDeletedDossiers][:nodes].second[:dateSupression]).to be_truthy
}
context 'first' do
let(:variables) { { demarcheNumber: procedure.id, includePendingDeletedDossiers: true, pendingDeletedFirst: 2 } }
let(:end_cursor) { cursor_for(pending_deleted_dossier1) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:hasNextPage]).to be_truthy
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:hasPreviousPage]).to be_falsey
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
context 'after' do
let(:variables) { { demarcheNumber: procedure.id, includePendingDeletedDossiers: true, pendingDeletedFirst: 2, pendingDeletedAfter: current_cursor } }
let(:current_cursor) { cursor_for(pending_deleted_dossier1) }
let(:start_cursor) { cursor_for(pending_deleted_dossier2) }
let(:end_cursor) { cursor_for(pending_deleted_dossier3) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:hasNextPage]).to be_falsey
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:hasPreviousPage]).to be_truthy
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
context 'before' do
let(:variables) { { demarcheNumber: procedure.id, includePendingDeletedDossiers: true, pendingDeletedFirst: 2, pendingDeletedBefore: current_cursor } }
let(:current_cursor) { cursor_for(pending_deleted_dossier2) }
let(:start_cursor) { cursor_for(pending_deleted_dossier) }
let(:end_cursor) { cursor_for(pending_deleted_dossier1) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:hasNextPage]).to be_truthy
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:hasPreviousPage]).to be_falsey
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
end
context 'last' do
let(:variables) { { demarcheNumber: procedure.id, includePendingDeletedDossiers: true, pendingDeletedLast: 2 } }
let(:start_cursor) { cursor_for(pending_deleted_dossier2) }
let(:end_cursor) { cursor_for(pending_deleted_dossier3) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:hasNextPage]).to be_falsey
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:hasPreviousPage]).to be_truthy
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
context 'before' do
let(:variables) { { demarcheNumber: procedure.id, includePendingDeletedDossiers: true, pendingDeletedLast: 2, pendingDeletedBefore: current_cursor } }
let(:current_cursor) { cursor_for(pending_deleted_dossier2) }
let(:start_cursor) { cursor_for(pending_deleted_dossier) }
let(:end_cursor) { cursor_for(pending_deleted_dossier1) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:hasNextPage]).to be_truthy
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:hasPreviousPage]).to be_falsey
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
context 'after' do
let(:variables) { { demarcheNumber: procedure.id, includePendingDeletedDossiers: true, pendingDeletedLast: 2, pendingDeletedAfter: current_cursor } }
let(:current_cursor) { cursor_for(pending_deleted_dossier1) }
let(:start_cursor) { cursor_for(pending_deleted_dossier2) }
let(:end_cursor) { cursor_for(pending_deleted_dossier3) }
it {
expect(gql_errors).to be_nil
expect(gql_data[:demarche][:pendingDeletedDossiers][:nodes].size).to eq(2)
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:hasNextPage]).to be_falsey
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:hasPreviousPage]).to be_truthy
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:startCursor]).to eq(start_cursor)
expect(gql_data[:demarche][:pendingDeletedDossiers][:pageInfo][:endCursor]).to eq(end_cursor)
}
end
end
end end
end end