diff --git a/app/graphql/resolvers/projects_resolver.rb b/app/graphql/resolvers/projects_resolver.rb index af24d9128fd71266e2309e7b9a757986e6ad5fd3..62b5b5bf61faabf7556abc25c05185f27437fb0a 100644 --- a/app/graphql/resolvers/projects_resolver.rb +++ b/app/graphql/resolvers/projects_resolver.rb @@ -35,6 +35,14 @@ class ProjectsResolver < BaseResolver required: false, description: 'Filter projects by programming language name (case insensitive). For example: "css" or "ruby".' + argument :trending, GraphQL::Types::Boolean, + required: false, + description: "Return only projects that are trending." + + argument :not_aimed_for_deletion, GraphQL::Types::Boolean, + required: false, + description: "Exclude projects that are marked for deletion." + before_connection_authorization do |projects, current_user| ::Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects, current_user).execute end @@ -69,7 +77,9 @@ def finder_params(args) full_paths: args[:full_paths], archived: args[:archived], min_access_level: args[:min_access_level], - language_name: args[:programming_language_name] + language_name: args[:programming_language_name], + trending: args[:trending], + not_aimed_for_deletion: args[:not_aimed_for_deletion] } end diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md index 7431969e928fe3dff9630d1192632719b647ad53..1cd2ad88874eed7e1bde583e043efe83570d5d9c 100644 --- a/doc/api/graphql/reference/_index.md +++ b/doc/api/graphql/reference/_index.md @@ -1197,12 +1197,14 @@ four standard [pagination arguments](#pagination-arguments): | <a id="queryprojectsmarkedfordeletionon"></a>`markedForDeletionOn` | [`Date`](#date) | Date when the project was marked for deletion. | | <a id="queryprojectsmembership"></a>`membership` | [`Boolean`](#boolean) | Return only projects that the current user is a member of. | | <a id="queryprojectsminaccesslevel"></a>`minAccessLevel` | [`AccessLevelEnum`](#accesslevelenum) | Return only projects where current user has at least the specified access level. | +| <a id="queryprojectsnotaimedfordeletion"></a>`notAimedForDeletion` | [`Boolean`](#boolean) | Exclude projects that are marked for deletion. | | <a id="queryprojectspersonal"></a>`personal` | [`Boolean`](#boolean) | Return only personal projects. | | <a id="queryprojectsprogramminglanguagename"></a>`programmingLanguageName` | [`String`](#string) | Filter projects by programming language name (case insensitive). For example: "css" or "ruby". | | <a id="queryprojectssearch"></a>`search` | [`String`](#string) | Search query, which can be for the project name, a path, or a description. | | <a id="queryprojectssearchnamespaces"></a>`searchNamespaces` | [`Boolean`](#boolean) | Include namespace in project search. | | <a id="queryprojectssort"></a>`sort` | [`String`](#string) | Sort order of results. Format: `<field_name>_<sort_direction>`, for example: `id_desc` or `name_asc`. | | <a id="queryprojectstopics"></a>`topics` | [`[String!]`](#string) | Filter projects by topics. | +| <a id="queryprojectstrending"></a>`trending` | [`Boolean`](#boolean) | Return only projects that are trending. | | <a id="queryprojectswithissuesenabled"></a>`withIssuesEnabled` | [`Boolean`](#boolean) | Return only projects with issues enabled. | | <a id="queryprojectswithmergerequestsenabled"></a>`withMergeRequestsEnabled` | [`Boolean`](#boolean) | Return only projects with merge requests enabled. | @@ -32703,12 +32705,14 @@ four standard [pagination arguments](#pagination-arguments): | <a id="organizationprojectsmarkedfordeletionon"></a>`markedForDeletionOn` | [`Date`](#date) | Date when the project was marked for deletion. | | <a id="organizationprojectsmembership"></a>`membership` | [`Boolean`](#boolean) | Return only projects that the current user is a member of. | | <a id="organizationprojectsminaccesslevel"></a>`minAccessLevel` | [`AccessLevelEnum`](#accesslevelenum) | Return only projects where current user has at least the specified access level. | +| <a id="organizationprojectsnotaimedfordeletion"></a>`notAimedForDeletion` | [`Boolean`](#boolean) | Exclude projects that are marked for deletion. | | <a id="organizationprojectspersonal"></a>`personal` | [`Boolean`](#boolean) | Return only personal projects. | | <a id="organizationprojectsprogramminglanguagename"></a>`programmingLanguageName` | [`String`](#string) | Filter projects by programming language name (case insensitive). For example: "css" or "ruby". | | <a id="organizationprojectssearch"></a>`search` | [`String`](#string) | Search query, which can be for the project name, a path, or a description. | | <a id="organizationprojectssearchnamespaces"></a>`searchNamespaces` | [`Boolean`](#boolean) | Include namespace in project search. | | <a id="organizationprojectssort"></a>`sort` | [`String`](#string) | Sort order of results. Format: `<field_name>_<sort_direction>`, for example: `id_desc` or `name_asc`. | | <a id="organizationprojectstopics"></a>`topics` | [`[String!]`](#string) | Filter projects by topics. | +| <a id="organizationprojectstrending"></a>`trending` | [`Boolean`](#boolean) | Return only projects that are trending. | | <a id="organizationprojectswithissuesenabled"></a>`withIssuesEnabled` | [`Boolean`](#boolean) | Return only projects with issues enabled. | | <a id="organizationprojectswithmergerequestsenabled"></a>`withMergeRequestsEnabled` | [`Boolean`](#boolean) | Return only projects with merge requests enabled. | diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index bbd2750865bac7e9f8c06223e5f39e9ad52b46dc..e41567cf1e4e58703beaa23addc221f544fc3512 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -224,6 +224,10 @@ archived { true } end + trait :not_aimed_for_deletion do + marked_for_deletion_at { nil } + end + trait :hidden do hidden { true } end diff --git a/spec/requests/api/graphql/projects/projects_spec.rb b/spec/requests/api/graphql/projects/projects_spec.rb index 69961ad20fa150881ab949ad5b175b6c91dec4c4..4e47f6e57aa6b9d2409fc520fb8203bac0750dd1 100644 --- a/spec/requests/api/graphql/projects/projects_spec.rb +++ b/spec/requests/api/graphql/projects/projects_spec.rb @@ -164,4 +164,70 @@ .to contain_exactly(a_graphql_entity_for(project)) end end + + context 'when providing the trending argument' do + let_it_be(:trending_project1) { create(:project, :public, group: group) } + let_it_be(:trending_project2) { create(:project, :public, group: group) } + let_it_be(:test_project) { create(:project, :public, group: group) } + + let(:filters) { { trending: true } } + + before do + create(:trending_project, project: trending_project1) + create(:trending_project, project: trending_project2) + + post_graphql(query, current_user: current_user) + end + + it 'returns only trending projects' do + expect(graphql_data_at(:projects, :nodes)) + .to contain_exactly( + a_graphql_entity_for(trending_project1), + a_graphql_entity_for(trending_project2) + ) + end + + it 'excludes non-trending projects' do + expect(graphql_data_at(:projects, :nodes)).not_to include( + a_graphql_entity_for(archived_project), + a_graphql_entity_for(test_project), + a_graphql_entity_for(other_project) + ) + end + end + + context 'when providing the not_aimed_for_deletion argument' do + let_it_be(:project_aimed_for_deletion1) do + create(:project, :public, marked_for_deletion_at: 1.day.ago, group: group) + end + + let_it_be(:project_aimed_for_deletion2) do + create(:project, :public, marked_for_deletion_at: 3.days.ago, group: group) + end + + let_it_be(:project_not_aimed_for_deletion) { create(:project, :public, group: group) } + + let(:filters) { { not_aimed_for_deletion: true, archived: :INCLUDE } } + + before do + post_graphql(query, current_user: current_user) + end + + it 'returns only projects not aimed for deletion' do + expect(graphql_data_at(:projects, :nodes)) + .to contain_exactly( + *projects.map { |project| a_graphql_entity_for(project) }, + a_graphql_entity_for(other_project), + a_graphql_entity_for(archived_project), + a_graphql_entity_for(project_not_aimed_for_deletion) + ) + end + + it 'excludes projects marked for deletion' do + expect(graphql_data_at(:projects, :nodes)).not_to include( + a_graphql_entity_for(project_aimed_for_deletion1), + a_graphql_entity_for(project_aimed_for_deletion2) + ) + end + end end