Commit 5a5248be authored by Shinya Maeda's avatar Shinya Maeda

Merge branch '215598-alert-management-graphql-sort-backend' into 'master'

Sorting for Alert Management Alerts in GraphQL

See merge request gitlab-org/gitlab!30964
parents a839d0d1 feafff8d
......@@ -12,7 +12,8 @@ module AlertManagement
return AlertManagement::Alert.none unless authorized?
collection = project.alert_management_alerts
by_iid(collection)
collection = by_iid(collection)
sort(collection)
end
private
......@@ -25,6 +26,10 @@ module AlertManagement
collection.for_iid(params[:iid])
end
def sort(collection)
params[:sort] ? collection.sort_by_attribute(params[:sort]) : collection
end
def authorized?
Ability.allowed?(current_user, :read_alert_management_alerts, project)
end
......
......@@ -6,6 +6,10 @@ module Resolvers
required: false,
description: 'IID of the alert. For example, "1"'
argument :sort, Types::AlertManagement::AlertSortEnum,
description: 'Sort alerts by this criteria',
required: false
type Types::AlertManagement::AlertType, null: true
def resolve(**args)
......
# frozen_string_literal: true
module Types
module AlertManagement
class AlertSortEnum < SortEnum
graphql_name 'AlertManagementAlertSort'
description 'Values for sorting alerts'
value 'START_TIME_ASC', 'Start time by ascending order', value: :start_time_asc
value 'START_TIME_DESC', 'Start time by descending order', value: :start_time_desc
value 'END_TIME_ASC', 'End time by ascending order', value: :end_time_asc
value 'END_TIME_DESC', 'End time by descending order', value: :end_time_desc
value 'CREATED_TIME_ASC', 'Created time by ascending order', value: :created_at_asc
value 'CREATED_TIME_DESC', 'Created time by ascending order', value: :created_at_desc
value 'UPDATED_TIME_ASC', 'Created time by ascending order', value: :updated_at_desc
value 'UPDATED_TIME_DESC', 'Created time by ascending order', value: :updated_at_desc
value 'EVENTS_COUNT_ASC', 'Events count by ascending order', value: :events_count_asc
value 'EVENTS_COUNT_DESC', 'Events count by descending order', value: :events_count_desc
value 'SEVERITY_ASC', 'Severity by ascending order', value: :severity_asc
value 'SEVERITY_DESC', 'Severity by descending order', value: :severity_desc
value 'STATUS_ASC', 'Status by ascending order', value: :status_asc
value 'STATUS_DESC', 'Status by descending order', value: :status_desc
end
end
end
......@@ -4,6 +4,7 @@ module AlertManagement
class Alert < ApplicationRecord
include AtomicInternalId
include ShaAttribute
include Sortable
belongs_to :project
belongs_to :issue, optional: true
......@@ -45,6 +46,29 @@ module AlertManagement
scope :for_iid, -> (iid) { where(iid: iid) }
scope :order_start_time, -> (sort_order) { order(started_at: sort_order) }
scope :order_end_time, -> (sort_order) { order(ended_at: sort_order) }
scope :order_events_count, -> (sort_order) { order(events: sort_order) }
scope :order_severity, -> (sort_order) { order(severity: sort_order) }
scope :order_status, -> (sort_order) { order(status: sort_order) }
def self.sort_by_attribute(method)
case method.to_s
when 'start_time_asc' then order_start_time(:asc)
when 'start_time_desc' then order_start_time(:desc)
when 'end_time_asc' then order_end_time(:asc)
when 'end_time_desc' then order_end_time(:desc)
when 'events_count_asc' then order_events_count(:asc)
when 'events_count_desc' then order_events_count(:desc)
when 'severity_asc' then order_severity(:asc)
when 'severity_desc' then order_severity(:desc)
when 'status_asc' then order_status(:asc)
when 'status_desc' then order_status(:desc)
else
order_by(method)
end
end
def fingerprint=(value)
if value.blank?
super(nil)
......
---
title: Add sorting to AlertManagement Alert Graphql
merge_request: 30964
author:
type: added
......@@ -248,6 +248,101 @@ type AlertManagementAlertEdge {
node: AlertManagementAlert
}
"""
Values for sorting alerts
"""
enum AlertManagementAlertSort {
"""
Created time by ascending order
"""
CREATED_TIME_ASC
"""
Created time by ascending order
"""
CREATED_TIME_DESC
"""
End time by ascending order
"""
END_TIME_ASC
"""
End time by descending order
"""
END_TIME_DESC
"""
Events count by ascending order
"""
EVENTS_COUNT_ASC
"""
Events count by descending order
"""
EVENTS_COUNT_DESC
"""
Severity by ascending order
"""
SEVERITY_ASC
"""
Severity by descending order
"""
SEVERITY_DESC
"""
Start time by ascending order
"""
START_TIME_ASC
"""
Start time by descending order
"""
START_TIME_DESC
"""
Status by ascending order
"""
STATUS_ASC
"""
Status by descending order
"""
STATUS_DESC
"""
Created time by ascending order
"""
UPDATED_TIME_ASC
"""
Created time by ascending order
"""
UPDATED_TIME_DESC
"""
Created at ascending order
"""
created_asc
"""
Created at descending order
"""
created_desc
"""
Updated at ascending order
"""
updated_asc
"""
Updated at descending order
"""
updated_desc
}
"""
Alert severity values
"""
......@@ -6705,6 +6800,11 @@ type Project {
IID of the alert. For example, "1"
"""
iid: String
"""
Sort alerts by this criteria
"""
sort: AlertManagementAlertSort
): AlertManagementAlert
"""
......@@ -6735,6 +6835,11 @@ type Project {
Returns the last _n_ elements from the list.
"""
last: Int
"""
Sort alerts by this criteria
"""
sort: AlertManagementAlertSort
): AlertManagementAlertConnection
"""
......
......@@ -722,6 +722,125 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "AlertManagementAlertSort",
"description": "Values for sorting alerts",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "updated_desc",
"description": "Updated at descending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "updated_asc",
"description": "Updated at ascending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"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": "START_TIME_ASC",
"description": "Start time by ascending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "START_TIME_DESC",
"description": "Start time by descending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "END_TIME_ASC",
"description": "End time by ascending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "END_TIME_DESC",
"description": "End time by descending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "CREATED_TIME_ASC",
"description": "Created time by ascending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "CREATED_TIME_DESC",
"description": "Created time by ascending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "UPDATED_TIME_ASC",
"description": "Created time by ascending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "UPDATED_TIME_DESC",
"description": "Created time by ascending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "EVENTS_COUNT_ASC",
"description": "Events count by ascending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "EVENTS_COUNT_DESC",
"description": "Events count by descending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "SEVERITY_ASC",
"description": "Severity by ascending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "SEVERITY_DESC",
"description": "Severity by descending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "STATUS_ASC",
"description": "Status by ascending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "STATUS_DESC",
"description": "Status by descending order",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "AlertManagementSeverity",
......@@ -20262,6 +20381,16 @@
"ofType": null
},
"defaultValue": null
},
{
"name": "sort",
"description": "Sort alerts by this criteria",
"type": {
"kind": "ENUM",
"name": "AlertManagementAlertSort",
"ofType": null
},
"defaultValue": null
}
],
"type": {
......@@ -20286,6 +20415,16 @@
},
"defaultValue": null
},
{
"name": "sort",
"description": "Sort alerts by this criteria",
"type": {
"kind": "ENUM",
"name": "AlertManagementAlertSort",
"ofType": null
},
"defaultValue": null
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
......
......@@ -5,8 +5,8 @@ require 'spec_helper'
describe AlertManagement::AlertsFinder, '#execute' do
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:alert_1) { create(:alert_management_alert, project: project) }
let_it_be(:alert_2) { create(:alert_management_alert, project: project) }
let_it_be(:alert_1) { create(:alert_management_alert, project: project, ended_at: 1.year.ago, events: 2, severity: :high, status: :resolved) }
let_it_be(:alert_2) { create(:alert_management_alert, project: project, events: 1, severity: :critical, status: :ignored) }
let_it_be(:alert_3) { create(:alert_management_alert) }
let(:params) { {} }
......@@ -36,5 +36,160 @@ describe AlertManagement::AlertsFinder, '#execute' do
it { is_expected.to be_empty }
end
end
describe 'sorting' do
context 'when sorting by created' do
context 'sorts alerts ascending' do
let(:params) { { sort: 'created_asc' } }
it { is_expected.to eq [alert_1, alert_2] }
end
context 'sorts alerts descending' do
let(:params) { { sort: 'created_desc' } }
it { is_expected.to eq [alert_2, alert_1] }
end
end
context 'when sorting by updated' do
context 'sorts alerts ascending' do
let(:params) { { sort: 'updated_asc' } }
it { is_expected.to eq [alert_1, alert_2] }
end
context 'sorts alerts descending' do
let(:params) { { sort: 'updated_desc' } }
it { is_expected.to eq [alert_2, alert_1] }
end
end
context 'when sorting by start time' do
context 'sorts alerts ascending' do
let(:params) { { sort: 'start_time_asc' } }
it { is_expected.to eq [alert_1, alert_2] }
end
context 'sorts alerts descending' do
let(:params) { { sort: 'start_time_desc' } }
it { is_expected.to eq [alert_2, alert_1] }
end
end
context 'when sorting by end time' do
context 'sorts alerts ascending' do
let(:params) { { sort: 'end_time_asc' } }
it { is_expected.to eq [alert_1, alert_2] }
end
context 'sorts alerts descending' do
let(:params) { { sort: 'end_time_desc' } }
it { is_expected.to eq [alert_2, alert_1] }
end
end
context 'when sorting by events count' do
let_it_be(:alert_count_6) { create(:alert_management_alert, project: project, events: 6) }
let_it_be(:alert_count_3) { create(:alert_management_alert, project: project, events: 3) }
context 'sorts alerts ascending' do
let(:params) { { sort: 'events_count_asc' } }
it { is_expected.to eq [alert_2, alert_1, alert_count_3, alert_count_6] }
end
context 'sorts alerts descending' do
let(:params) { { sort: 'events_count_desc' } }
it { is_expected.to eq [alert_count_6, alert_count_3, alert_1, alert_2] }
end
end
context 'when sorting by severity' do
let_it_be(:alert_critical) { create(:alert_management_alert, project: project, severity: :critical) }
let_it_be(:alert_high) { create(:alert_management_alert, project: project, severity: :high) }
let_it_be(:alert_medium) { create(:alert_management_alert, project: project, severity: :medium) }
let_it_be(:alert_low) { create(:alert_management_alert, project: project, severity: :low) }
let_it_be(:alert_info) { create(:alert_management_alert, project: project, severity: :info) }
let_it_be(:alert_unknown) { create(:alert_management_alert, project: project, severity: :unknown) }
context 'sorts alerts ascending' do
let(:params) { { sort: 'severity_asc' } }
it do
is_expected.to eq [
alert_2,
alert_critical,
alert_1,
alert_high,
alert_medium,
alert_low,
alert_info,
alert_unknown
]
end
end
context 'sorts alerts descending' do
let(:params) { { sort: 'severity_desc' } }
it do
is_expected.to eq [
alert_unknown,
alert_info,
alert_low,
alert_medium,
alert_1,
alert_high,
alert_critical,
alert_2
]
end
end
end
context 'when sorting by status' do
let_it_be(:alert_triggered) { create(:alert_management_alert, project: project, status: :triggered) }
let_it_be(:alert_acknowledged) { create(:alert_management_alert, project: project, status: :acknowledged) }
let_it_be(:alert_resolved) { create(:alert_management_alert, project: project, status: :resolved) }
let_it_be(:alert_ignored) { create(:alert_management_alert, project: project, status: :ignored) }
context 'sorts alerts ascending' do
let(:params) { { sort: 'status_asc' } }
it do
is_expected.to eq [
alert_triggered,
alert_acknowledged,
alert_1,
alert_resolved,
alert_2,
alert_ignored
]
end
end
context 'sorts alerts descending' do
let(:params) { { sort: 'status_desc' } }
it do
is_expected.to eq [
alert_2,
alert_ignored,
alert_1,
alert_resolved,
alert_acknowledged,
alert_triggered
]
end
end
end
end
end
end
......@@ -7,8 +7,8 @@ describe Resolvers::AlertManagementAlertResolver do
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:alert_1) { create(:alert_management_alert, project: project) }
let_it_be(:alert_2) { create(:alert_management_alert, project: project) }
let_it_be(:alert_1) { create(:alert_management_alert, project: project, ended_at: 1.year.ago, events: 2, severity: :high, status: :resolved) }
let_it_be(:alert_2) { create(:alert_management_alert, project: project, events: 1, severity: :critical, status: :ignored) }
let_it_be(:alert_other_proj) { create(:alert_management_alert) }
let(:args) { {} }
......@@ -31,6 +31,22 @@ describe Resolvers::AlertManagementAlertResolver do
it { is_expected.to contain_exactly(alert_1) }
end
describe 'sorting' do
# Other sorting examples in spec/finders/alert_management/alerts_finder_spec.rb
context 'when sorting by events count' do
let_it_be(:alert_count_6) { create(:alert_management_alert, project: project, events: 6) }
let_it_be(:alert_count_3) { create(:alert_management_alert, project: project, events: 3) }
it 'sorts alerts ascending' do
expect(resolve_alerts(sort: :events_count_asc)).to eq [alert_2, alert_1, alert_count_3, alert_count_6]
end
it 'sorts alerts descending' do
expect(resolve_alerts(sort: :events_count_desc)).to eq [alert_count_6, alert_count_3, alert_1, alert_2]
end
end
end
end
private
......
......@@ -7,8 +7,9 @@ describe 'getting Alert Management Alerts' do
let_it_be(:payload) { { 'custom' => { 'alert' => 'payload' } } }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:current_user) { create(:user) }
let_it_be(:alert_1) { create(:alert_management_alert, :all_fields, project: project) }
let_it_be(:alert_2) { create(:alert_management_alert, :all_fields, project: project, payload: payload) }
let_it_be(:alert_1) { create(:alert_management_alert, :all_fields, project: project, severity: :low) }
let_it_be(:alert_2) { create(:alert_management_alert, :all_fields, project: project, severity: :critical, payload: payload) }
let_it_be(:other_project_alert) { create(:alert_management_alert, :all_fields) }
let(:fields) do
<<~QUERY
......@@ -85,6 +86,33 @@ describe 'getting Alert Management Alerts' do
it { expect(alerts.size).to eq(1) }
it { expect(first_alert['iid']).to eq(alert_1.iid.to_s) }
end
context 'sorting data given' do
let(:query) do
graphql_query_for(
'project',
{ 'fullPath' => project.full_path },
query_graphql_field('alertManagementAlerts', params, fields)
)
end
let(:params) { 'sort: SEVERITY_DESC' }
let(:iids) { alerts.map { |a| a['iid'] } }
it_behaves_like 'a working graphql query'
it 'sorts in the correct order' do
expect(iids).to eq [alert_1.iid.to_s, alert_2.iid.to_s]
end
context 'ascending order' do
let(:params) { 'sort: SEVERITY_ASC' }
it 'sorts in the correct order' do
expect(iids).to eq [alert_2.iid.to_s, alert_1.iid.to_s]
end
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