Commit 7e69e2f8 authored by rpereira2's avatar rpereira2

Add search by state in graphql environments API

Add 'states' argument to Environments GraphQL API to allow filtering
of environments by state.
parent cb9ac5a6
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class EnvironmentsFinder class EnvironmentsFinder
attr_reader :project, :current_user, :params attr_reader :project, :current_user, :params
InvalidStatesError = Class.new(StandardError)
def initialize(project, current_user, params = {}) def initialize(project, current_user, params = {})
@project, @current_user, @params = project, current_user, params @project, @current_user, @params = project, current_user, params
end end
...@@ -45,6 +47,9 @@ class EnvironmentsFinder ...@@ -45,6 +47,9 @@ class EnvironmentsFinder
environments = by_name(environments) environments = by_name(environments)
environments = by_search(environments) environments = by_search(environments)
# Raises InvalidStatesError if params[:states] contains invalid states.
environments = by_states(environments)
environments environments
end end
...@@ -91,4 +96,27 @@ class EnvironmentsFinder ...@@ -91,4 +96,27 @@ class EnvironmentsFinder
environments environments
end end
end end
def by_states(environments)
if params[:states].present?
environments_with_states(environments)
else
environments
end
end
def environments_with_states(environments)
# Convert to array of strings
states = Array(params[:states]).map(&:to_s)
raise InvalidStatesError, _('Requested states are invalid') unless valid_states?(states)
environments.with_states(states)
end
def valid_states?(states)
valid_states = Environment.valid_states.map(&:to_s)
(states - valid_states).empty?
end
end end
...@@ -10,6 +10,10 @@ module Resolvers ...@@ -10,6 +10,10 @@ module Resolvers
required: false, required: false,
description: 'Search query' description: 'Search query'
argument :states, [GraphQL::STRING_TYPE],
required: false,
description: 'States of environments that should be included in result'
type Types::EnvironmentType, null: true type Types::EnvironmentType, null: true
alias_method :project, :object alias_method :project, :object
...@@ -18,6 +22,8 @@ module Resolvers ...@@ -18,6 +22,8 @@ module Resolvers
return unless project.present? return unless project.present?
EnvironmentsFinder.new(project, context[:current_user], args).find EnvironmentsFinder.new(project, context[:current_user], args).find
rescue EnvironmentsFinder::InvalidStatesError => exception
raise Gitlab::Graphql::Errors::ArgumentError, exception.message
end end
end end
end end
...@@ -118,6 +118,10 @@ class Environment < ApplicationRecord ...@@ -118,6 +118,10 @@ class Environment < ApplicationRecord
find_or_create_by(name: name) find_or_create_by(name: name)
end end
def self.valid_states
self.state_machine.states.map(&:name)
end
class << self class << self
## ##
# This method returns stop actions (jobs) for multiple environments within one # This method returns stop actions (jobs) for multiple environments within one
......
---
title: Add ability to search by environment state in environments GraphQL API
merge_request: 28567
author:
type: changed
...@@ -5875,6 +5875,11 @@ type Project { ...@@ -5875,6 +5875,11 @@ type Project {
Search query Search query
""" """
search: String search: String
"""
States of environments that should be included in result
"""
states: [String!]
): EnvironmentConnection ): EnvironmentConnection
""" """
......
...@@ -17785,6 +17785,24 @@ ...@@ -17785,6 +17785,24 @@
}, },
"defaultValue": null "defaultValue": null
}, },
{
"name": "states",
"description": "States of environments that should be included in result",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"defaultValue": null
},
{ {
"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.",
......
...@@ -16970,6 +16970,9 @@ msgstr "" ...@@ -16970,6 +16970,9 @@ msgstr ""
msgid "Requested design version does not exist" msgid "Requested design version does not exist"
msgstr "" msgstr ""
msgid "Requested states are invalid"
msgstr ""
msgid "Requests Profiles" msgid "Requests Profiles"
msgstr "" msgstr ""
......
...@@ -7,6 +7,14 @@ FactoryBot.define do ...@@ -7,6 +7,14 @@ FactoryBot.define do
association :project, :repository association :project, :repository
sequence(:external_url) { |n| "https://env#{n}.example.gitlab.com" } sequence(:external_url) { |n| "https://env#{n}.example.gitlab.com" }
trait :available do
state { :available }
end
trait :stopped do
state { :stopped }
end
trait :with_review_app do |environment| trait :with_review_app do |environment|
transient do transient do
ref { 'master' } ref { 'master' }
......
...@@ -3,15 +3,15 @@ ...@@ -3,15 +3,15 @@
require 'spec_helper' require 'spec_helper'
describe EnvironmentsFinder do describe EnvironmentsFinder do
describe '#execute' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:user) { project.creator } let(:user) { project.creator }
let(:environment) { create(:environment, project: project) } let(:environment) { create(:environment, :available, project: project) }
before do before do
project.add_maintainer(user) project.add_maintainer(user)
end end
describe '#execute' do
context 'tagged deployment' do context 'tagged deployment' do
let(:environment_two) { create(:environment, project: project) } let(:environment_two) { create(:environment, project: project) }
# Environments need to include commits, so rewind two commits to fit # Environments need to include commits, so rewind two commits to fit
...@@ -124,4 +124,53 @@ describe EnvironmentsFinder do ...@@ -124,4 +124,53 @@ describe EnvironmentsFinder do
end end
end end
end end
describe '#find' do
context 'with states parameter' do
let(:stopped_environment) { create(:environment, :stopped, project: project) }
it 'returns environments with the requested state' do
result = described_class.new(project, user, states: 'available').find
expect(result).to contain_exactly(environment)
end
it 'returns environments with any of the requested states' do
result = described_class.new(project, user, states: %w(available stopped)).find
expect(result).to contain_exactly(environment, stopped_environment)
end
it 'raises exception when requested state is invalid' do
expect { described_class.new(project, user, states: %w(invalid stopped)).find }.to(
raise_error(described_class::InvalidStatesError, 'Requested states are invalid')
)
end
context 'works with symbols' do
it 'returns environments with the requested state' do
result = described_class.new(project, user, states: :available).find
expect(result).to contain_exactly(environment)
end
it 'returns environments with any of the requested states' do
result = described_class.new(project, user, states: [:available, :stopped]).find
expect(result).to contain_exactly(environment, stopped_environment)
end
end
end
context 'with search and states' do
let(:environment2) { create(:environment, :stopped, name: 'test2', project: project) }
let(:environment3) { create(:environment, :available, name: 'test3', project: project) }
it 'searches environments by name and state' do
result = described_class.new(project, user, search: 'test', states: :available).find
expect(result).to contain_exactly(environment3)
end
end
end
end end
...@@ -10,9 +10,9 @@ describe Resolvers::EnvironmentsResolver do ...@@ -10,9 +10,9 @@ describe Resolvers::EnvironmentsResolver do
context "with a group" do context "with a group" do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:project) { create(:project, :public, group: group) } let(:project) { create(:project, :public, group: group) }
let!(:environment1) { create(:environment, name: 'production', project: project) } let!(:environment1) { create(:environment, :available, name: 'production', project: project) }
let!(:environment2) { create(:environment, name: 'test', project: project) } let!(:environment2) { create(:environment, :stopped, name: 'test', project: project) }
let!(:environment3) { create(:environment, name: 'test2', project: project) } let!(:environment3) { create(:environment, :available, name: 'test2', project: project) }
before do before do
group.add_developer(current_user) group.add_developer(current_user)
...@@ -41,6 +41,18 @@ describe Resolvers::EnvironmentsResolver do ...@@ -41,6 +41,18 @@ describe Resolvers::EnvironmentsResolver do
end end
end end
context 'with states' do
it 'searches environments by state' do
expect(resolve_environments(states: ['available'])).to contain_exactly(environment1, environment3)
end
it 'returns error if requested state is invalid' do
expect { resolve_environments(states: ['invalid']) }.to(
raise_error(Gitlab::Graphql::Errors::ArgumentError)
)
end
end
context 'when project is nil' do context 'when project is nil' do
subject { resolve(described_class, obj: nil, args: {}, ctx: { current_user: current_user }) } subject { resolve(described_class, obj: nil, args: {}, ctx: { current_user: current_user }) }
......
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