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 } }