diff --git a/app/graphql/resolvers/users/personal_access_tokens_resolver.rb b/app/graphql/resolvers/users/personal_access_tokens_resolver.rb
index 566a99357f2b0d72d25430cac29c23ec70c52b43..4e16d510e9fabc264c88ce4945ea0f08df5bd949 100644
--- a/app/graphql/resolvers/users/personal_access_tokens_resolver.rb
+++ b/app/graphql/resolvers/users/personal_access_tokens_resolver.rb
@@ -33,9 +33,13 @@ class PersonalAccessTokensResolver < BaseResolver
required: false,
description: 'Filter personal access tokens by their revoked status.'
+ argument :expires_before, Types::DateType,
+ required: false,
+ description: 'Filter personal access tokens that expire before the specified date.'
+
argument :expires_after, Types::DateType,
required: false,
- description: 'Filter personal access tokens that expire after the timestamp.'
+ description: 'Filter personal access tokens that expire after the specified date.'
argument :created_after, Types::TimeType,
required: false,
@@ -71,6 +75,7 @@ def filter_params(args)
state: args[:state],
sort: args[:sort],
expires_after: args[:expires_after],
+ expires_before: args[:expires_before],
created_after: args[:created_after],
last_used_after: args[:last_used_after]
}.tap do |params|
diff --git a/app/graphql/types/authz/personal_access_tokens/personal_access_token_type.rb b/app/graphql/types/authz/personal_access_tokens/personal_access_token_type.rb
index 3f885227437d6f73be42d3681b4b5300dd0a760d..673b177406feff361fb5f9d790e1d0f93c77e173 100644
--- a/app/graphql/types/authz/personal_access_tokens/personal_access_token_type.rb
+++ b/app/graphql/types/authz/personal_access_tokens/personal_access_token_type.rb
@@ -8,6 +8,8 @@ class PersonalAccessTokenType < BaseObject
graphql_name 'PersonalAccessToken'
description 'Personal access token.'
+ connection_type_class Types::CountableConnectionType
+
field :id,
GraphQL::Types::ID,
null: false,
diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md
index 98899c853b8b17f17d2cd1de904b7109904824da..49e90b0234d565f04337fb1dfb9ce9d9463b7b29 100644
--- a/doc/api/graphql/reference/_index.md
+++ b/doc/api/graphql/reference/_index.md
@@ -21910,6 +21910,20 @@ The connection type for [`PersonalAccessToken`](#personalaccesstoken).
| `nodes` | [`[PersonalAccessToken]`](#personalaccesstoken) | A list of nodes. |
| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+##### Fields with arguments
+
+###### `PersonalAccessTokenConnection.count`
+
+Total count of collection. Returns limit + 1 for counts greater than the limit.
+
+Returns [`Int!`](#int).
+
+####### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `limit` | [`Int`](#int) | Limit applied to the count query, returns limit + 1. When not provided, returns the exact count. |
+
#### `PersonalAccessTokenEdge`
The edge type for [`PersonalAccessToken`](#personalaccesstoken).
@@ -25089,7 +25103,8 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `createdAfter` | [`Time`](#time) | Filter personal access tokens created after the timestamp. |
-| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the timestamp. |
+| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the specified date. |
+| `expiresBefore` | [`Date`](#date) | Filter personal access tokens that expire before the specified date. |
| `lastUsedAfter` | [`Time`](#time) | Filter personal access tokens last used after the timestamp. |
| `revoked` | [`Boolean`](#boolean) | Filter personal access tokens by their revoked status. |
| `search` | [`String`](#string) | Query to search personal access tokens by name. |
@@ -26781,7 +26796,8 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `createdAfter` | [`Time`](#time) | Filter personal access tokens created after the timestamp. |
-| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the timestamp. |
+| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the specified date. |
+| `expiresBefore` | [`Date`](#date) | Filter personal access tokens that expire before the specified date. |
| `lastUsedAfter` | [`Time`](#time) | Filter personal access tokens last used after the timestamp. |
| `revoked` | [`Boolean`](#boolean) | Filter personal access tokens by their revoked status. |
| `search` | [`String`](#string) | Query to search personal access tokens by name. |
@@ -30152,7 +30168,8 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `createdAfter` | [`Time`](#time) | Filter personal access tokens created after the timestamp. |
-| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the timestamp. |
+| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the specified date. |
+| `expiresBefore` | [`Date`](#date) | Filter personal access tokens that expire before the specified date. |
| `lastUsedAfter` | [`Time`](#time) | Filter personal access tokens last used after the timestamp. |
| `revoked` | [`Boolean`](#boolean) | Filter personal access tokens by their revoked status. |
| `search` | [`String`](#string) | Query to search personal access tokens by name. |
@@ -37714,7 +37731,8 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `createdAfter` | [`Time`](#time) | Filter personal access tokens created after the timestamp. |
-| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the timestamp. |
+| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the specified date. |
+| `expiresBefore` | [`Date`](#date) | Filter personal access tokens that expire before the specified date. |
| `lastUsedAfter` | [`Time`](#time) | Filter personal access tokens last used after the timestamp. |
| `revoked` | [`Boolean`](#boolean) | Filter personal access tokens by their revoked status. |
| `search` | [`String`](#string) | Query to search personal access tokens by name. |
@@ -38171,7 +38189,8 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `createdAfter` | [`Time`](#time) | Filter personal access tokens created after the timestamp. |
-| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the timestamp. |
+| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the specified date. |
+| `expiresBefore` | [`Date`](#date) | Filter personal access tokens that expire before the specified date. |
| `lastUsedAfter` | [`Time`](#time) | Filter personal access tokens last used after the timestamp. |
| `revoked` | [`Boolean`](#boolean) | Filter personal access tokens by their revoked status. |
| `search` | [`String`](#string) | Query to search personal access tokens by name. |
@@ -38679,7 +38698,8 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `createdAfter` | [`Time`](#time) | Filter personal access tokens created after the timestamp. |
-| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the timestamp. |
+| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the specified date. |
+| `expiresBefore` | [`Date`](#date) | Filter personal access tokens that expire before the specified date. |
| `lastUsedAfter` | [`Time`](#time) | Filter personal access tokens last used after the timestamp. |
| `revoked` | [`Boolean`](#boolean) | Filter personal access tokens by their revoked status. |
| `search` | [`String`](#string) | Query to search personal access tokens by name. |
@@ -39155,7 +39175,8 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `createdAfter` | [`Time`](#time) | Filter personal access tokens created after the timestamp. |
-| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the timestamp. |
+| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the specified date. |
+| `expiresBefore` | [`Date`](#date) | Filter personal access tokens that expire before the specified date. |
| `lastUsedAfter` | [`Time`](#time) | Filter personal access tokens last used after the timestamp. |
| `revoked` | [`Boolean`](#boolean) | Filter personal access tokens by their revoked status. |
| `search` | [`String`](#string) | Query to search personal access tokens by name. |
@@ -47838,7 +47859,8 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `createdAfter` | [`Time`](#time) | Filter personal access tokens created after the timestamp. |
-| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the timestamp. |
+| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the specified date. |
+| `expiresBefore` | [`Date`](#date) | Filter personal access tokens that expire before the specified date. |
| `lastUsedAfter` | [`Time`](#time) | Filter personal access tokens last used after the timestamp. |
| `revoked` | [`Boolean`](#boolean) | Filter personal access tokens by their revoked status. |
| `search` | [`String`](#string) | Query to search personal access tokens by name. |
@@ -58184,7 +58206,8 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `createdAfter` | [`Time`](#time) | Filter personal access tokens created after the timestamp. |
-| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the timestamp. |
+| `expiresAfter` | [`Date`](#date) | Filter personal access tokens that expire after the specified date. |
+| `expiresBefore` | [`Date`](#date) | Filter personal access tokens that expire before the specified date. |
| `lastUsedAfter` | [`Time`](#time) | Filter personal access tokens last used after the timestamp. |
| `revoked` | [`Boolean`](#boolean) | Filter personal access tokens by their revoked status. |
| `search` | [`String`](#string) | Query to search personal access tokens by name. |
diff --git a/spec/requests/api/graphql/authz/personal_access_tokens/personal_access_tokens_query_spec.rb b/spec/requests/api/graphql/authz/personal_access_tokens/personal_access_tokens_query_spec.rb
index 4b014edd18696bdeaf52ebf96bd5c28a08a43b4d..087bccca933771793627086e259f64c99be1b48e 100644
--- a/spec/requests/api/graphql/authz/personal_access_tokens/personal_access_tokens_query_spec.rb
+++ b/spec/requests/api/graphql/authz/personal_access_tokens/personal_access_tokens_query_spec.rb
@@ -15,6 +15,7 @@
let_it_be(:legacy_token_revoked) { create(:personal_access_token, :revoked, user: user, name: 'Revoked token') }
let_it_be(:legacy_token_expired) { create(:personal_access_token, :expired, :with_last_used_ips, user:) }
+ let_it_be(:legacy_token_expiring_soon) { create(:personal_access_token, user: user, expires_at: 1.week.from_now) }
let_it_be(:granular_token) do
create(:granular_pat, name: 'Special token', last_used_at: 1.day.ago, permissions: ['read_member_role'],
user: user, namespace: group)
@@ -109,6 +110,10 @@
'revoked' => true,
'active' => false
}),
+ a_hash_including({
+ 'name' => legacy_token_expiring_soon.name,
+ 'active' => true
+ }),
a_hash_including({
'name' => legacy_token_expired.name,
'active' => false,
@@ -117,6 +122,16 @@
)
end
+ describe 'count field' do
+ let(:fields) { 'count' }
+
+ it 'returns the count of PersonalAccessTokens' do
+ send_query
+
+ expect(graphql_data_at(*%i[user personalAccessTokens count])).to eq 5
+ end
+ end
+
it 'avoids N+1 queries' do
control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
post_graphql(query, current_user: current_user)
@@ -195,6 +210,15 @@
end
end
+ context 'with { expires_before: }' do
+ let(:args) { { expires_before: 2.weeks.from_now.to_date } }
+
+ it 'returns only personal access tokens that expire before the given date' do
+ expires_at_dates = personal_access_tokens_data.pluck('expiresAt').map(&:to_date)
+ expect(expires_at_dates).to all(be <= args[:expires_before])
+ end
+ end
+
context 'with { expires_after: }' do
let(:args) { { expires_after: 50.days.from_now.to_date } }