Commit a715d2de authored by Nathan Friend's avatar Nathan Friend Committed by Mark Chao

Add ability to sort releases from GraphQL

This commit updates our GraphQL endpiont to allow releases to be
sorted by released_at or created_at.
parent c88dcfa3
...@@ -4,6 +4,10 @@ module Resolvers ...@@ -4,6 +4,10 @@ module Resolvers
class ReleasesResolver < BaseResolver class ReleasesResolver < BaseResolver
type Types::ReleaseType.connection_type, null: true type Types::ReleaseType.connection_type, null: true
argument :sort, Types::ReleaseSortEnum,
required: false, default_value: :released_at_desc,
description: 'Sort releases by this criteria'
alias_method :project, :object alias_method :project, :object
# This resolver has a custom singular resolver # This resolver has a custom singular resolver
...@@ -11,12 +15,20 @@ module Resolvers ...@@ -11,12 +15,20 @@ module Resolvers
Resolvers::ReleaseResolver Resolvers::ReleaseResolver
end end
def resolve(**args) SORT_TO_PARAMS_MAP = {
released_at_desc: { order_by: 'released_at', sort: 'desc' },
released_at_asc: { order_by: 'released_at', sort: 'asc' },
created_desc: { order_by: 'created_at', sort: 'desc' },
created_asc: { order_by: 'created_at', sort: 'asc' }
}.freeze
def resolve(sort:)
return unless Feature.enabled?(:graphql_release_data, project, default_enabled: true) return unless Feature.enabled?(:graphql_release_data, project, default_enabled: true)
ReleasesFinder.new( ReleasesFinder.new(
project, project,
current_user current_user,
SORT_TO_PARAMS_MAP[sort]
).execute ).execute
end end
end end
......
# frozen_string_literal: true
module Types
# Not inheriting from Types::SortEnum since we only want
# to implement a subset of the sort values it defines.
class ReleaseSortEnum < BaseEnum
graphql_name 'ReleaseSort'
description 'Values for sorting releases'
# Borrowed from Types::SortEnum
# These values/descriptions should stay in-sync as much as possible.
value 'CREATED_DESC', 'Created at descending order', value: :created_desc
value 'CREATED_ASC', 'Created at ascending order', value: :created_asc
value 'RELEASED_AT_DESC', 'Released at by descending order', value: :released_at_desc
value 'RELEASED_AT_ASC', 'Released at by ascending order', value: :released_at_asc
end
end
---
title: Allow sorting of releases from GraphQL
merge_request: 45577
author:
type: added
...@@ -14745,6 +14745,11 @@ type Project { ...@@ -14745,6 +14745,11 @@ type Project {
Returns the last _n_ elements from the list. Returns the last _n_ elements from the list.
""" """
last: Int last: Int
"""
Sort releases by this criteria
"""
sort: ReleaseSort = RELEASED_AT_DESC
): ReleaseConnection ): ReleaseConnection
""" """
...@@ -16519,6 +16524,31 @@ type ReleaseLinks { ...@@ -16519,6 +16524,31 @@ type ReleaseLinks {
selfUrl: String selfUrl: String
} }
"""
Values for sorting releases
"""
enum ReleaseSort {
"""
Created at ascending order
"""
CREATED_ASC
"""
Created at descending order
"""
CREATED_DESC
"""
Released at by ascending order
"""
RELEASED_AT_ASC
"""
Released at by descending order
"""
RELEASED_AT_DESC
}
""" """
Represents the source code attached to a release in a particular format Represents the source code attached to a release in a particular format
""" """
......
...@@ -42602,6 +42602,16 @@ ...@@ -42602,6 +42602,16 @@
"name": "releases", "name": "releases",
"description": "Releases of the project", "description": "Releases of the project",
"args": [ "args": [
{
"name": "sort",
"description": "Sort releases by this criteria",
"type": {
"kind": "ENUM",
"name": "ReleaseSort",
"ofType": null
},
"defaultValue": "RELEASED_AT_DESC"
},
{ {
"name": "after", "name": "after",
"description": "Returns the elements in the list that come after the specified cursor.", "description": "Returns the elements in the list that come after the specified cursor.",
...@@ -47517,6 +47527,41 @@ ...@@ -47517,6 +47527,41 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "ENUM",
"name": "ReleaseSort",
"description": "Values for sorting releases",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "CREATED_DESC",
"description": "Created at descending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "CREATED_ASC",
"description": "Created at ascending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "RELEASED_AT_DESC",
"description": "Released at by descending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "RELEASED_AT_ASC",
"description": "Released at by ascending order",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "ReleaseSource", "name": "ReleaseSource",
...@@ -3643,6 +3643,17 @@ Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`. ...@@ -3643,6 +3643,17 @@ Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`.
| `PACKAGE` | Package link type | | `PACKAGE` | Package link type |
| `RUNBOOK` | Runbook link type | | `RUNBOOK` | Runbook link type |
### ReleaseSort
Values for sorting releases.
| Value | Description |
| ----- | ----------- |
| `CREATED_ASC` | Created at ascending order |
| `CREATED_DESC` | Created at descending order |
| `RELEASED_AT_ASC` | Released at by ascending order |
| `RELEASED_AT_DESC` | Released at by descending order |
### RequirementState ### RequirementState
State of a requirement. State of a requirement.
......
...@@ -5,12 +5,19 @@ require 'spec_helper' ...@@ -5,12 +5,19 @@ require 'spec_helper'
RSpec.describe Resolvers::ReleasesResolver do RSpec.describe Resolvers::ReleasesResolver do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:today) { Time.now }
let_it_be(:yesterday) { today - 1.day }
let_it_be(:tomorrow) { today + 1.day }
let_it_be(:project) { create(:project, :private) } let_it_be(:project) { create(:project, :private) }
let_it_be(:release_v1) { create(:release, project: project, tag: 'v1.0.0') } let_it_be(:release_v1) { create(:release, project: project, tag: 'v1.0.0', released_at: yesterday, created_at: tomorrow) }
let_it_be(:release_v2) { create(:release, project: project, tag: 'v2.0.0') } let_it_be(:release_v2) { create(:release, project: project, tag: 'v2.0.0', released_at: today, created_at: yesterday) }
let_it_be(:release_v3) { create(:release, project: project, tag: 'v3.0.0', released_at: tomorrow, created_at: today) }
let_it_be(:developer) { create(:user) } let_it_be(:developer) { create(:user) }
let_it_be(:public_user) { create(:user) } let_it_be(:public_user) { create(:user) }
let(:args) { { sort: :released_at_desc } }
before do before do
project.add_developer(developer) project.add_developer(developer)
end end
...@@ -28,7 +35,41 @@ RSpec.describe Resolvers::ReleasesResolver do ...@@ -28,7 +35,41 @@ RSpec.describe Resolvers::ReleasesResolver do
let(:current_user) { developer } let(:current_user) { developer }
it 'returns all releases associated to the project' do it 'returns all releases associated to the project' do
expect(resolve_releases).to eq([release_v1, release_v2]) expect(resolve_releases).to eq([release_v3, release_v2, release_v1])
end
describe 'sorting behavior' do
context 'with sort: :released_at_desc' do
let(:args) { { sort: :released_at_desc } }
it 'returns the releases ordered by released_at in descending order' do
expect(resolve_releases).to eq([release_v3, release_v2, release_v1])
end
end
context 'with sort: :released_at_asc' do
let(:args) { { sort: :released_at_asc } }
it 'returns the releases ordered by released_at in ascending order' do
expect(resolve_releases).to eq([release_v1, release_v2, release_v3])
end
end
context 'with sort: :created_desc' do
let(:args) { { sort: :created_desc } }
it 'returns the releases ordered by created_at in descending order' do
expect(resolve_releases).to eq([release_v1, release_v3, release_v2])
end
end
context 'with sort: :created_asc' do
let(:args) { { sort: :created_asc } }
it 'returns the releases ordered by created_at in ascending order' do
expect(resolve_releases).to eq([release_v2, release_v3, release_v1])
end
end
end end
end end
end end
...@@ -37,6 +78,6 @@ RSpec.describe Resolvers::ReleasesResolver do ...@@ -37,6 +78,6 @@ RSpec.describe Resolvers::ReleasesResolver do
def resolve_releases def resolve_releases
context = { current_user: current_user } context = { current_user: current_user }
resolve(described_class, obj: project, args: {}, ctx: context) resolve(described_class, obj: project, args: args, ctx: context)
end end
end end
...@@ -300,4 +300,77 @@ RSpec.describe 'Query.project(fullPath).releases()' do ...@@ -300,4 +300,77 @@ RSpec.describe 'Query.project(fullPath).releases()' do
it_behaves_like 'no access to any release data' it_behaves_like 'no access to any release data'
end end
end end
describe 'sorting behavior' do
let_it_be(:today) { Time.now }
let_it_be(:yesterday) { today - 1.day }
let_it_be(:tomorrow) { today + 1.day }
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:release_v1) { create(:release, project: project, tag: 'v1', released_at: yesterday, created_at: tomorrow) }
let_it_be(:release_v2) { create(:release, project: project, tag: 'v2', released_at: today, created_at: yesterday) }
let_it_be(:release_v3) { create(:release, project: project, tag: 'v3', released_at: tomorrow, created_at: today) }
let(:current_user) { developer }
let(:params) { nil }
let(:sorted_tags) do
graphql_data.dig('project', 'releases', 'nodes').map { |release| release['tagName'] }
end
let(:query) do
graphql_query_for(:project, { fullPath: project.full_path },
%{
releases#{params ? "(#{params})" : ""} {
nodes {
tagName
}
}
})
end
before do
post_query
end
context 'when no sort: parameter is provided' do
it 'returns the results with the default sort applied (sort: RELEASED_AT_DESC)' do
expect(sorted_tags).to eq(%w(v3 v2 v1))
end
end
context 'with sort: RELEASED_AT_DESC' do
let(:params) { 'sort: RELEASED_AT_DESC' }
it 'returns the releases ordered by released_at in descending order' do
expect(sorted_tags).to eq(%w(v3 v2 v1))
end
end
context 'with sort: RELEASED_AT_ASC' do
let(:params) { 'sort: RELEASED_AT_ASC' }
it 'returns the releases ordered by released_at in ascending order' do
expect(sorted_tags).to eq(%w(v1 v2 v3))
end
end
context 'with sort: CREATED_DESC' do
let(:params) { 'sort: CREATED_DESC' }
it 'returns the releases ordered by created_at in descending order' do
expect(sorted_tags).to eq(%w(v1 v3 v2))
end
end
context 'with sort: CREATED_ASC' do
let(:params) { 'sort: CREATED_ASC' }
it 'returns the releases ordered by created_at in ascending order' do
expect(sorted_tags).to eq(%w(v2 v3 v1))
end
end
end
end end
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