Commit 6e258795 authored by Kirstie Cook's avatar Kirstie Cook Committed by Heinrich Lee Yu

Add project environments graphql endpoint

Add id and name environment fields

Add search by name for environment
parent 7fc3b11a
# frozen_string_literal: true
module Resolvers
class EnvironmentsResolver < BaseResolver
argument :name, GraphQL::STRING_TYPE,
required: false,
description: 'Name of the environment'
argument :search, GraphQL::STRING_TYPE,
required: false,
description: 'Search query'
type Types::EnvironmentType, null: true
alias_method :project, :object
def resolve(**args)
return unless project.present?
EnvironmentsFinder.new(project, context[:current_user], args).find
end
end
end
# frozen_string_literal: true
module Types
class EnvironmentType < BaseObject
graphql_name 'Environment'
description 'Describes where code is deployed for a project'
authorize :read_environment
field :name, GraphQL::STRING_TYPE, null: false,
description: 'Human-readable name of the environment'
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the environment'
end
end
......@@ -138,6 +138,12 @@ module Types
description: 'Issues of the project',
resolver: Resolvers::IssuesResolver
field :environments,
Types::EnvironmentType.connection_type,
null: true,
description: 'Environments of the project',
resolver: Resolvers::EnvironmentsResolver
field :issue,
Types::IssueType,
null: true,
......
---
title: Get Project's environment names via GraphQL
merge_request: 22932
author:
type: added
......@@ -1412,6 +1412,56 @@ enum EntryType {
tree
}
"""
Describes where code is deployed for a project
"""
type Environment {
"""
ID of the environment
"""
id: ID!
"""
Human-readable name of the environment
"""
name: String!
}
"""
The connection type for Environment.
"""
type EnvironmentConnection {
"""
A list of edges.
"""
edges: [EnvironmentEdge]
"""
A list of nodes.
"""
nodes: [Environment]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type EnvironmentEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: Environment
}
"""
Represents an epic.
"""
......@@ -4706,6 +4756,41 @@ type Project {
"""
descriptionHtml: String
"""
Environments of the project
"""
environments(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
"""
Name of the environment
"""
name: String
"""
Search query
"""
search: String
): EnvironmentConnection
"""
Number of times the project has been forked
"""
......
......@@ -406,6 +406,79 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "environments",
"description": "Environments of the project",
"args": [
{
"name": "name",
"description": "Name of the environment",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "search",
"description": "Search query",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "EnvironmentConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "forksCount",
"description": "Number of times the project has been forked",
......@@ -15431,6 +15504,167 @@
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "EnvironmentConnection",
"description": "The connection type for Environment.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "EnvironmentEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Environment",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pageInfo",
"description": "Information to aid in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PageInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "EnvironmentEdge",
"description": "An edge in a connection.",
"fields": [
{
"name": "cursor",
"description": "A cursor for use in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "node",
"description": "The item at the end of the edge.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Environment",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Environment",
"description": "Describes where code is deployed for a project",
"fields": [
{
"name": "id",
"description": "ID of the environment",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "name",
"description": "Human-readable name of the environment",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SentryDetailedError",
......
......@@ -232,6 +232,15 @@ Autogenerated return type of DestroySnippet
| `replyId` | ID! | ID used to reply to this discussion |
| `createdAt` | Time! | Timestamp of the discussion's creation |
## Environment
Describes where code is deployed for a project
| Name | Type | Description |
| --- | ---- | ---------- |
| `name` | String! | Human-readable name of the environment |
| `id` | ID! | ID of the environment |
## Epic
Represents an epic.
......
# frozen_string_literal: true
require 'spec_helper'
describe Resolvers::EnvironmentsResolver do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
context "with a group" do
let(:group) { create(:group) }
let(:project) { create(:project, :public, group: group) }
let!(:environment1) { create(:environment, name: 'production', project: project) }
let!(:environment2) { create(:environment, name: 'test', project: project) }
let!(:environment3) { create(:environment, name: 'test2', project: project) }
before do
group.add_developer(current_user)
end
describe '#resolve' do
it 'finds all environments' do
expect(resolve_environments).to contain_exactly(environment1, environment2, environment3)
end
context 'with name' do
it 'finds a specific environment with name' do
expect(resolve_environments(name: environment1.name)).to contain_exactly(environment1)
end
end
context 'with search' do
it 'searches environment by name' do
expect(resolve_environments(search: 'test')).to contain_exactly(environment2, environment3)
end
context 'when the search term does not match any environments' do
it 'is empty' do
expect(resolve_environments(search: 'nonsense')).to be_empty
end
end
end
context 'when project is nil' do
subject { resolve(described_class, obj: nil, args: {}, ctx: { current_user: current_user }) }
it { is_expected.to be_nil }
end
end
end
def resolve_environments(args = {}, context = { current_user: current_user })
resolve(described_class, obj: project, args: args, ctx: context)
end
end
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['Environment'] do
it { expect(described_class.graphql_name).to eq('Environment') }
it 'has the expected fields' do
expected_fields = %w[
name id
]
is_expected.to have_graphql_fields(*expected_fields)
end
it { is_expected.to require_graphql_authorizations(:read_environment) }
end
......@@ -23,7 +23,7 @@ describe GitlabSchema.types['Project'] do
only_allow_merge_if_all_discussions_are_resolved printing_merge_request_link_enabled
namespace group statistics repository merge_requests merge_request issues
issue pipelines removeSourceBranchAfterMerge sentryDetailedError snippets
grafanaIntegration autocloseReferencedIssues suggestion_commit_message
grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments
]
is_expected.to include_graphql_fields(*expected_fields)
......@@ -70,4 +70,11 @@ describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_type(Types::GrafanaIntegrationType) }
it { is_expected.to have_graphql_resolver(Resolvers::Projects::GrafanaIntegrationResolver) }
end
describe 'environments field' do
subject { described_class.fields['environments'] }
it { is_expected.to have_graphql_type(Types::EnvironmentType.connection_type) }
it { is_expected.to have_graphql_resolver(Resolvers::EnvironmentsResolver) }
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