Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-10-31 06:09:06 +00:00
parent e89a412ba3
commit f791b9447b
19 changed files with 437 additions and 96 deletions

View File

@ -186,18 +186,12 @@ RSpec/ExpectChange:
# Offense count: 47
RSpec/ExpectGitlabTracking:
Exclude:
- 'ee/spec/controllers/projects/settings/operations_controller_spec.rb'
- 'ee/spec/requests/api/visual_review_discussions_spec.rb'
- 'ee/spec/services/epics/issue_promote_service_spec.rb'
- 'spec/controllers/groups/registry/repositories_controller_spec.rb'
- 'spec/controllers/projects/registry/repositories_controller_spec.rb'
- 'spec/controllers/projects/registry/tags_controller_spec.rb'
- 'spec/controllers/projects/settings/operations_controller_spec.rb'
- 'spec/controllers/registrations_controller_spec.rb'
- 'spec/lib/api/helpers_spec.rb'
- 'spec/requests/api/project_container_repositories_spec.rb'
- 'spec/support/shared_examples/controllers/trackable_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/discussions_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/tracking_shared_examples.rb'

View File

@ -395,7 +395,7 @@ class Environment < ApplicationRecord
# Overrides ReactiveCaching default to activate limit checking behind a FF
def reactive_cache_limit_enabled?
Feature.enabled?(:reactive_caching_limit_environment, project)
Feature.enabled?(:reactive_caching_limit_environment, project, default_enabled: true)
end
end

View File

@ -0,0 +1,5 @@
---
title: Move Personal Access Token API to Core
merge_request: 46145
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Make all Project Issue Boards API available even in CE
merge_request: 46137
author: Takuya Noguchi
type: changed

View File

@ -0,0 +1,6 @@
---
title: Limits the Deploy Boards data to 10 MB. This change is enabled by default behind
a feature flag
merge_request: 46043
author:
type: changed

View File

@ -4,4 +4,4 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34202
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/202633
group: group::configure
type: development
default_enabled: false
default_enabled: true

View File

@ -3,5 +3,5 @@ name: suggest_pipeline
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45926
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/267492
type: development
group: group::growth
group: group::expansion
default_enabled: true

View File

@ -4,13 +4,14 @@ group: unassigned
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
# Personal access tokens API **(ULTIMATE)**
# Personal access tokens API
You can read more about [personal access tokens](../user/profile/personal_access_tokens.md#personal-access-tokens).
## List personal access tokens
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227264) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.3.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227264) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.3.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/270200) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.6.
Get a list of personal access tokens.

View File

@ -237,6 +237,7 @@ module API
mount ::API::ProjectTemplates
mount ::API::Terraform::State
mount ::API::Terraform::StateVersion
mount ::API::PersonalAccessTokens
mount ::API::ProtectedBranches
mount ::API::ProtectedTags
mount ::API::Releases

View File

@ -42,6 +42,43 @@ module API
authorize!(:read_board, user_project)
present board, with: Entities::Board
end
desc 'Create a project board' do
detail 'This feature was introduced in 10.4'
success Entities::Board
end
params do
requires :name, type: String, desc: 'The board name'
end
post '/' do
authorize!(:admin_board, board_parent)
create_board
end
desc 'Update a project board' do
detail 'This feature was introduced in 11.0'
success Entities::Board
end
params do
use :update_params
end
put '/:board_id' do
authorize!(:admin_board, board_parent)
update_board
end
desc 'Delete a project board' do
detail 'This feature was introduced in 10.4'
success Entities::Board
end
delete '/:board_id' do
authorize!(:admin_board, board_parent)
delete_board
end
end
params do

View File

@ -10,6 +10,35 @@ module API
board_parent.boards.find(params[:board_id])
end
def create_board
forbidden! unless board_parent.multiple_issue_boards_available?
response =
::Boards::CreateService.new(board_parent, current_user, { name: params[:name] }).execute
present response.payload, with: Entities::Board
end
def update_board
service = ::Boards::UpdateService.new(board_parent, current_user, declared_params(include_missing: false))
service.execute(board)
if board.valid?
present board, with: Entities::Board
else
bad_request!("Failed to save board #{board.errors.messages}")
end
end
def delete_board
forbidden! unless board_parent.multiple_issue_boards_available?
destroy_conditionally!(board) do |board|
service = ::Boards::DestroyService.new(board_parent, current_user)
service.execute(board)
end
end
def board_lists
board.destroyable_lists
end
@ -62,6 +91,12 @@ module API
params :list_creation_params do
requires :label_id, type: Integer, desc: 'The ID of an existing label'
end
params :update_params do
# Configurable issue boards are not available in CE/EE Core.
# https://docs.gitlab.com/ee/user/project/issue_board.html#configurable-issue-boards
optional :name, type: String, desc: 'The board name'
end
end
end
end

View File

@ -4,6 +4,7 @@ module API
module Entities
class Board < Grape::Entity
expose :id
expose :name
expose :project, using: Entities::BasicProjectDetails
expose :lists, using: Entities::List do |board|

View File

@ -0,0 +1,59 @@
# frozen_string_literal: true
module API
class PersonalAccessTokens < ::API::Base
include ::API::PaginationParams
desc 'Get all Personal Access Tokens' do
detail 'This feature was added in GitLab 13.3'
success Entities::PersonalAccessToken
end
params do
optional :user_id, type: Integer, desc: 'User ID'
use :pagination
end
before do
authenticate!
restrict_non_admins! unless current_user.admin?
end
helpers do
def finder_params(current_user)
current_user.admin? ? { user: user(params[:user_id]) } : { user: current_user }
end
def user(user_id)
UserFinder.new(user_id).find_by_id
end
def restrict_non_admins!
return if params[:user_id].blank?
unauthorized! unless Ability.allowed?(current_user, :read_user_personal_access_tokens, user(params[:user_id]))
end
def find_token(id)
PersonalAccessToken.find(id) || not_found!
end
end
resources :personal_access_tokens do
get do
tokens = PersonalAccessTokensFinder.new(finder_params(current_user), current_user).execute
present paginate(tokens), with: Entities::PersonalAccessToken
end
delete ':id' do
service = ::PersonalAccessTokens::RevokeService.new(
current_user,
{ token: find_token(params[:id]) }
).execute
service.success? ? no_content! : bad_request!(nil)
end
end
end
end

View File

@ -64,12 +64,11 @@ RSpec.describe Groups::Registry::RepositoriesController do
context 'html format' do
let(:format) { :html }
it 'show index page' do
expect(Gitlab::Tracking).not_to receive(:event)
it 'show index page', :snowplow do
subject
expect(response).to have_gitlab_http_status(:ok)
expect_no_snowplow_event
end
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
# rubocop: disable RSpec/FactoriesInMigrationSpecs
RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do
RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover, :aggregate_failures do
let(:test_dir) { FileUploader.options['storage_path'] }
let(:filename) { 'image.png' }
@ -67,27 +67,35 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do
end
end
shared_examples 'migrates the file correctly' do
before do
described_class.new(legacy_upload).execute
end
shared_examples 'migrates the file correctly' do |remote|
it 'creates a new upload record correctly, updates the legacy upload note so that it references the file in the markdown, removes the attachment from the note model, removes the file, moves legacy uploads to the correct location, removes the upload record' do
expect(File.exist?(legacy_upload.absolute_path)).to be_truthy unless remote
described_class.new(legacy_upload).execute
it 'creates a new uplaod record correctly' do
expect(new_upload.secret).not_to be_nil
expect(new_upload.path).to end_with("#{new_upload.secret}/image.png")
expect(new_upload.path).to end_with("#{new_upload.secret}/#{filename}")
expect(new_upload.model_id).to eq(project.id)
expect(new_upload.model_type).to eq('Project')
expect(new_upload.uploader).to eq('FileUploader')
end
it 'updates the legacy upload note so that it references the file in the markdown' do
expected_path = File.join('/uploads', new_upload.secret, 'image.png')
expected_path = File.join('/uploads', new_upload.secret, filename)
expected_markdown = "some note \n ![image](#{expected_path})"
expect(note.reload.note).to eq(expected_markdown)
end
it 'removes the attachment from the note model' do
expect(note.reload.attachment.file).to be_nil
expect(note.reload.note).to eq(expected_markdown)
expect(note.attachment.file).to be_nil
if remote
expect(bucket.files.get(remote_file[:key])).to be_nil
connection = ::Fog::Storage.new(FileUploader.object_store_credentials)
expect(connection.get_object('uploads', new_upload.path)[:status]).to eq(200)
else
expect(File.exist?(legacy_upload.absolute_path)).to be_falsey
expected_path = File.join(test_dir, 'uploads', project.disk_path, new_upload.secret, filename)
expect(File.exist?(expected_path)).to be_truthy
end
expect { legacy_upload.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
@ -120,23 +128,6 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do
end
context 'when the upload is in local storage' do
shared_examples 'legacy local file' do
it 'removes the file correctly' do
expect(File.exist?(legacy_upload.absolute_path)).to be_truthy
described_class.new(legacy_upload).execute
expect(File.exist?(legacy_upload.absolute_path)).to be_falsey
end
it 'moves legacy uploads to the correct location' do
described_class.new(legacy_upload).execute
expected_path = File.join(test_dir, 'uploads', project.disk_path, new_upload.secret, filename)
expect(File.exist?(expected_path)).to be_truthy
end
end
context 'when the upload file does not exist on the filesystem' do
let(:legacy_upload) { create_upload(note, filename, false) }
@ -201,15 +192,11 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do
path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", model: note, mount_point: nil)
end
it_behaves_like 'migrates the file correctly'
it_behaves_like 'legacy local file'
it_behaves_like 'legacy upload deletion'
it_behaves_like 'migrates the file correctly', false
end
context 'when the file can be handled correctly' do
it_behaves_like 'migrates the file correctly'
it_behaves_like 'legacy local file'
it_behaves_like 'legacy upload deletion'
it_behaves_like 'migrates the file correctly', false
end
end
@ -217,17 +204,13 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do
context 'when the file belongs to a legacy project' do
let(:project) { legacy_project }
it_behaves_like 'migrates the file correctly'
it_behaves_like 'legacy local file'
it_behaves_like 'legacy upload deletion'
it_behaves_like 'migrates the file correctly', false
end
context 'when the file belongs to a hashed project' do
let(:project) { hashed_project }
it_behaves_like 'migrates the file correctly'
it_behaves_like 'legacy local file'
it_behaves_like 'legacy upload deletion'
it_behaves_like 'migrates the file correctly', false
end
end
@ -244,17 +227,13 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do
context 'when the file belongs to a legacy project' do
let(:project) { legacy_project }
it_behaves_like 'migrates the file correctly'
it_behaves_like 'legacy local file'
it_behaves_like 'legacy upload deletion'
it_behaves_like 'migrates the file correctly', false
end
context 'when the file belongs to a hashed project' do
let(:project) { hashed_project }
it_behaves_like 'migrates the file correctly'
it_behaves_like 'legacy local file'
it_behaves_like 'legacy upload deletion'
it_behaves_like 'migrates the file correctly', false
end
end
end
@ -272,23 +251,6 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do
stub_uploads_object_storage(FileUploader)
end
shared_examples 'legacy remote file' do
it 'removes the file correctly' do
# expect(bucket.files.get(remote_file[:key])).to be_nil
described_class.new(legacy_upload).execute
expect(bucket.files.get(remote_file[:key])).to be_nil
end
it 'moves legacy uploads to the correct remote location' do
described_class.new(legacy_upload).execute
connection = ::Fog::Storage.new(FileUploader.object_store_credentials)
expect(connection.get_object('uploads', new_upload.path)[:status]).to eq(200)
end
end
context 'when the upload file does not exist on the filesystem' do
it_behaves_like 'legacy upload deletion'
end
@ -300,9 +262,7 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do
let(:project) { legacy_project }
it_behaves_like 'migrates the file correctly'
it_behaves_like 'legacy remote file'
it_behaves_like 'legacy upload deletion'
it_behaves_like 'migrates the file correctly', true
end
context 'when the file belongs to a hashed project' do
@ -312,9 +272,7 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do
let(:project) { hashed_project }
it_behaves_like 'migrates the file correctly'
it_behaves_like 'legacy remote file'
it_behaves_like 'legacy upload deletion'
it_behaves_like 'migrates the file correctly', true
end
end
end

View File

@ -35,7 +35,46 @@ RSpec.describe API::Boards do
it_behaves_like 'group and project boards', "/projects/:id/boards"
describe "POST /projects/:id/boards/lists" do
describe "POST /projects/:id/boards" do
let(:url) { "/projects/#{board_parent.id}/boards" }
it 'creates a new issue board' do
post api(url, user), params: { name: 'foo' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq('foo')
end
it 'fails to create a new board' do
post api(url, user), params: { some_name: 'foo' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('name is missing')
end
end
describe "PUT /projects/:id/boards/:board_id" do
let(:url) { "/projects/#{board_parent.id}/boards/#{board.id}" }
it 'updates the issue board' do
put api(url, user), params: { name: 'changed board name' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq('changed board name')
end
end
describe "DELETE /projects/:id/boards/:board_id" do
let(:url) { "/projects/#{board_parent.id}/boards/#{board.id}" }
it 'delete the issue board' do
delete api(url, user)
expect(response).to have_gitlab_http_status(:no_content)
end
end
describe "POST /projects/:id/boards/:board_id/lists" do
let(:url) { "/projects/#{board_parent.id}/boards/#{board.id}/lists" }
it 'creates a new issue board list for group labels' do
@ -65,7 +104,7 @@ RSpec.describe API::Boards do
end
end
describe "POST /groups/:id/boards/lists" do
describe "POST /groups/:id/boards/:board_id/lists" do
let_it_be(:group) { create(:group) }
let_it_be(:board_parent) { create(:group, parent: group ) }
let(:url) { "/groups/#{board_parent.id}/boards/#{board.id}/lists" }

View File

@ -0,0 +1,112 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::PersonalAccessTokens do
let_it_be(:path) { '/personal_access_tokens' }
let_it_be(:token1) { create(:personal_access_token) }
let_it_be(:token2) { create(:personal_access_token) }
let_it_be(:current_user) { create(:user) }
describe 'GET /personal_access_tokens' do
context 'logged in as an Administrator' do
let_it_be(:current_user) { create(:admin) }
it 'returns all PATs by default' do
get api(path, current_user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq(PersonalAccessToken.all.count)
end
context 'filtered with user_id parameter' do
it 'returns only PATs belonging to that user' do
get api(path, current_user), params: { user_id: token1.user.id }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq(1)
expect(json_response.first['user_id']).to eq(token1.user.id)
end
end
context 'logged in as a non-Administrator' do
let_it_be(:current_user) { create(:user) }
let_it_be(:user) { create(:user) }
let_it_be(:token) { create(:personal_access_token, user: current_user)}
let_it_be(:other_token) { create(:personal_access_token, user: user) }
it 'returns all PATs belonging to the signed-in user' do
get api(path, current_user, personal_access_token: token)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq(1)
expect(json_response.map { |r| r['user_id'] }.uniq).to contain_exactly(current_user.id)
end
context 'filtered with user_id parameter' do
it 'returns PATs belonging to the specific user' do
get api(path, current_user, personal_access_token: token), params: { user_id: current_user.id }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq(1)
expect(json_response.map { |r| r['user_id'] }.uniq).to contain_exactly(current_user.id)
end
it 'is unauthorized if filtered by a user other than current_user' do
get api(path, current_user, personal_access_token: token), params: { user_id: user.id }
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
end
context 'not authenticated' do
it 'is forbidden' do
get api(path)
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
end
end
describe 'DELETE /personal_access_tokens/:id' do
let(:path) { "/personal_access_tokens/#{token1.id}" }
context 'when current_user is an administrator', :enable_admin_mode do
let_it_be(:admin_user) { create(:admin) }
let_it_be(:admin_token) { create(:personal_access_token, user: admin_user) }
let_it_be(:admin_path) { "/personal_access_tokens/#{admin_token.id}" }
it 'revokes a different users token' do
delete api(path, admin_user)
expect(response).to have_gitlab_http_status(:no_content)
expect(token1.reload.revoked?).to be true
end
it 'revokes their own token' do
delete api(admin_path, admin_user)
expect(response).to have_gitlab_http_status(:no_content)
end
end
context 'when current_user is not an administrator' do
let_it_be(:user_token) { create(:personal_access_token, user: current_user) }
let_it_be(:user_token_path) { "/personal_access_tokens/#{user_token.id}" }
it 'fails revokes a different users token' do
delete api(path, current_user)
expect(response).to have_gitlab_http_status(:bad_request)
end
it 'revokes their own token' do
delete api(user_token_path, current_user)
expect(response).to have_gitlab_http_status(:no_content)
end
end
end
end

View File

@ -117,15 +117,10 @@ RSpec.shared_examples 'discussions API' do |parent_type, noteable_type, id_name,
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'tracks a Notes::CreateService event' do
expect(Gitlab::Tracking).to receive(:event) do |category, action, data|
expect(category).to eq('Notes::CreateService')
expect(action).to eq('execute')
expect(data[:label]).to eq('note')
expect(data[:value]).to be_an(Integer)
end
it 'tracks a Notes::CreateService event', :snowplow do
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), params: { body: 'hi!' }
expect_snowplow_event(category: 'Notes::CreateService', action: 'execute', label: 'note', value: anything)
end
context 'with notes_create_service_tracking feature flag disabled' do

View File

@ -0,0 +1,94 @@
# frozen_string_literal: true
RSpec.shared_examples 'multiple and scoped issue boards' do |route_definition|
let(:root_url) { route_definition.gsub(":id", board_parent.id.to_s) }
context 'multiple issue boards' do
before do
board_parent.add_reporter(user)
stub_licensed_features(multiple_group_issue_boards: true)
end
describe "POST #{route_definition}" do
it 'creates a board' do
post api(root_url, user), params: { name: "new board" }
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/board', dir: "ee")
end
end
describe "PUT #{route_definition}/:board_id" do
let(:url) { "#{root_url}/#{board.id}" }
it 'updates a board' do
put api(url, user), params: { name: 'new name', weight: 4, labels: 'foo, bar' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/board', dir: "ee")
board.reload
expect(board.name).to eq('new name')
expect(board.weight).to eq(4)
expect(board.labels.map(&:title)).to contain_exactly('foo', 'bar')
end
it 'does not remove missing attributes from the board' do
expect { put api(url, user), params: { name: 'new name' } }
.to not_change { board.reload.assignee }
.and not_change { board.reload.milestone }
.and not_change { board.reload.weight }
.and not_change { board.reload.labels.map(&:title).sort }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/board', dir: "ee")
end
it 'allows removing optional attributes' do
put api(url, user), params: { name: 'new name', assignee_id: nil, milestone_id: nil, weight: nil, labels: nil }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/board', dir: "ee")
board.reload
expect(board.name).to eq('new name')
expect(board.assignee).to be_nil
expect(board.milestone).to be_nil
expect(board.weight).to be_nil
expect(board.labels).to be_empty
end
end
describe "DELETE #{route_definition}/:board_id" do
let(:url) { "#{root_url}/#{board.id}" }
it 'deletes a board' do
delete api(url, user)
expect(response).to have_gitlab_http_status(:no_content)
end
end
end
context 'with the scoped_issue_board-feature available' do
it 'returns the milestone when the `scoped_issue_board` feature is enabled' do
stub_licensed_features(scoped_issue_board: true)
get api(root_url, user)
expect(json_response.first["milestone"]).not_to be_nil
end
it 'hides the milestone when the `scoped_issue_board` feature is disabled' do
stub_licensed_features(scoped_issue_board: false)
get api(root_url, user)
expect(json_response.first["milestone"]).to be_nil
end
end
end