Commit af6a7a19 authored by Alper Akgun's avatar Alper Akgun

Merge branch '292658-include-keyword-for-cilint-graphql' into 'master'

Add project scope to ci clint graphql endpoint

See merge request gitlab-org/gitlab!50418
parents cfb5cecd 1201f46a
#import "~/pipelines/graphql/queries/pipeline_stages_connection.fragment.graphql" #import "~/pipelines/graphql/queries/pipeline_stages_connection.fragment.graphql"
query getCiConfigData($content: String!) { query getCiConfigData($projectPath: ID!, $content: String!) {
ciConfig(content: $content) { ciConfig(projectPath: $projectPath, content: $content) {
errors errors
status status
stages { stages {
......
...@@ -105,6 +105,7 @@ export default { ...@@ -105,6 +105,7 @@ export default {
}, },
variables() { variables() {
return { return {
projectPath: this.projectPath,
content: this.contentModel, content: this.contentModel,
}; };
}, },
......
...@@ -3,14 +3,27 @@ ...@@ -3,14 +3,27 @@
module Resolvers module Resolvers
module Ci module Ci
class ConfigResolver < BaseResolver class ConfigResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
include ResolvesProject
type Types::Ci::Config::ConfigType, null: true type Types::Ci::Config::ConfigType, null: true
authorize :read_pipeline
argument :project_path, GraphQL::ID_TYPE,
required: true,
description: 'The project of the CI config'
argument :content, GraphQL::STRING_TYPE, argument :content, GraphQL::STRING_TYPE,
required: true, required: true,
description: 'Contents of .gitlab-ci.yml' description: 'Contents of .gitlab-ci.yml'
def resolve(content:) def resolve(project_path:, content:)
result = ::Gitlab::Ci::YamlProcessor.new(content).execute project = authorized_find!(project_path: project_path)
result = ::Gitlab::Ci::YamlProcessor.new(content, project: project,
user: current_user,
sha: project.repository.commit.sha).execute
response = if result.errors.empty? response = if result.errors.empty?
{ {
...@@ -55,6 +68,10 @@ module Resolvers ...@@ -55,6 +68,10 @@ module Resolvers
.group_by { |group| group[:stage] } .group_by { |group| group[:stage] }
.map { |name, groups| { name: name, groups: groups } } .map { |name, groups| { name: name, groups: groups } }
end end
def find_object(project_path:)
resolve_project(full_path: project_path)
end
end end
end end
end end
---
title: Add project scope to ci clint graphql endpoint
merge_request: 50418
author:
type: fixed
...@@ -19387,6 +19387,11 @@ type Query { ...@@ -19387,6 +19387,11 @@ type Query {
Contents of .gitlab-ci.yml Contents of .gitlab-ci.yml
""" """
content: String! content: String!
"""
The project of the CI config
"""
projectPath: ID!
): CiConfig ): CiConfig
""" """
......
...@@ -56517,6 +56517,20 @@ ...@@ -56517,6 +56517,20 @@
"name": "ciConfig", "name": "ciConfig",
"description": "Get linted and processed contents of a CI config. Should not be requested more than once per request.", "description": "Get linted and processed contents of a CI config. Should not be requested more than once per request.",
"args": [ "args": [
{
"name": "projectPath",
"description": "The project of the CI config",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{ {
"name": "content", "name": "content",
"description": "Contents of .gitlab-ci.yml", "description": "Contents of .gitlab-ci.yml",
...@@ -142,7 +142,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { ...@@ -142,7 +142,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
beforeEach(() => { beforeEach(() => {
mockBlobContentData = jest.fn(); mockBlobContentData = jest.fn();
mockCiConfigData = jest.fn().mockResolvedValue(mockCiConfigQueryResponse); mockCiConfigData = jest.fn();
}); });
afterEach(() => { afterEach(() => {
...@@ -413,9 +413,13 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { ...@@ -413,9 +413,13 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
}); });
}); });
describe('displays fetch content errors', () => { describe('when queries are called', () => {
it('no error is shown when data is set', async () => { beforeEach(() => {
mockBlobContentData.mockResolvedValue(mockCiYml); mockBlobContentData.mockResolvedValue(mockCiYml);
mockCiConfigData.mockResolvedValue(mockCiConfigQueryResponse);
});
it('no error is shown when data is set', async () => {
createComponentWithApollo(); createComponentWithApollo();
await waitForPromises(); await waitForPromises();
...@@ -424,6 +428,17 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { ...@@ -424,6 +428,17 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
expect(findEditorLite().attributes('value')).toBe(mockCiYml); expect(findEditorLite().attributes('value')).toBe(mockCiYml);
}); });
it('ci config query is called with correct variables', async () => {
createComponentWithApollo();
await waitForPromises();
expect(mockCiConfigData).toHaveBeenCalledWith({
content: mockCiYml,
projectPath: mockProjectPath,
});
});
it('shows a 404 error message', async () => { it('shows a 404 error message', async () => {
mockBlobContentData.mockRejectedValueOnce({ mockBlobContentData.mockRejectedValueOnce({
response: { response: {
......
...@@ -13,6 +13,15 @@ RSpec.describe Resolvers::Ci::ConfigResolver do ...@@ -13,6 +13,15 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
allow(::Gitlab::Ci::YamlProcessor).to receive(:new).and_return(yaml_processor_double) allow(::Gitlab::Ci::YamlProcessor).to receive(:new).and_return(yaml_processor_double)
end end
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
subject(:response) do
resolve(described_class,
args: { project_path: project.full_path, content: content },
ctx: { current_user: user })
end
context 'with a valid .gitlab-ci.yml' do context 'with a valid .gitlab-ci.yml' do
let(:fake_result) do let(:fake_result) do
::Gitlab::Ci::YamlProcessor::Result.new( ::Gitlab::Ci::YamlProcessor::Result.new(
...@@ -27,8 +36,6 @@ RSpec.describe Resolvers::Ci::ConfigResolver do ...@@ -27,8 +36,6 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
end end
it 'lints the ci config file' do it 'lints the ci config file' do
response = resolve(described_class, args: { content: content }, ctx: {})
expect(response[:status]).to eq(:valid) expect(response[:status]).to eq(:valid)
expect(response[:errors]).to be_empty expect(response[:errors]).to be_empty
end end
...@@ -46,8 +53,6 @@ RSpec.describe Resolvers::Ci::ConfigResolver do ...@@ -46,8 +53,6 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
end end
it 'responds with errors about invalid syntax' do it 'responds with errors about invalid syntax' do
response = resolve(described_class, args: { content: content }, ctx: {})
expect(response[:status]).to eq(:invalid) expect(response[:status]).to eq(:invalid)
expect(response[:errors]).to eq(['Invalid configuration format']) expect(response[:errors]).to eq(['Invalid configuration format'])
end end
......
...@@ -7,7 +7,8 @@ RSpec.describe 'Query.ciConfig' do ...@@ -7,7 +7,8 @@ RSpec.describe 'Query.ciConfig' do
subject(:post_graphql_query) { post_graphql(query, current_user: user) } subject(:post_graphql_query) { post_graphql(query, current_user: user) }
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
let_it_be(:content) do let_it_be(:content) do
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml')) File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml'))
...@@ -16,7 +17,7 @@ RSpec.describe 'Query.ciConfig' do ...@@ -16,7 +17,7 @@ RSpec.describe 'Query.ciConfig' do
let(:query) do let(:query) do
%( %(
query { query {
ciConfig(content: "#{content}") { ciConfig(projectPath: "#{project.full_path}", content: "#{content}") {
status status
errors errors
stages { stages {
...@@ -47,13 +48,15 @@ RSpec.describe 'Query.ciConfig' do ...@@ -47,13 +48,15 @@ RSpec.describe 'Query.ciConfig' do
) )
end end
before do it_behaves_like 'a working graphql query' do
post_graphql_query before do
post_graphql_query
end
end end
it_behaves_like 'a working graphql query'
it 'returns the correct structure' do it 'returns the correct structure' do
post_graphql_query
expect(graphql_data['ciConfig']).to eq( expect(graphql_data['ciConfig']).to eq(
"status" => "VALID", "status" => "VALID",
"errors" => [], "errors" => [],
...@@ -114,4 +117,75 @@ RSpec.describe 'Query.ciConfig' do ...@@ -114,4 +117,75 @@ RSpec.describe 'Query.ciConfig' do
} }
) )
end end
context 'when the config file includes other files' do
let_it_be(:content) do
YAML.dump(
include: 'other_file.yml',
rspec: {
script: 'rspec'
}
)
end
before do
allow_next_instance_of(Repository) do |repository|
allow(repository).to receive(:blob_data_at).with(an_instance_of(String), 'other_file.yml') do
YAML.dump(
build: {
script: 'build'
}
)
end
end
post_graphql_query
end
it_behaves_like 'a working graphql query'
it 'returns the correct structure with included files' do
expect(graphql_data['ciConfig']).to eq(
"status" => "VALID",
"errors" => [],
"stages" =>
{
"nodes" =>
[
{
"name" => "test",
"groups" =>
{
"nodes" =>
[
{
"name" => "build",
"size" => 1,
"jobs" =>
{
"nodes" =>
[
{ "name" => "build", "groupName" => "build", "stage" => "test", "needs" => { "nodes" => [] } }
]
}
},
{
"name" => "rspec",
"size" => 1,
"jobs" =>
{
"nodes" =>
[
{ "name" => "rspec", "groupName" => "rspec", "stage" => "test", "needs" => { "nodes" => [] } }
]
}
}
]
}
}
]
}
)
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