Merge pull request #9387 from tchak/graphql-pagination-fix
fix(graphql): implement real cursor pagination
This commit is contained in:
commit
6ff2287a2d
13 changed files with 913 additions and 124 deletions
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
143
app/graphql/connections/cursor_connection.rb
Normal file
143
app/graphql/connections/cursor_connection.rb
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
# [d1, d2, d3, d4, d5, d6]
|
||||||
|
#
|
||||||
|
# first: 2
|
||||||
|
# -> d1, d2
|
||||||
|
# first: 2, after: d2
|
||||||
|
# -> d3, d4
|
||||||
|
# first: 2, before: d3
|
||||||
|
# -> d1, d2
|
||||||
|
#
|
||||||
|
# last: 2
|
||||||
|
# -> d5, d6
|
||||||
|
# last: 2, before: d5
|
||||||
|
# -> d3, d4
|
||||||
|
# last: 2, after: d4
|
||||||
|
# -> d5, d6
|
||||||
|
#
|
||||||
|
# si after ou before present, last ou first donne juste limit
|
||||||
|
#
|
||||||
|
# order:
|
||||||
|
# order, ne sert rien si after ou before
|
||||||
|
#
|
||||||
|
# first: 2, order: desc => last: 2
|
||||||
|
# -> d5, d6
|
||||||
|
#
|
||||||
|
# last: 2, order: desc => first 2
|
||||||
|
# -> d1, d2
|
||||||
|
def limit_and_inverted(first: nil, last: nil, after: nil, before: nil, order: nil)
|
||||||
|
limit = [first, last, max_page_size].compact.min + 1
|
||||||
|
inverted = last.present? || before.present?
|
||||||
|
|
||||||
|
if order == :desc && after.nil? && before.nil?
|
||||||
|
inverted = !inverted
|
||||||
|
end
|
||||||
|
|
||||||
|
[limit, inverted]
|
||||||
|
end
|
||||||
|
|
||||||
|
def previous_page?(after, result_size, limit, inverted)
|
||||||
|
after.present? || (result_size == limit && inverted)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_page?(before, result_size, limit, inverted)
|
||||||
|
before.present? || (result_size == limit && !inverted)
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_nodes
|
||||||
|
@nodes ||= begin
|
||||||
|
ensure_valid_params
|
||||||
|
|
||||||
|
limit, inverted = limit_and_inverted(first:, last:, after:, before:, order: @deprecated_order)
|
||||||
|
expected_size = limit - 1
|
||||||
|
|
||||||
|
nodes = resolve_nodes(limit:, before:, after:, inverted:)
|
||||||
|
|
||||||
|
result_size = nodes.size
|
||||||
|
@has_previous_page = previous_page?(after, result_size, limit, inverted)
|
||||||
|
@has_next_page = next_page?(before, result_size, limit, inverted)
|
||||||
|
|
||||||
|
trimmed_nodes = nodes.first(expected_size)
|
||||||
|
trimmed_nodes.reverse! if inverted
|
||||||
|
trimmed_nodes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_valid_params
|
||||||
|
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
|
||||||
|
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
|
||||||
|
end
|
||||||
|
end
|
13
app/graphql/connections/deleted_dossiers_connection.rb
Normal file
13
app/graphql/connections/deleted_dossiers_connection.rb
Normal 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
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
@ -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 {
|
||||||
"""
|
"""
|
||||||
L’ordre des dossiers supprimés.
|
L’ordre des dossiers supprimés.
|
||||||
"""
|
"""
|
||||||
order: Order = ASC
|
order: Order = ASC @deprecated(reason: "Utilisez l’argument `last` à la place.")
|
||||||
): DeletedDossierConnection!
|
): DeletedDossierConnection!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -950,7 +955,7 @@ type Demarche {
|
||||||
"""
|
"""
|
||||||
L’ordre des dossiers.
|
L’ordre des dossiers.
|
||||||
"""
|
"""
|
||||||
order: Order = ASC
|
order: Order = ASC @deprecated(reason: "Utilisez l’argument `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 {
|
||||||
"""
|
"""
|
||||||
L’ordre des dossiers en attente de suppression.
|
L’ordre des dossiers en attente de suppression.
|
||||||
"""
|
"""
|
||||||
order: Order = ASC
|
order: Order = ASC @deprecated(reason: "Utilisez l’argument `last` à la place.")
|
||||||
): DeletedDossierConnection!
|
): DeletedDossierConnection!
|
||||||
publishedRevision: Revision
|
publishedRevision: Revision
|
||||||
revisions: [Revision!]!
|
revisions: [Revision!]!
|
||||||
|
@ -2580,7 +2585,7 @@ type GroupeInstructeurWithDossiers {
|
||||||
"""
|
"""
|
||||||
L’ordre des dossiers supprimés.
|
L’ordre des dossiers supprimés.
|
||||||
"""
|
"""
|
||||||
order: Order = ASC
|
order: Order = ASC @deprecated(reason: "Utilisez l’argument `last` à la place.")
|
||||||
): DeletedDossierConnection!
|
): DeletedDossierConnection!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -2630,7 +2635,7 @@ type GroupeInstructeurWithDossiers {
|
||||||
"""
|
"""
|
||||||
L’ordre des dossiers.
|
L’ordre des dossiers.
|
||||||
"""
|
"""
|
||||||
order: Order = ASC
|
order: Order = ASC @deprecated(reason: "Utilisez l’argument `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 {
|
||||||
"""
|
"""
|
||||||
L’ordre des dossiers en attente de suppression.
|
L’ordre des dossiers en attente de suppression.
|
||||||
"""
|
"""
|
||||||
order: Order = ASC
|
order: Order = ASC @deprecated(reason: "Utilisez l’argument `last` à la place.")
|
||||||
): DeletedDossierConnection!
|
): DeletedDossierConnection!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 d’une démarche.", null: false, extras: [:lookahead] do
|
field :dossiers, Types::DossierType.connection_type, "Liste de tous les dossiers d’une démarche.", null: false, extras: [:lookahead] do
|
||||||
argument :order, Types::Order, default_value: :asc, required: false, description: "L’ordre des dossiers."
|
argument :order, Types::Order, default_value: :asc, required: false, description: "L’ordre des dossiers.", deprecation_reason: 'Utilisez l’argument `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 d’une démarche.", null: false do
|
field :deleted_dossiers, Types::DeletedDossierType.connection_type, "Liste de tous les dossiers supprimés d’une démarche.", null: false do
|
||||||
argument :order, Types::Order, default_value: :asc, required: false, description: "L’ordre des dossiers supprimés."
|
argument :order, Types::Order, default_value: :asc, required: false, description: "L’ordre des dossiers supprimés.", deprecation_reason: 'Utilisez l’argument `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 d’une démarche.", null: false do
|
field :pending_deleted_dossiers, Types::DeletedDossierType.connection_type, "Liste de tous les dossiers en attente de suppression définitive d’une démarche.", null: false do
|
||||||
argument :order, Types::Order, default_value: :asc, required: false, description: "L’ordre des dossiers en attente de suppression."
|
argument :order, Types::Order, default_value: :asc, required: false, description: "L’ordre des dossiers en attente de suppression.", deprecation_reason: 'Utilisez l’argument `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
|
||||||
|
|
|
@ -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 d’un groupe instructeur.", null: false, extras: [:lookahead] do
|
field :dossiers, Types::DossierType.connection_type, "Liste de tous les dossiers d’un groupe instructeur.", null: false, extras: [:lookahead] do
|
||||||
argument :order, Types::Order, default_value: :asc, required: false, description: "L’ordre des dossiers."
|
argument :order, Types::Order, default_value: :asc, required: false, description: "L’ordre des dossiers.", deprecation_reason: 'Utilisez l’argument `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 d’un groupe instructeur.", null: false do
|
field :deleted_dossiers, Types::DeletedDossierType.connection_type, "Liste de tous les dossiers supprimés d’un groupe instructeur.", null: false do
|
||||||
argument :order, Types::Order, default_value: :asc, required: false, description: "L’ordre des dossiers supprimés."
|
argument :order, Types::Order, default_value: :asc, required: false, description: "L’ordre des dossiers supprimés.", deprecation_reason: 'Utilisez l’argument `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 d’un groupe instructeur.", null: false do
|
field :pending_deleted_dossiers, Types::DeletedDossierType.connection_type, "Liste de tous les dossiers en attente de suppression définitive d’un groupe instructeur.", null: false do
|
||||||
argument :order, Types::Order, default_value: :asc, required: false, description: "L’ordre des dossiers en attente de suppression."
|
argument :order, Types::Order, default_value: :asc, required: false, description: "L’ordre des dossiers en attente de suppression.", deprecation_reason: 'Utilisez l’argument `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
|
||||||
|
|
|
@ -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) }
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"type": "controller",
|
"type": "controller",
|
||||||
"class": "Users::DossiersController",
|
"class": "Users::DossiersController",
|
||||||
"method": "merci",
|
"method": "merci",
|
||||||
"line": 232,
|
"line": 291,
|
||||||
"file": "app/controllers/users/dossiers_controller.rb",
|
"file": "app/controllers/users/dossiers_controller.rb",
|
||||||
"rendered": {
|
"rendered": {
|
||||||
"name": "users/dossiers/merci",
|
"name": "users/dossiers/merci",
|
||||||
|
@ -39,6 +39,9 @@
|
||||||
},
|
},
|
||||||
"user_input": "current_user.dossiers.includes(:procedure)",
|
"user_input": "current_user.dossiers.includes(:procedure)",
|
||||||
"confidence": "Weak",
|
"confidence": "Weak",
|
||||||
|
"cwe_id": [
|
||||||
|
79
|
||||||
|
],
|
||||||
"note": ""
|
"note": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -70,26 +73,55 @@
|
||||||
},
|
},
|
||||||
"user_input": "FranceConnectInformation.find_by(:merge_token => merge_token_params).email_france_connect",
|
"user_input": "FranceConnectInformation.find_by(:merge_token => merge_token_params).email_france_connect",
|
||||||
"confidence": "Weak",
|
"confidence": "Weak",
|
||||||
|
"cwe_id": [
|
||||||
|
79
|
||||||
|
],
|
||||||
"note": "explicitely sanitized even if we are using html_safe"
|
"note": "explicitely sanitized even if we are using html_safe"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"warning_type": "Redirect",
|
"warning_type": "SQL Injection",
|
||||||
"warning_code": 18,
|
"warning_code": 0,
|
||||||
"fingerprint": "8a1ccc92988486094b2c89e586902a3b6fcbd43910d6363dce14b9981ca8ddeb",
|
"fingerprint": "737aa4f7931ece068cce98d7cc66057a1ec81b9be43e469c3569ff1be91bbf09",
|
||||||
"check_name": "Redirect",
|
"check_name": "SQL",
|
||||||
"message": "Possible unprotected redirect",
|
"message": "Possible SQL injection",
|
||||||
"file": "app/controllers/instructeurs/procedures_controller.rb",
|
"file": "app/graphql/connections/cursor_connection.rb",
|
||||||
"line": 175,
|
"line": 66,
|
||||||
"link": "https://brakemanscanner.org/docs/warning_types/redirect/",
|
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
|
||||||
"code": "redirect_to(Export.find_or_create_export(export_format, current_instructeur.groupe_instructeurs.where(:procedure => procedure), :force => force_export?, **export_options).file.service_url)",
|
"code": "items.order(order_column => ((:desc or :asc)), :id => ((:desc or :asc))).limit(limit).where(\"(#{order_table}.#{order_column}, #{order_table}.id) < (?, ?)\", timestamp, id)",
|
||||||
"render_path": null,
|
"render_path": null,
|
||||||
"location": {
|
"location": {
|
||||||
"type": "method",
|
"type": "method",
|
||||||
"class": "Instructeurs::ProceduresController",
|
"class": "Connections::CursorConnection",
|
||||||
"method": "download_export"
|
"method": "resolve_nodes"
|
||||||
},
|
},
|
||||||
"user_input": "Export.find_or_create_export(export_format, current_instructeur.groupe_instructeurs.where(:procedure => procedure), :force => force_export?, **export_options).file.service_url",
|
"user_input": "order_table",
|
||||||
"confidence": "High",
|
"confidence": "Weak",
|
||||||
|
"cwe_id": [
|
||||||
|
89
|
||||||
|
],
|
||||||
|
"note": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"warning_type": "SQL Injection",
|
||||||
|
"warning_code": 0,
|
||||||
|
"fingerprint": "a94939cb1e551341f443c6414634816e335bbfb03f0836ebd8b3ad8564d7f343",
|
||||||
|
"check_name": "SQL",
|
||||||
|
"message": "Possible SQL injection",
|
||||||
|
"file": "app/graphql/connections/cursor_connection.rb",
|
||||||
|
"line": 69,
|
||||||
|
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
|
||||||
|
"code": "items.order(order_column => ((:desc or :asc)), :id => ((:desc or :asc))).limit(limit).where(\"(#{order_table}.#{order_column}, #{order_table}.id) > (?, ?)\", timestamp, id)",
|
||||||
|
"render_path": null,
|
||||||
|
"location": {
|
||||||
|
"type": "method",
|
||||||
|
"class": "Connections::CursorConnection",
|
||||||
|
"method": "resolve_nodes"
|
||||||
|
},
|
||||||
|
"user_input": "order_table",
|
||||||
|
"confidence": "Weak",
|
||||||
|
"cwe_id": [
|
||||||
|
89
|
||||||
|
],
|
||||||
"note": ""
|
"note": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -99,7 +131,7 @@
|
||||||
"check_name": "SQL",
|
"check_name": "SQL",
|
||||||
"message": "Possible SQL injection",
|
"message": "Possible SQL injection",
|
||||||
"file": "app/models/concerns/dossier_filtering_concern.rb",
|
"file": "app/models/concerns/dossier_filtering_concern.rb",
|
||||||
"line": 30,
|
"line": 32,
|
||||||
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
|
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
|
||||||
"code": "where(\"#{values.count} OR #{\"(#{ProcedurePresentation.sanitized_column(table, column)} ILIKE ?)\"}\", *values.map do\n \"%#{value}%\"\n end)",
|
"code": "where(\"#{values.count} OR #{\"(#{ProcedurePresentation.sanitized_column(table, column)} ILIKE ?)\"}\", *values.map do\n \"%#{value}%\"\n end)",
|
||||||
"render_path": null,
|
"render_path": null,
|
||||||
|
@ -110,29 +142,12 @@
|
||||||
},
|
},
|
||||||
"user_input": "values.count",
|
"user_input": "values.count",
|
||||||
"confidence": "Medium",
|
"confidence": "Medium",
|
||||||
|
"cwe_id": [
|
||||||
|
89
|
||||||
|
],
|
||||||
"note": "The table and column are escaped, which should make this safe"
|
"note": "The table and column are escaped, which should make this safe"
|
||||||
},
|
|
||||||
{
|
|
||||||
"warning_type": "Redirect",
|
|
||||||
"warning_code": 18,
|
|
||||||
"fingerprint": "e2220b7cda7df5d02de77e7c3ce137653126e0d8e91ce445676b63ec4c94bbcb",
|
|
||||||
"check_name": "Redirect",
|
|
||||||
"message": "Possible unprotected redirect",
|
|
||||||
"file": "app/controllers/administrateurs/exports_controller.rb",
|
|
||||||
"line": 18,
|
|
||||||
"link": "https://brakemanscanner.org/docs/warning_types/redirect/",
|
|
||||||
"code": "redirect_to(Export.find_or_create_export(export_format, all_groupe_instructeurs, :force => force_export?, **export_options).file.service_url)",
|
|
||||||
"render_path": null,
|
|
||||||
"location": {
|
|
||||||
"type": "method",
|
|
||||||
"class": "Administrateurs::ExportsController",
|
|
||||||
"method": "download"
|
|
||||||
},
|
|
||||||
"user_input": "Export.find_or_create_export(export_format, all_groupe_instructeurs, :force => force_export?, **export_options).file.service_url",
|
|
||||||
"confidence": "High",
|
|
||||||
"note": ""
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated": "2022-11-15 23:04:53 +0100",
|
"updated": "2023-08-28 12:16:04 +0200",
|
||||||
"brakeman_version": "5.2.2"
|
"brakeman_version": "5.4.1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
112
spec/graphql/connections/cursor_connection_spec.rb
Normal file
112
spec/graphql/connections/cursor_connection_spec.rb
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
RSpec.describe Connections::CursorConnection do
|
||||||
|
describe '.limit_and_inverted' do
|
||||||
|
let(:max_page_size) { 100 }
|
||||||
|
|
||||||
|
subject do
|
||||||
|
cursor = Connections::CursorConnection.new(Dossier)
|
||||||
|
allow(cursor).to receive(:max_page_size).and_return(max_page_size)
|
||||||
|
limit, inverted = cursor.send(:limit_and_inverted, **args)
|
||||||
|
{ limit:, inverted: }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without explicit args' do
|
||||||
|
let(:args) { {} }
|
||||||
|
|
||||||
|
it { is_expected.to eq(limit: max_page_size + 1, inverted: false) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when asked for 2 first elements' do
|
||||||
|
let(:args) { { first: 2 } }
|
||||||
|
|
||||||
|
it { is_expected.to eq(limit: 3, inverted: false) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when asked for 2 first elements, in order desc' do
|
||||||
|
let(:args) { { first: 2, order: :desc } }
|
||||||
|
|
||||||
|
it { is_expected.to eq(limit: 3, inverted: true) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when exceeding the max_page_size' do
|
||||||
|
let(:args) { { first: max_page_size + 1 } }
|
||||||
|
|
||||||
|
it { is_expected.to eq(limit: max_page_size + 1, inverted: false) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when asked for 2 last elements' do
|
||||||
|
let(:args) { { last: 2 } }
|
||||||
|
|
||||||
|
it { is_expected.to eq(limit: 3, inverted: true) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when asked for 2 last elements, in order desc' do
|
||||||
|
let(:args) { { last: 2, order: :desc } }
|
||||||
|
|
||||||
|
it { is_expected.to eq(limit: 3, inverted: false) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context '' do
|
||||||
|
let(:args) { { after: :after, first: 2 } }
|
||||||
|
|
||||||
|
it { is_expected.to eq(limit: 3, inverted: false) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context '' do
|
||||||
|
let(:args) { { before: :before, first: 2 } }
|
||||||
|
|
||||||
|
it { is_expected.to eq(limit: 3, inverted: true) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.previous_page?' do
|
||||||
|
let(:after) { nil }
|
||||||
|
let(:result_size) { nil }
|
||||||
|
let(:limit) { nil }
|
||||||
|
let(:inverted) { false }
|
||||||
|
|
||||||
|
subject do
|
||||||
|
cursor = Connections::CursorConnection.new(Dossier)
|
||||||
|
cursor.send(:previous_page?, after, result_size, limit, inverted)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when after is present' do
|
||||||
|
let(:after) { :after }
|
||||||
|
|
||||||
|
it { is_expected.to be true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when inverted and result_size == limit' do
|
||||||
|
let(:inverted) { true }
|
||||||
|
let(:result_size) { 3 }
|
||||||
|
let(:limit) { 3 }
|
||||||
|
|
||||||
|
it { is_expected.to be true }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.next_page?' do
|
||||||
|
let(:before) { nil }
|
||||||
|
let(:result_size) { nil }
|
||||||
|
let(:limit) { nil }
|
||||||
|
let(:inverted) { false }
|
||||||
|
|
||||||
|
subject do
|
||||||
|
cursor = Connections::CursorConnection.new(Dossier)
|
||||||
|
cursor.send(:next_page?, before, result_size, limit, inverted)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when before is present' do
|
||||||
|
let(:before) { :before }
|
||||||
|
|
||||||
|
it { is_expected.to be true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when not inverted and result_size == limit' do
|
||||||
|
let(:inverted) { false }
|
||||||
|
let(:result_size) { 3 }
|
||||||
|
let(:limit) { 3 }
|
||||||
|
|
||||||
|
it { is_expected.to be true }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue