diff options
Diffstat (limited to 'lib')
40 files changed, 583 insertions, 277 deletions
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index ed723b94cfd..789f45489eb 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -1,5 +1,7 @@ module API class AccessRequests < Grape::API + include PaginationParams + before { authenticate! } helpers ::API::Helpers::MembersHelpers @@ -13,6 +15,9 @@ module API detail 'This feature was introduced in GitLab 8.11.' success Entities::AccessRequester end + params do + use :pagination + end get ":id/access_requests" do source = find_source(source_type, params[:id]) diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb index e9ccba3b465..58a4df54bea 100644 --- a/lib/api/award_emoji.rb +++ b/lib/api/award_emoji.rb @@ -1,5 +1,7 @@ module API class AwardEmoji < Grape::API + include PaginationParams + before { authenticate! } AWARDABLES = %w[issue merge_request snippet] @@ -21,6 +23,9 @@ module API detail 'This feature was introduced in 8.9' success Entities::AwardEmoji end + params do + use :pagination + end get endpoint do if can_read_awardable? awards = paginate(awardable.award_emoji) diff --git a/lib/api/builds.rb b/lib/api/builds.rb index 67adca6605f..af61be343be 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -1,6 +1,7 @@ module API - # Projects builds API class Builds < Grape::API + include PaginationParams + before { authenticate! } params do @@ -28,6 +29,7 @@ module API end params do use :optional_scope + use :pagination end get ':id/builds' do builds = user_project.builds.order('id DESC') @@ -41,8 +43,9 @@ module API success Entities::Build end params do - requires :sha, type: String, desc: 'The SHA id of a commit' + requires :sha, type: String, desc: 'The SHA id of a commit' use :optional_scope + use :pagination end get ':id/repository/commits/:sha/builds' do authorize_read_builds! diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 492884d162b..4bbdf06a49c 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -1,9 +1,10 @@ require 'mime/types' module API - # Project commit statuses API class CommitStatuses < Grape::API resource :projects do + include PaginationParams + before { authenticate! } desc "Get a commit's statuses" do @@ -16,6 +17,7 @@ module API optional :stage, type: String, desc: 'The stage' optional :name, type: String, desc: 'The name' optional :all, type: String, desc: 'Show all statuses, default: false' + use :pagination end get ':id/repository/commits/:sha/statuses' do authorize!(:read_commit_status, user_project) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index d5dfb8d00be..006d5f9f44e 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -22,7 +22,7 @@ module API expose :provider, :extern_uid end - class UserFull < User + class UserPublic < User expose :last_sign_in_at expose :confirmed_at expose :email @@ -34,7 +34,7 @@ module API expose :external end - class UserLogin < UserFull + class UserWithPrivateToken < UserPublic expose :private_token end @@ -174,6 +174,7 @@ module API class RepoCommit < Grape::Entity expose :id, :short_id, :title, :author_name, :author_email, :created_at + expose :committer_name, :committer_email expose :safe_message, as: :message end @@ -288,7 +289,7 @@ module API end class SSHKeyWithUser < SSHKey - expose :user, using: Entities::UserFull + expose :user, using: Entities::UserPublic end class Note < Grape::Entity diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 5315c22e1e4..fbf7513302b 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -1,5 +1,7 @@ module API class Groups < Grape::API + include PaginationParams + before { authenticate! } helpers do @@ -21,6 +23,7 @@ module API optional :search, type: String, desc: 'Search for a specific group' optional :order_by, type: String, values: %w[name path], default: 'name', desc: 'Order by name or path' optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)' + use :pagination end get do groups = if current_user.admin @@ -41,6 +44,9 @@ module API desc 'Get list of owned groups for authenticated user' do success Entities::Group end + params do + use :pagination + end get '/owned' do groups = current_user.owned_groups present paginate(groups), with: Entities::Group, user: current_user @@ -110,11 +116,13 @@ module API desc 'Get a list of projects in this group.' do success Entities::Project end + params do + use :pagination + end get ":id/projects" do group = find_group!(params[:id]) projects = GroupProjectsFinder.new(group).execute(current_user) - projects = paginate projects - present projects, with: Entities::Project, user: current_user + present paginate(projects), with: Entities::Project, user: current_user end desc 'Transfer a project to the group namespace. Available only for admin.' do diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 7f94ede7940..8db2678b368 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -44,11 +44,14 @@ module API return nil end - identifier = sudo_identifier() + identifier = sudo_identifier - # If the sudo is the current user do nothing - if identifier && !(@current_user.id == identifier || @current_user.username == identifier) + if identifier + # We check for private_token because we cannot allow PAT to be used forbidden!('Must be admin to use sudo') unless @current_user.is_admin? + forbidden!('Private token must be specified in order to use sudo') unless private_token_used? + + @impersonator = @current_user @current_user = User.by_username_or_id(identifier) not_found!("No user id or username for: #{identifier}") if @current_user.nil? end @@ -217,22 +220,6 @@ module API end end - def issuable_order_by - if params["order_by"] == 'updated_at' - 'updated_at' - else - 'created_at' - end - end - - def issuable_sort - if params["sort"] == 'asc' - :asc - else - :desc - end - end - def filter_by_iid(items, iid) items.where(iid: iid) end @@ -399,6 +386,10 @@ module API links.join(', ') end + def private_token_used? + private_token == @current_user.private_token + end + def secret_token Gitlab::Shell.secret_token end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 049b4fb214c..c9124649cbb 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -1,6 +1,7 @@ module API - # Issues API class Issues < Grape::API + include PaginationParams + before { authenticate! } helpers do @@ -20,77 +21,68 @@ module API issues.includes(:milestone).where('milestones.title' => milestone) end - def issue_params - new_params = declared(params, include_parent_namespace: false, include_missing: false).to_h - new_params = new_params.with_indifferent_access - new_params.delete(:id) - new_params.delete(:issue_id) + params :issues_params do + optional :labels, type: String, desc: 'Comma-separated list of label names' + optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at', + desc: 'Return issues ordered by `created_at` or `updated_at` fields.' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return issues sorted in `asc` or `desc` order.' + use :pagination + end - new_params + params :issue_params do + optional :description, type: String, desc: 'The description of an issue' + optional :assignee_id, type: Integer, desc: 'The ID of a user to assign issue' + optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue' + optional :labels, type: String, desc: 'Comma-separated list of label names' + optional :due_date, type: String, desc: 'Date time string in the format YEAR-MONTH-DAY' + optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential' + optional :state_event, type: String, values: %w[open close], + desc: 'State of the issue' end end resource :issues do - # Get currently authenticated user's issues - # - # Parameters: - # state (optional) - Return "opened" or "closed" issues - # labels (optional) - Comma-separated list of label names - # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` - # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - # - # Example Requests: - # GET /issues - # GET /issues?state=opened - # GET /issues?state=closed - # GET /issues?labels=foo - # GET /issues?labels=foo,bar - # GET /issues?labels=foo,bar&state=opened + desc "Get currently authenticated user's issues" do + success Entities::Issue + end + params do + optional :state, type: String, values: %w[opened closed all], default: 'all', + desc: 'Return opened, closed, or all issues' + use :issues_params + end get do issues = current_user.issues.inc_notes_with_associations - issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? + issues = filter_issues_state(issues, params[:state]) issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? - issues = issues.reorder(issuable_order_by => issuable_sort) + issues = issues.reorder(params[:order_by] => params[:sort]) present paginate(issues), with: Entities::Issue, current_user: current_user end end + params do + requires :id, type: String, desc: 'The ID of a group' + end resource :groups do - # Get a list of group issues - # - # Parameters: - # id (required) - The ID of a group - # state (optional) - Return "opened" or "closed" issues - # labels (optional) - Comma-separated list of label names - # milestone (optional) - Milestone title - # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` - # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - # - # Example Requests: - # GET /groups/:id/issues - # GET /groups/:id/issues?state=opened - # GET /groups/:id/issues?state=closed - # GET /groups/:id/issues?labels=foo - # GET /groups/:id/issues?labels=foo,bar - # GET /groups/:id/issues?labels=foo,bar&state=opened - # GET /groups/:id/issues?milestone=1.0.0 - # GET /groups/:id/issues?milestone=1.0.0&state=closed + desc 'Get a list of group issues' do + success Entities::Issue + end + params do + optional :state, type: String, values: %w[opened closed all], default: 'opened', + desc: 'Return opened, closed, or all issues' + use :issues_params + end get ":id/issues" do - group = find_group!(params[:id]) + group = find_group!(params.delete(:id)) - params[:state] ||= 'opened' params[:group_id] = group.id params[:milestone_title] = params.delete(:milestone) params[:label_name] = params.delete(:labels) - if params[:order_by] || params[:sort] - # The Sortable concern takes 'created_desc', not 'created_at_desc' (for example) - params[:sort] = "#{issuable_order_by.sub('_at', '')}_#{issuable_sort}" - end - issues = IssuesFinder.new(current_user, params).execute + issues = issues.reorder(params[:order_by] => params[:sort]) present paginate(issues), with: Entities::Issue, current_user: current_user end end @@ -98,32 +90,19 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects do - # Get a list of project issues - # - # Parameters: - # id (required) - The ID of a project - # iid (optional) - Return the project issue having the given `iid` - # state (optional) - Return "opened" or "closed" issues - # labels (optional) - Comma-separated list of label names - # milestone (optional) - Milestone title - # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` - # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - # - # Example Requests: - # GET /projects/:id/issues - # GET /projects/:id/issues?state=opened - # GET /projects/:id/issues?state=closed - # GET /projects/:id/issues?labels=foo - # GET /projects/:id/issues?labels=foo,bar - # GET /projects/:id/issues?labels=foo,bar&state=opened - # GET /projects/:id/issues?milestone=1.0.0 - # GET /projects/:id/issues?milestone=1.0.0&state=closed - # GET /issues?iid=42 + desc 'Get a list of project issues' do + success Entities::Issue + end + params do + optional :state, type: String, values: %w[opened closed all], default: 'all', + desc: 'Return opened, closed, or all issues' + optional :iid, type: Integer, desc: 'The IID of the issue' + use :issues_params + end get ":id/issues" do issues = IssuesFinder.new(current_user, project_id: user_project.id).execute.inc_notes_with_associations - issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? + issues = filter_issues_state(issues, params[:state]) issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? issues = filter_by_iid(issues, params[:iid]) unless params[:iid].nil? @@ -131,50 +110,49 @@ module API issues = filter_issues_milestone(issues, params[:milestone]) end - issues = issues.reorder(issuable_order_by => issuable_sort) - + issues = issues.reorder(params[:order_by] => params[:sort]) present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project end - # Get a single project issue - # - # Parameters: - # id (required) - The ID of a project - # issue_id (required) - The ID of a project issue - # Example Request: - # GET /projects/:id/issues/:issue_id + desc 'Get a single project issue' do + success Entities::Issue + end + params do + requires :issue_id, type: Integer, desc: 'The ID of a project issue' + end get ":id/issues/:issue_id" do - @issue = find_project_issue(params[:issue_id]) - present @issue, with: Entities::Issue, current_user: current_user, project: user_project + issue = find_project_issue(params[:issue_id]) + present issue, with: Entities::Issue, current_user: current_user, project: user_project end - # Create a new project issue - # - # Parameters: - # id (required) - The ID of a project - # title (required) - The title of an issue - # description (optional) - The description of an issue - # assignee_id (optional) - The ID of a user to assign issue - # milestone_id (optional) - The ID of a milestone to assign issue - # labels (optional) - The labels of an issue - # created_at (optional) - Date time string, ISO 8601 formatted - # due_date (optional) - Date time string in the format YEAR-MONTH-DAY - # confidential (optional) - Boolean parameter if the issue should be confidential - # Example Request: - # POST /projects/:id/issues + desc 'Create a new project issue' do + success Entities::Issue + end + params do + requires :title, type: String, desc: 'The title of an issue' + optional :created_at, type: DateTime, + desc: 'Date time when the issue was created. Available only for admins and project owners.' + optional :merge_request_for_resolving_discussions, type: Integer, + desc: 'The IID of a merge request for which to resolve discussions' + use :issue_params + end post ':id/issues' do - required_attributes! [:title] - - keys = [:title, :description, :assignee_id, :milestone_id, :due_date, :confidential, :labels] - keys << :created_at if current_user.admin? || user_project.owner == current_user - attrs = attributes_for_keys(keys) + # Setting created_at time only allowed for admins and project owners + unless current_user.admin? || user_project.owner == current_user + params.delete(:created_at) + end - # Convert and filter out invalid confidential flags - attrs['confidential'] = to_boolean(attrs['confidential']) - attrs.delete('confidential') if attrs['confidential'].nil? + issue_params = declared_params(include_missing: false) - issue = ::Issues::CreateService.new(user_project, current_user, attrs.merge(request: request, api: true)).execute + if merge_request_iid = params[:merge_request_for_resolving_discussions] + issue_params[:merge_request_for_resolving_discussions] = MergeRequestsFinder.new(current_user, project_id: user_project.id). + execute. + find_by(iid: merge_request_iid) + end + issue = ::Issues::CreateService.new(user_project, + current_user, + issue_params.merge(request: request, api: true)).execute if issue.spam? render_api_error!({ error: 'Spam detected' }, 400) end @@ -190,31 +168,26 @@ module API success Entities::Issue end params do - requires :id, type: String, desc: 'The ID of a project' - requires :issue_id, type: Integer, desc: "The ID of a project issue" - optional :title, type: String, desc: 'The new title of the issue' - optional :description, type: String, desc: 'The description of an issue' - optional :assignee_id, type: Integer, desc: 'The ID of a user to assign issue' - optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue' - optional :labels, type: String, desc: 'The labels of an issue' - optional :state_event, type: String, values: ['close', 'reopen'], desc: 'The state event of an issue' - # TODO 9.0, use the Grape DateTime type here - optional :updated_at, type: String, desc: 'Date time string, ISO 8601 formatted' - optional :due_date, type: String, desc: 'Date time string in the format YEAR-MONTH-DAY' - # TODO 9.0, use the Grape boolean type here - optional :confidential, type: String, desc: 'Boolean parameter if the issue should be confidential' + requires :issue_id, type: Integer, desc: 'The ID of a project issue' + optional :title, type: String, desc: 'The title of an issue' + optional :updated_at, type: DateTime, + desc: 'Date time when the issue was updated. Available only for admins and project owners.' + use :issue_params + at_least_one_of :title, :description, :assignee_id, :milestone_id, + :labels, :created_at, :due_date, :confidential, :state_event end put ':id/issues/:issue_id' do - issue = user_project.issues.find(params[:issue_id]) + issue = user_project.issues.find(params.delete(:issue_id)) authorize! :update_issue, issue - # Convert and filter out invalid confidential flags - params[:confidential] = to_boolean(params[:confidential]) - params.delete(:confidential) if params[:confidential].nil? - - params.delete(:updated_at) unless current_user.admin? || user_project.owner == current_user + # Setting created_at time only allowed for admins and project owners + unless current_user.admin? || user_project.owner == current_user + params.delete(:updated_at) + end - issue = ::Issues::UpdateService.new(user_project, current_user, issue_params).execute(issue) + issue = ::Issues::UpdateService.new(user_project, + current_user, + declared_params(include_missing: false)).execute(issue) if issue.valid? present issue, with: Entities::Issue, current_user: current_user, project: user_project @@ -223,19 +196,19 @@ module API end end - # Move an existing issue - # - # Parameters: - # id (required) - The ID of a project - # issue_id (required) - The ID of a project issue - # to_project_id (required) - The ID of the new project - # Example Request: - # POST /projects/:id/issues/:issue_id/move + desc 'Move an existing issue' do + success Entities::Issue + end + params do + requires :issue_id, type: Integer, desc: 'The ID of a project issue' + requires :to_project_id, type: Integer, desc: 'The ID of the new project' + end post ':id/issues/:issue_id/move' do - required_attributes! [:to_project_id] + issue = user_project.issues.find_by(id: params[:issue_id]) + not_found!('Issue') unless issue - issue = user_project.issues.find(params[:issue_id]) - new_project = Project.find(params[:to_project_id]) + new_project = Project.find_by(id: params[:to_project_id]) + not_found!('Project') unless new_project begin issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project) @@ -245,16 +218,13 @@ module API end end - # - # Delete a project issue - # - # Parameters: - # id (required) - The ID of a project - # issue_id (required) - The ID of a project issue - # Example Request: - # DELETE /projects/:id/issues/:issue_id + desc 'Delete a project issue' + params do + requires :issue_id, type: Integer, desc: 'The ID of a project issue' + end delete ":id/issues/:issue_id" do issue = user_project.issues.find_by(id: params[:issue_id]) + not_found!('Issue') unless issue authorize!(:destroy_issue, issue) issue.destroy diff --git a/lib/api/members.rb b/lib/api/members.rb index 2d4d5cedf20..d85f1f78cd6 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -1,5 +1,7 @@ module API class Members < Grape::API + include PaginationParams + before { authenticate! } helpers ::API::Helpers::MembersHelpers @@ -14,15 +16,15 @@ module API end params do optional :query, type: String, desc: 'A query string to search for members' + use :pagination end get ":id/members" do source = find_source(source_type, params[:id]) users = source.users users = users.merge(User.search(params[:query])) if params[:query] - users = paginate(users) - present users, with: Entities::Member, source: source + present paginate(users), with: Entities::Member, source: source end desc 'Gets a member of a group or project.' do diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 97baebc1d27..253460830ff 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -1,5 +1,7 @@ module API class MergeRequests < Grape::API + include PaginationParams + DEPRECATION_MESSAGE = 'This endpoint is deprecated and will be removed in GitLab 9.0.'.freeze before { authenticate! } @@ -42,6 +44,7 @@ module API optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Return merge requests sorted in `asc` or `desc` order.' optional :iid, type: Array[Integer], desc: 'The IID of the merge requests' + use :pagination end get ":id/merge_requests" do authorize! :read_merge_request, user_project @@ -169,7 +172,7 @@ module API optional :should_remove_source_branch, type: Boolean, desc: 'When true, the source branch will be deleted if possible' optional :merge_when_build_succeeds, type: Boolean, - desc: 'When true, this merge request will be merged when the build succeeds' + desc: 'When true, this merge request will be merged when the pipeline succeeds' optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' end put "#{path}/merge" do @@ -193,17 +196,19 @@ module API } if params[:merge_when_build_succeeds] && merge_request.head_pipeline && merge_request.head_pipeline.active? - ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). - execute(merge_request) + ::MergeRequests::MergeWhenPipelineSucceedsService + .new(merge_request.target_project, current_user, merge_params) + .execute(merge_request) else - ::MergeRequests::MergeService.new(merge_request.target_project, current_user, merge_params). - execute(merge_request) + ::MergeRequests::MergeService + .new(merge_request.target_project, current_user, merge_params) + .execute(merge_request) end present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project end - desc 'Cancel merge if "Merge when build succeeds" is enabled' do + desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do success Entities::MergeRequest end post "#{path}/cancel_merge_when_build_succeeds" do @@ -211,13 +216,18 @@ module API unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user) - ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user).cancel(merge_request) + ::MergeRequest::MergeWhenPipelineSucceedsService + .new(merge_request.target_project, current_user) + .cancel(merge_request) end desc 'Get the comments of a merge request' do detail 'Duplicate. DEPRECATED and WILL BE REMOVED in 9.0' success Entities::MRNote end + params do + use :pagination + end get "#{path}/comments" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) @@ -255,6 +265,9 @@ module API desc 'List issues that will be closed on merge' do success Entities::MRNote end + params do + use :pagination + end get "#{path}/closes_issues" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 50d6109be3d..3c373a84ec5 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -1,6 +1,7 @@ module API - # Milestones API class Milestones < Grape::API + include PaginationParams + before { authenticate! } helpers do @@ -30,6 +31,7 @@ module API optional :state, type: String, values: %w[active closed all], default: 'all', desc: 'Return "active", "closed", or "all" milestones' optional :iid, type: Array[Integer], desc: 'The IID of the milestone' + use :pagination end get ":id/milestones" do authorize! :read_milestone, user_project @@ -103,6 +105,7 @@ module API end params do requires :milestone_id, type: Integer, desc: 'The ID of a project milestone' + use :pagination end get ":id/milestones/:milestone_id/issues" do authorize! :read_milestone, user_project diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb index fe981d7b9fa..30761cb9b55 100644 --- a/lib/api/namespaces.rb +++ b/lib/api/namespaces.rb @@ -1,6 +1,7 @@ module API - # namespaces API class Namespaces < Grape::API + include PaginationParams + before { authenticate! } resource :namespaces do @@ -9,6 +10,7 @@ module API end params do optional :search, type: String, desc: "Search query for namespaces" + use :pagination end get do namespaces = current_user.admin ? Namespace.all : current_user.namespaces diff --git a/lib/api/notes.rb b/lib/api/notes.rb index b255b47742b..d0faf17714b 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -1,6 +1,7 @@ module API - # Notes API class Notes < Grape::API + include PaginationParams + before { authenticate! } NOTEABLE_TYPES = [Issue, MergeRequest, Snippet] @@ -17,6 +18,7 @@ module API end params do requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + use :pagination end get ":id/#{noteables_str}/:noteable_id/notes" do noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id]) diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 2b36ef7c426..dcc0fb7a911 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -1,6 +1,10 @@ module API - # Projects API class ProjectHooks < Grape::API + include PaginationParams + + before { authenticate! } + before { authorize_admin_project } + helpers do params :project_hook_properties do requires :url, type: String, desc: "The URL to send the request to" @@ -17,9 +21,6 @@ module API end end - before { authenticate! } - before { authorize_admin_project } - params do requires :id, type: String, desc: 'The ID of a project' end @@ -27,6 +28,9 @@ module API desc 'Get project hooks' do success Entities::ProjectHook end + params do + use :pagination + end get ":id/hooks" do hooks = paginate user_project.hooks diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index d0ee9c9a5b2..9d8c5b63685 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -1,6 +1,7 @@ module API - # Projects API class ProjectSnippets < Grape::API + include PaginationParams + before { authenticate! } params do @@ -24,6 +25,9 @@ module API desc 'Get all project snippets' do success Entities::ProjectSnippet end + params do + use :pagination + end get ":id/snippets" do present paginate(snippets_for_current_user), with: Entities::ProjectSnippet end diff --git a/lib/api/runners.rb b/lib/api/runners.rb index b145cce7e3e..4816b5ed1b7 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -1,5 +1,7 @@ module API class Runners < Grape::API + include PaginationParams + before { authenticate! } resource :runners do @@ -9,6 +11,7 @@ module API params do optional :scope, type: String, values: %w[active paused online], desc: 'The scope of specific runners to show' + use :pagination end get do runners = filter_runners(current_user.ci_authorized_runners, params[:scope], without: ['specific', 'shared']) @@ -21,6 +24,7 @@ module API params do optional :scope, type: String, values: %w[active paused online specific shared], desc: 'The scope of specific runners to show' + use :pagination end get 'all' do authenticated_as_admin! @@ -91,6 +95,7 @@ module API params do optional :scope, type: String, values: %w[active paused online specific shared], desc: 'The scope of specific runners to show' + use :pagination end get ':id/runners' do runners = filter_runners(Ci::Runner.owned_or_shared(user_project.id), params[:scope]) diff --git a/lib/api/session.rb b/lib/api/session.rb index d09400b81f5..002ffd1d154 100644 --- a/lib/api/session.rb +++ b/lib/api/session.rb @@ -1,7 +1,7 @@ module API class Session < Grape::API desc 'Login to get token' do - success Entities::UserLogin + success Entities::UserWithPrivateToken end params do optional :login, type: String, desc: 'The username' @@ -14,7 +14,7 @@ module API return unauthorized! unless user return render_api_error!('401 Unauthorized. You have 2FA enabled. Please use a personal access token to access the API', 401) if user.two_factor_enabled? - present user, with: Entities::UserLogin + present user, with: Entities::UserWithPrivateToken end end end diff --git a/lib/api/tags.rb b/lib/api/tags.rb index cd33f9a9903..5b345db3a41 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -1,7 +1,6 @@ module API # Git Tags API class Tags < Grape::API - before { authenticate! } before { authorize! :download_code, user_project } params do diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 832b04a3bb1..ed8f48aa1e3 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -1,6 +1,7 @@ module API - # Todos API class Todos < Grape::API + include PaginationParams + before { authenticate! } ISSUABLE_TYPES = { @@ -44,10 +45,11 @@ module API desc 'Get a todo list' do success Entities::Todo end + params do + use :pagination + end get do - todos = find_todos - - present paginate(todos), with: Entities::Todo, current_user: current_user + present paginate(find_todos), with: Entities::Todo, current_user: current_user end desc 'Mark a todo as done' do diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index bb4de39def1..87a717ba751 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -1,5 +1,7 @@ module API class Triggers < Grape::API + include PaginationParams + params do requires :id, type: String, desc: 'The ID of a project' end @@ -42,6 +44,9 @@ module API desc 'Get triggers list' do success Entities::Trigger end + params do + use :pagination + end get ':id/triggers' do authenticate! authorize! :admin_build, user_project diff --git a/lib/api/users.rb b/lib/api/users.rb index a73650dc361..1dab799dd61 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -1,6 +1,7 @@ module API - # Users API class Users < Grape::API + include PaginationParams + before { authenticate! } resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do @@ -33,6 +34,7 @@ module API optional :active, type: Boolean, default: false, desc: 'Filters only active users' optional :external, type: Boolean, default: false, desc: 'Filters only external users' optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users' + use :pagination end get do unless can?(current_user, :read_users_list, nil) @@ -49,7 +51,7 @@ module API users = users.external if params[:external] && current_user.is_admin? end - entity = current_user.is_admin? ? Entities::UserFull : Entities::UserBasic + entity = current_user.is_admin? ? Entities::UserPublic : Entities::UserBasic present paginate(users), with: entity end @@ -64,7 +66,7 @@ module API not_found!('User') unless user if current_user && current_user.is_admin? - present user, with: Entities::UserFull + present user, with: Entities::UserPublic elsif can?(current_user, :read_user, user) present user, with: Entities::User else @@ -73,7 +75,7 @@ module API end desc 'Create a user. Available only for admins.' do - success Entities::UserFull + success Entities::UserPublic end params do requires :email, type: String, desc: 'The email of the user' @@ -97,7 +99,7 @@ module API end if user.save - present user, with: Entities::UserFull + present user, with: Entities::UserPublic else conflict!('Email has already been taken') if User. where(email: user.email). @@ -112,7 +114,7 @@ module API end desc 'Update a user. Available only for admins.' do - success Entities::UserFull + success Entities::UserPublic end params do requires :id, type: Integer, desc: 'The ID of the user' @@ -159,7 +161,7 @@ module API user_params.delete(:provider) if user.update_attributes(user_params) - present user, with: Entities::UserFull + present user, with: Entities::UserPublic else render_validation_error!(user) end @@ -330,6 +332,7 @@ module API end params do requires :id, type: Integer, desc: 'The ID of the user' + use :pagination end get ':id/events' do user = User.find_by(id: params[:id]) @@ -347,10 +350,10 @@ module API resource :user do desc 'Get the currently authenticated user' do - success Entities::UserFull + success Entities::UserPublic end get do - present current_user, with: Entities::UserFull + present current_user, with: @impersonator ? Entities::UserWithPrivateToken : Entities::UserPublic end desc "Get the currently authenticated user's SSH keys" do diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 96c20100541..7e6537e3d9e 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -82,16 +82,17 @@ module Backup removed = 0 Dir.chdir(Gitlab.config.backup.path) do - file_list = Dir.glob('*_gitlab_backup.tar') - file_list.map! do |path_string| - if path_string =~ /(\d+)(?:_\d{4}_\d{2}_\d{2})?_gitlab_backup\.tar/ - { timestamp: $1.to_i, path: path_string } - end - end - file_list.sort.each do |file| - if Time.at(file[:timestamp]) < (Time.now - keep_time) - if Kernel.system(*%W(rm #{file[:path]})) + Dir.glob('*_gitlab_backup.tar').each do |file| + next unless file =~ /(\d+)(?:_\d{4}_\d{2}_\d{2})?_gitlab_backup\.tar/ + + timestamp = $1.to_i + + if Time.at(timestamp) < (Time.now - keep_time) + begin + FileUtils.rm(file) removed += 1 + rescue => e + $progress.puts "Deleting #{file} failed: #{e.message}".color(:red) end end end diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index 3740d4fb4cd..d904a8bd4ae 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -33,7 +33,7 @@ module Banzai # Returns a String replaced with the return of the block. def self.references_in(text, pattern = object_class.reference_pattern) text.gsub(pattern) do |match| - yield match, $~[object_sym].to_i, $~[:project], $~ + yield match, $~[object_sym].to_i, $~[:project], $~[:namespace], $~ end end @@ -145,8 +145,9 @@ module Banzai # Returns a String with references replaced with links. All links # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling. def object_link_filter(text, pattern, link_content: nil) - references_in(text, pattern) do |match, id, project_ref, matches| - project = project_from_ref_cached(project_ref) + references_in(text, pattern) do |match, id, project_ref, namespace_ref, matches| + project_path = full_project_path(namespace_ref, project_ref) + project = project_from_ref_cached(project_path) if project && object = find_object_cached(project, id) title = object_link_title(object) @@ -217,10 +218,9 @@ module Banzai nodes.each do |node| node.to_html.scan(regex) do - project = $~[:project] || current_project_path + project_path = full_project_path($~[:namespace], $~[:project]) symbol = $~[object_sym] - - refs[project] << symbol if object_class.reference_valid?(symbol) + refs[project_path] << symbol if object_class.reference_valid?(symbol) end end @@ -272,8 +272,19 @@ module Banzai @current_project_path ||= project.path_with_namespace end + def current_project_namespace_path + @current_project_namespace_path ||= project.namespace.path + end + private + def full_project_path(namespace, project_ref) + return current_project_path unless project_ref + + namespace_ref = namespace || current_project_namespace_path + "#{namespace_ref}/#{project_ref}" + end + def project_refs_cache RequestStore[:banzai_project_refs] ||= {} end diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb index 4358bf45549..eaacb9591b1 100644 --- a/lib/banzai/filter/commit_range_reference_filter.rb +++ b/lib/banzai/filter/commit_range_reference_filter.rb @@ -12,7 +12,7 @@ module Banzai def self.references_in(text, pattern = CommitRange.reference_pattern) text.gsub(pattern) do |match| - yield match, $~[:commit_range], $~[:project], $~ + yield match, $~[:commit_range], $~[:project], $~[:namespace], $~ end end diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb index a26dd09c25a..69c06117eda 100644 --- a/lib/banzai/filter/commit_reference_filter.rb +++ b/lib/banzai/filter/commit_reference_filter.rb @@ -12,7 +12,7 @@ module Banzai def self.references_in(text, pattern = Commit.reference_pattern) text.gsub(pattern) do |match| - yield match, $~[:commit], $~[:project], $~ + yield match, $~[:commit], $~[:project], $~[:namespace], $~ end end diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 9f9a96cdc65..a605dea149e 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -14,16 +14,18 @@ module Banzai def self.references_in(text, pattern = Label.reference_pattern) unescape_html_entities(text).gsub(pattern) do |match| - yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~ + yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~[:namespace], $~ end end def references_in(text, pattern = Label.reference_pattern) unescape_html_entities(text).gsub(pattern) do |match| - label = find_label($~[:project], $~[:label_id], $~[:label_name]) + namespace, project = $~[:namespace], $~[:project] + project_path = full_project_path(namespace, project) + label = find_label(project_path, $~[:label_id], $~[:label_name]) if label - yield match, label.id, $~[:project], $~ + yield match, label.id, project, namespace, $~ else match end @@ -64,48 +66,12 @@ module Banzai end def object_link_text(object, matches) - if same_group?(object) && namespace_match?(matches) - render_same_project_label(object) - elsif same_project?(object) - render_same_project_label(object) - else - render_cross_project_label(object, matches) - end - end - - def same_group?(object) - object.is_a?(GroupLabel) && object.group == project.group - end - - def namespace_match?(matches) - matches[:project].blank? || matches[:project] == project.path_with_namespace - end - - def same_project?(object) - object.is_a?(ProjectLabel) && object.project == project - end - - def user - context[:current_user] || context[:author] - end - - def project - context[:project] - end - - def render_same_project_label(object) - LabelsHelper.render_colored_label(object) - end - - def render_cross_project_label(object, matches) - source_project = - if matches[:project] - Project.find_with_namespace(matches[:project]) - else - object.project - end + project_path = full_project_path(matches[:namespace], matches[:project]) + project_from_ref = project_from_ref_cached(project_path) + reference = project_from_ref.to_human_reference(project) + label_suffix = " <i>in #{reference}</i>" if reference.present? - LabelsHelper.render_colored_cross_project_label(object, source_project) + LabelsHelper.render_colored_label(object, label_suffix) end def unescape_html_entities(text) diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index 58fff496d00..f12014e191f 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -19,18 +19,20 @@ module Banzai return super(text, pattern) if pattern != Milestone.reference_pattern text.gsub(pattern) do |match| - milestone = find_milestone($~[:project], $~[:milestone_iid], $~[:milestone_name]) + milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name]) if milestone - yield match, milestone.iid, $~[:project], $~ + yield match, milestone.iid, $~[:project], $~[:namespace], $~ else match end end end - def find_milestone(project_ref, milestone_id, milestone_name) - project = project_from_ref(project_ref) + def find_milestone(project_ref, namespace_ref, milestone_id, milestone_name) + project_path = full_project_path(namespace_ref, project_ref) + project = project_from_ref(project_path) + return unless project milestone_params = milestone_params(milestone_id, milestone_name) @@ -52,11 +54,13 @@ module Banzai end def object_link_text(object, matches) - if context[:project] == object.project - super + milestone_link = escape_once(super) + reference = object.project.to_reference(project) + + if reference.present? + "#{milestone_link} <i>in #{reference}</i>".html_safe else - "#{escape_once(super)} <i>in #{escape_once(object.project.path_with_namespace)}</i>". - html_safe + milestone_link end end diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_create.rb index 99c1382af44..1dba85c1b51 100644 --- a/lib/gitlab/chat_commands/issue_create.rb +++ b/lib/gitlab/chat_commands/issue_create.rb @@ -4,11 +4,11 @@ module Gitlab def self.match(text) # we can not match \n with the dot by passing the m modifier as than # the title and description are not seperated - /\Aissue\s+create\s+(?<title>[^\n]*)\n*(?<description>(.|\n)*)/.match(text) + /\Aissue\s+(new|create)\s+(?<title>[^\n]*)\n*(?<description>(.|\n)*)/.match(text) end def self.help_message - 'issue create <title>\n<description>' + 'issue new <title>\n<description>' end def self.allowed?(project, user) diff --git a/lib/gitlab/ci/status/canceled.rb b/lib/gitlab/ci/status/canceled.rb new file mode 100644 index 00000000000..dd6d99e9075 --- /dev/null +++ b/lib/gitlab/ci/status/canceled.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + module Status + class Canceled < Status::Core + def text + 'canceled' + end + + def label + 'canceled' + end + + def icon + 'icon_status_canceled' + end + end + end + end +end diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb new file mode 100644 index 00000000000..ce4108fdcf2 --- /dev/null +++ b/lib/gitlab/ci/status/core.rb @@ -0,0 +1,58 @@ +module Gitlab + module Ci + module Status + # Base abstract class fore core status + # + class Core + include Gitlab::Routing.url_helpers + + def initialize(subject) + @subject = subject + end + + def icon + raise NotImplementedError + end + + def label + raise NotImplementedError + end + + def title + "#{@subject.class.name.demodulize}: #{label}" + end + + # Deprecation warning: this method is here because we need to maintain + # backwards compatibility with legacy statuses. We often do something + # like "ci-status ci-status-#{status}" to set CSS class. + # + # `to_s` method should be renamed to `group` at some point, after + # phasing legacy satuses out. + # + def to_s + self.class.name.demodulize.downcase.underscore + end + + def has_details? + raise NotImplementedError + end + + def details_path + raise NotImplementedError + end + + def has_action? + raise NotImplementedError + end + + def action_icon + raise NotImplementedError + end + + def action_path + raise NotImplementedError + end + end + end + end +end diff --git a/lib/gitlab/ci/status/created.rb b/lib/gitlab/ci/status/created.rb new file mode 100644 index 00000000000..6596d7e01ca --- /dev/null +++ b/lib/gitlab/ci/status/created.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + module Status + class Created < Status::Core + def text + 'created' + end + + def label + 'created' + end + + def icon + 'icon_status_created' + end + end + end + end +end diff --git a/lib/gitlab/ci/status/extended.rb b/lib/gitlab/ci/status/extended.rb new file mode 100644 index 00000000000..6bfb5d38c1f --- /dev/null +++ b/lib/gitlab/ci/status/extended.rb @@ -0,0 +1,11 @@ +module Gitlab + module Ci + module Status + module Extended + def matches?(_subject) + raise NotImplementedError + end + end + end + end +end diff --git a/lib/gitlab/ci/status/failed.rb b/lib/gitlab/ci/status/failed.rb new file mode 100644 index 00000000000..c5b5e3203ad --- /dev/null +++ b/lib/gitlab/ci/status/failed.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + module Status + class Failed < Status::Core + def text + 'failed' + end + + def label + 'failed' + end + + def icon + 'icon_status_failed' + end + end + end + end +end diff --git a/lib/gitlab/ci/status/pending.rb b/lib/gitlab/ci/status/pending.rb new file mode 100644 index 00000000000..d30f35a59a2 --- /dev/null +++ b/lib/gitlab/ci/status/pending.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + module Status + class Pending < Status::Core + def text + 'pending' + end + + def label + 'pending' + end + + def icon + 'icon_status_pending' + end + end + end + end +end diff --git a/lib/gitlab/ci/status/pipeline/common.rb b/lib/gitlab/ci/status/pipeline/common.rb new file mode 100644 index 00000000000..25e52bec3da --- /dev/null +++ b/lib/gitlab/ci/status/pipeline/common.rb @@ -0,0 +1,23 @@ +module Gitlab + module Ci + module Status + module Pipeline + module Common + def has_details? + true + end + + def details_path + namespace_project_pipeline_path(@subject.project.namespace, + @subject.project, + @subject) + end + + def has_action? + false + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/pipeline/factory.rb b/lib/gitlab/ci/status/pipeline/factory.rb new file mode 100644 index 00000000000..71d27bf7cf5 --- /dev/null +++ b/lib/gitlab/ci/status/pipeline/factory.rb @@ -0,0 +1,39 @@ +module Gitlab + module Ci + module Status + module Pipeline + class Factory + EXTENDED_STATUSES = [Pipeline::SuccessWithWarnings] + + def initialize(pipeline) + @pipeline = pipeline + @status = pipeline.status || :created + end + + def fabricate! + if extended_status + extended_status.new(core_status) + else + core_status + end + end + + private + + def core_status + Gitlab::Ci::Status + .const_get(@status.capitalize) + .new(@pipeline) + .extend(Status::Pipeline::Common) + end + + def extended_status + @extended ||= EXTENDED_STATUSES.find do |status| + status.matches?(@pipeline) + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb new file mode 100644 index 00000000000..4b040d60df8 --- /dev/null +++ b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb @@ -0,0 +1,31 @@ +module Gitlab + module Ci + module Status + module Pipeline + class SuccessWithWarnings < SimpleDelegator + extend Status::Extended + + def text + 'passed' + end + + def label + 'passed with warnings' + end + + def icon + 'icon_status_warning' + end + + def to_s + 'success_with_warnings' + end + + def self.matches?(pipeline) + pipeline.success? && pipeline.has_warnings? + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/running.rb b/lib/gitlab/ci/status/running.rb new file mode 100644 index 00000000000..2aba3c373c7 --- /dev/null +++ b/lib/gitlab/ci/status/running.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + module Status + class Running < Status::Core + def text + 'running' + end + + def label + 'running' + end + + def icon + 'icon_status_running' + end + end + end + end +end diff --git a/lib/gitlab/ci/status/skipped.rb b/lib/gitlab/ci/status/skipped.rb new file mode 100644 index 00000000000..16282aefd03 --- /dev/null +++ b/lib/gitlab/ci/status/skipped.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + module Status + class Skipped < Status::Core + def text + 'skipped' + end + + def label + 'skipped' + end + + def icon + 'icon_status_skipped' + end + end + end + end +end diff --git a/lib/gitlab/ci/status/success.rb b/lib/gitlab/ci/status/success.rb new file mode 100644 index 00000000000..c09c5f006e3 --- /dev/null +++ b/lib/gitlab/ci/status/success.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + module Status + class Success < Status::Core + def text + 'passed' + end + + def label + 'passed' + end + + def icon + 'icon_status_success' + end + end + end + end +end |