Commit ce9225c0 authored by Vitali Tatarintev's avatar Vitali Tatarintev

Merge branch '258202_sort_projects_by_exceeded_storage' into 'master'

Sort a namespace's projects by exceeded storage

See merge request gitlab-org/gitlab!44124
parents 9e32cc91 8c22ebb1
...@@ -23,7 +23,6 @@ module Resolvers ...@@ -23,7 +23,6 @@ module Resolvers
# The namespace could have been loaded in batch by `BatchLoader`. # The namespace could have been loaded in batch by `BatchLoader`.
# At this point we need the `id` or the `full_path` of the namespace # At this point we need the `id` or the `full_path` of the namespace
# to query for projects, so make sure it's loaded and not `nil` before continuing. # to query for projects, so make sure it's loaded and not `nil` before continuing.
namespace = object.respond_to?(:sync) ? object.sync : object
return Project.none if namespace.nil? return Project.none if namespace.nil?
query = include_subgroups ? namespace.all_projects.with_route : namespace.projects.with_route query = include_subgroups ? namespace.all_projects.with_route : namespace.projects.with_route
...@@ -41,6 +40,14 @@ module Resolvers ...@@ -41,6 +40,14 @@ module Resolvers
complexity = super complexity = super
complexity + 10 complexity + 10
end end
private
def namespace
strong_memoize(:namespace) do
object.respond_to?(:sync) ? object.sync : object
end
end
end end
end end
......
...@@ -7,6 +7,7 @@ module Types ...@@ -7,6 +7,7 @@ module Types
description 'Values for sorting projects' description 'Values for sorting projects'
value 'SIMILARITY', 'Most similar to the search query', value: :similarity value 'SIMILARITY', 'Most similar to the search query', value: :similarity
value 'STORAGE', 'Sort by storage size', value: :storage
end end
end end
end end
...@@ -13276,6 +13276,11 @@ enum NamespaceProjectSort { ...@@ -13276,6 +13276,11 @@ enum NamespaceProjectSort {
Most similar to the search query Most similar to the search query
""" """
SIMILARITY SIMILARITY
"""
Sort by storage size
"""
STORAGE
} }
input NegatedBoardIssueInput { input NegatedBoardIssueInput {
......
...@@ -39133,6 +39133,12 @@ ...@@ -39133,6 +39133,12 @@
"description": "Most similar to the search query", "description": "Most similar to the search query",
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
},
{
"name": "STORAGE",
"description": "Sort by storage size",
"isDeprecated": false,
"deprecationReason": null
} }
], ],
"possibleTypes": null "possibleTypes": null
...@@ -3771,6 +3771,7 @@ Values for sorting projects. ...@@ -3771,6 +3771,7 @@ Values for sorting projects.
| Value | Description | | Value | Description |
| ----- | ----------- | | ----- | ----------- |
| `SIMILARITY` | Most similar to the search query | | `SIMILARITY` | Most similar to the search query |
| `STORAGE` | Sort by storage size |
### PackageTypeEnum ### PackageTypeEnum
......
...@@ -19,7 +19,7 @@ query getStorageCounter($fullPath: ID!, $withExcessStorageData: Boolean = false) ...@@ -19,7 +19,7 @@ query getStorageCounter($fullPath: ID!, $withExcessStorageData: Boolean = false)
wikiSize wikiSize
snippetsSize snippetsSize
} }
projects(includeSubgroups: true) { projects(includeSubgroups: true, sort: STORAGE) {
edges { edges {
node { node {
id id
......
...@@ -14,8 +14,9 @@ module EE ...@@ -14,8 +14,9 @@ module EE
def resolve(include_subgroups:, search:, sort:, has_vulnerabilities: false) def resolve(include_subgroups:, search:, sort:, has_vulnerabilities: false)
projects = super(include_subgroups: include_subgroups, search: search, sort: sort) projects = super(include_subgroups: include_subgroups, search: search, sort: sort)
projects = projects.has_vulnerabilities if has_vulnerabilities
has_vulnerabilities ? projects.has_vulnerabilities : projects projects = projects.order_by_total_repository_size_excess_desc(namespace.actual_size_limit) if sort == :storage
projects
end end
end end
end end
......
...@@ -165,6 +165,16 @@ module EE ...@@ -165,6 +165,16 @@ module EE
scope :without_unlimited_repository_size_limit, -> { where.not(repository_size_limit: 0) } scope :without_unlimited_repository_size_limit, -> { where.not(repository_size_limit: 0) }
scope :without_repository_size_limit, -> { where(repository_size_limit: nil) } scope :without_repository_size_limit, -> { where(repository_size_limit: nil) }
scope :order_by_total_repository_size_excess_desc, -> (limit) do
excess = ::ProjectStatistics.arel_table[:repository_size] +
::ProjectStatistics.arel_table[:lfs_objects_size] -
::Project.arel_table.coalesce(::Project.arel_table[:repository_size_limit], limit, 0)
joins(:statistics).order(
Arel.sql(Arel::Nodes::Descending.new(excess).to_sql)
)
end
delegate :shared_runners_minutes, :shared_runners_seconds, :shared_runners_seconds_last_reset, delegate :shared_runners_minutes, :shared_runners_seconds, :shared_runners_seconds_last_reset,
to: :statistics, allow_nil: true to: :statistics, allow_nil: true
......
...@@ -19,27 +19,51 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do ...@@ -19,27 +19,51 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
end end
describe '#resolve' do describe '#resolve' do
subject(:projects) { resolve_projects(has_vulnerabilities) } context 'has_vulnerabilities' do
subject(:projects) { resolve_projects(has_vulnerabilities: has_vulnerabilities) }
context 'when the `has_vulnerabilities` parameter is not truthy' do context 'when the `has_vulnerabilities` parameter is not truthy' do
let(:has_vulnerabilities) { false } let(:has_vulnerabilities) { false }
it { is_expected.to contain_exactly(project_1, project_2) } it { is_expected.to contain_exactly(project_1, project_2) }
end
context 'when the `has_vulnerabilities` parameter is truthy' do
let(:has_vulnerabilities) { true }
it { is_expected.to contain_exactly(project_1) }
end
end end
context 'when the `has_vulnerabilities` parameter is truthy' do context 'sorting' do
let(:has_vulnerabilities) { true } let(:project_3) { create(:project, namespace: group) }
before do
project_1.statistics.update!(lfs_objects_size: 11, repository_size: 10)
project_2.statistics.update!(lfs_objects_size: 10, repository_size: 12)
project_3.statistics.update!(lfs_objects_size: 12, repository_size: 11)
end
context 'when sort equals :storage' do
subject(:projects) { resolve_projects(sort: :storage) }
it { is_expected.to eq([project_3, project_2, project_1]) }
end
context 'when sort does not equal :storage' do
subject(:projects) { resolve_projects }
it { is_expected.to contain_exactly(project_1) } it { is_expected.to eq([project_1, project_2, project_3]) }
end
end end
end end
end end
def resolve_projects(has_vulnerabilities) def resolve_projects(has_vulnerabilities: false, sort: :similarity)
args = { args = {
include_subgroups: false, include_subgroups: false,
has_vulnerabilities: has_vulnerabilities, has_vulnerabilities: has_vulnerabilities,
sort: :similarity, sort: sort,
search: nil search: nil
} }
......
...@@ -279,6 +279,17 @@ RSpec.describe Project do ...@@ -279,6 +279,17 @@ RSpec.describe Project do
expect(described_class.not_aimed_for_deletion).to contain_exactly(project) expect(described_class.not_aimed_for_deletion).to contain_exactly(project)
end end
end end
describe '.order_by_total_repository_size_excess_desc' do
let_it_be(:project_1) { create(:project_statistics, lfs_objects_size: 10, repository_size: 10).project }
let_it_be(:project_2) { create(:project_statistics, lfs_objects_size: 5, repository_size: 55).project }
let_it_be(:project_3) { create(:project, repository_size_limit: 30, statistics: create(:project_statistics, lfs_objects_size: 8, repository_size: 32)) }
let(:limit) { 20 }
subject { described_class.order_by_total_repository_size_excess_desc(limit) }
it { is_expected.to eq([project_2, project_3, project_1]) }
end
end end
describe 'validations' do describe 'validations' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment