diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/api/api.rb | 9 | ||||
| -rw-r--r-- | lib/api/boards.rb | 2 | ||||
| -rw-r--r-- | lib/api/builds.rb | 2 | ||||
| -rw-r--r-- | lib/api/entities.rb | 1 | ||||
| -rw-r--r-- | lib/api/helpers.rb | 2 | ||||
| -rw-r--r-- | lib/api/issues.rb | 3 | ||||
| -rw-r--r-- | lib/api/merge_requests.rb | 274 | ||||
| -rw-r--r-- | lib/api/projects.rb | 16 | ||||
| -rw-r--r-- | lib/api/settings.rb | 3 | ||||
| -rw-r--r-- | lib/api/v3/issues.rb | 231 | ||||
| -rw-r--r-- | lib/api/v3/merge_requests.rb | 280 | ||||
| -rw-r--r-- | lib/api/v3/projects.rb | 458 | ||||
| -rw-r--r-- | lib/gitlab/kubernetes.rb | 4 | ||||
| -rw-r--r-- | lib/gitlab/middleware/webpack_proxy.rb | 24 | ||||
| -rw-r--r-- | lib/gitlab/workhorse.rb | 3 | ||||
| -rw-r--r-- | lib/tasks/gitlab/assets.rake | 1 | ||||
| -rw-r--r-- | lib/tasks/gitlab/test.rake | 2 | ||||
| -rw-r--r-- | lib/tasks/karma.rake | 25 | ||||
| -rw-r--r-- | lib/tasks/teaspoon.rake | 25 | ||||
| -rw-r--r-- | lib/tasks/test.rake | 2 | 
20 files changed, 1170 insertions, 197 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb index 6cf6b501021..1950d2791ab 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -1,7 +1,14 @@  module API    class API < Grape::API      include APIGuard -    version 'v3', using: :path + +    version %w(v3 v4), using: :path + +    version 'v3', using: :path do +      mount ::API::V3::Issues +      mount ::API::V3::MergeRequests +      mount ::API::V3::Projects +    end      before { allow_access_with_scope :api } diff --git a/lib/api/boards.rb b/lib/api/boards.rb index 4ac491edc1b..13752eb4947 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -37,7 +37,7 @@ module API          end          desc 'Get the lists of a project board' do -          detail 'Does not include `backlog` and `done` lists. This feature was introduced in 8.13' +          detail 'Does not include `done` list. This feature was introduced in 8.13'            success Entities::List          end          get '/lists' do diff --git a/lib/api/builds.rb b/lib/api/builds.rb index af61be343be..44fe0fc4a95 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -209,7 +209,7 @@ module API          build = get_build!(params[:build_id]) -        bad_request!("Unplayable Build") unless build.playable? +        bad_request!("Unplayable Job") unless build.playable?          build.play(current_user) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index a07b2a9ca0f..b1ead48caf7 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -575,6 +575,7 @@ module API        expose :koding_url        expose :plantuml_enabled        expose :plantuml_url +      expose :terminal_max_session_time      end      class Release < Grape::Entity diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index eb5b947172a..dfab60f7fa5 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -304,7 +304,7 @@ module API          header['X-Sendfile'] = path          body        else -        path +        file path        end      end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index fe016c1ec0a..90fca20d4fa 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -15,8 +15,6 @@ module API          labels = args.delete(:labels)          args[:label_name] = labels if match_all_labels -        args[:search] = "#{Issue.reference_prefix}#{args.delete(:iid)}" if args.key?(:iid) -          issues = IssuesFinder.new(current_user, args).execute.inc_notes_with_associations          # TODO: Remove in 9.0  pass `label_name: args.delete(:labels)` to IssuesFinder @@ -97,7 +95,6 @@ module API        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: 'Return the issue having the given `iid`'          use :issues_params        end        get ":id/issues" do diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 7ffb38e62da..782147883c8 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -2,8 +2,6 @@ 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! }      params do @@ -46,14 +44,14 @@ module API                              desc: 'Return merge requests ordered by `created_at` or `updated_at` fields.'          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' +        optional :iids, type: Array[Integer], desc: 'The IID array of merge requests'          use :pagination        end        get ":id/merge_requests" do          authorize! :read_merge_request, user_project          merge_requests = user_project.merge_requests.inc_notes_with_associations -        merge_requests = filter_by_iid(merge_requests, params[:iid]) if params[:iid].present? +        merge_requests = filter_by_iid(merge_requests, params[:iids]) if params[:iids].present?          merge_requests =            case params[:state] @@ -104,177 +102,167 @@ module API          merge_request.destroy        end -      # Routing "merge_request/:merge_request_id/..." is DEPRECATED and WILL BE REMOVED in version 9.0 -      # Use "merge_requests/:merge_request_id/..." instead. -      #        params do          requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'        end -      { ":id/merge_request/:merge_request_id" => :deprecated, ":id/merge_requests/:merge_request_id" => :ok }.each do |path, status| -        desc 'Get a single merge request' do -          if status == :deprecated -            detail DEPRECATION_MESSAGE -          end -          success Entities::MergeRequest -        end -        get path do -          merge_request = find_merge_request_with_access(params[:merge_request_id]) +      desc 'Get a single merge request' do +        success Entities::MergeRequest +      end +      get ':id/merge_requests/:merge_request_id' do +        merge_request = find_merge_request_with_access(params[:merge_request_id]) -          present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project -        end +        present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project +      end -        desc 'Get the commits of a merge request' do -          success Entities::RepoCommit -        end -        get "#{path}/commits" do -          merge_request = find_merge_request_with_access(params[:merge_request_id]) +      desc 'Get the commits of a merge request' do +        success Entities::RepoCommit +      end +      get ':id/merge_requests/:merge_request_id/commits' do +        merge_request = find_merge_request_with_access(params[:merge_request_id]) -          present merge_request.commits, with: Entities::RepoCommit -        end +        present merge_request.commits, with: Entities::RepoCommit +      end -        desc 'Show the merge request changes' do -          success Entities::MergeRequestChanges -        end -        get "#{path}/changes" do -          merge_request = find_merge_request_with_access(params[:merge_request_id]) +      desc 'Show the merge request changes' do +        success Entities::MergeRequestChanges +      end +      get ':id/merge_requests/:merge_request_id/changes' do +        merge_request = find_merge_request_with_access(params[:merge_request_id]) -          present merge_request, with: Entities::MergeRequestChanges, current_user: current_user -        end +        present merge_request, with: Entities::MergeRequestChanges, current_user: current_user +      end -        desc 'Update a merge request' do -          success Entities::MergeRequest -        end -        params do -          optional :title, type: String, allow_blank: false, desc: 'The title of the merge request' -          optional :target_branch, type: String, allow_blank: false, desc: 'The target branch' -          optional :state_event, type: String, values: %w[close reopen merge], -                                 desc: 'Status of the merge request' -          use :optional_params -          at_least_one_of :title, :target_branch, :description, :assignee_id, -                          :milestone_id, :labels, :state_event, -                          :remove_source_branch -        end -        put path do -          merge_request = find_merge_request_with_access(params.delete(:merge_request_id), :update_merge_request) +      desc 'Update a merge request' do +        success Entities::MergeRequest +      end +      params do +        optional :title, type: String, allow_blank: false, desc: 'The title of the merge request' +        optional :target_branch, type: String, allow_blank: false, desc: 'The target branch' +        optional :state_event, type: String, values: %w[close reopen merge], +                               desc: 'Status of the merge request' +        use :optional_params +        at_least_one_of :title, :target_branch, :description, :assignee_id, +                        :milestone_id, :labels, :state_event, +                        :remove_source_branch +      end +      put ':id/merge_requests/:merge_request_id' do +        merge_request = find_merge_request_with_access(params.delete(:merge_request_id), :update_merge_request) -          mr_params = declared_params(include_missing: false) -          mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present? +        mr_params = declared_params(include_missing: false) +        mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present? -          merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request) +        merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request) -          if merge_request.valid? -            present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project -          else -            handle_merge_request_errors! merge_request.errors -          end +        if merge_request.valid? +          present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project +        else +          handle_merge_request_errors! merge_request.errors          end +      end -        desc 'Merge a merge request' do -          success Entities::MergeRequest -        end -        params do -          optional :merge_commit_message, type: String, desc: 'Custom merge commit message' -          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 pipeline succeeds' -          optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' -        end -        put "#{path}/merge" do -          merge_request = find_project_merge_request(params[:merge_request_id]) +      desc 'Merge a merge request' do +        success Entities::MergeRequest +      end +      params do +        optional :merge_commit_message, type: String, desc: 'Custom merge commit message' +        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 pipeline succeeds' +        optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' +      end +      put ':id/merge_requests/:merge_request_id/merge' do +        merge_request = find_project_merge_request(params[:merge_request_id]) -          # Merge request can not be merged -          # because user dont have permissions to push into target branch -          unauthorized! unless merge_request.can_be_merged_by?(current_user) +        # Merge request can not be merged +        # because user dont have permissions to push into target branch +        unauthorized! unless merge_request.can_be_merged_by?(current_user) -          not_allowed! unless merge_request.mergeable_state? +        not_allowed! unless merge_request.mergeable_state? -          render_api_error!('Branch cannot be merged', 406) unless merge_request.mergeable? +        render_api_error!('Branch cannot be merged', 406) unless merge_request.mergeable? -          if params[:sha] && merge_request.diff_head_sha != params[:sha] -            render_api_error!("SHA does not match HEAD of source branch: #{merge_request.diff_head_sha}", 409) -          end +        if params[:sha] && merge_request.diff_head_sha != params[:sha] +          render_api_error!("SHA does not match HEAD of source branch: #{merge_request.diff_head_sha}", 409) +        end -          merge_params = { -            commit_message: params[:merge_commit_message], -            should_remove_source_branch: params[:should_remove_source_branch] -          } - -          if params[:merge_when_build_succeeds] && merge_request.head_pipeline && merge_request.head_pipeline.active? -            ::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) -          end +        merge_params = { +          commit_message: params[:merge_commit_message], +          should_remove_source_branch: params[:should_remove_source_branch] +        } -          present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project +        if params[:merge_when_build_succeeds] && merge_request.head_pipeline && merge_request.head_pipeline.active? +          ::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)          end -        desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do -          success Entities::MergeRequest -        end -        post "#{path}/cancel_merge_when_build_succeeds" do -          merge_request = find_project_merge_request(params[:merge_request_id]) +        present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project +      end -          unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user) +      desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do +        success Entities::MergeRequest +      end +      post ':id/merge_requests/:merge_request_id/cancel_merge_when_build_succeeds' do +        merge_request = find_project_merge_request(params[:merge_request_id]) -          ::MergeRequest::MergeWhenPipelineSucceedsService -            .new(merge_request.target_project, current_user) -            .cancel(merge_request) -        end +        unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user) -        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 = find_merge_request_with_access(params[:merge_request_id]) -          present paginate(merge_request.notes.fresh), with: Entities::MRNote -        end +        ::MergeRequest::MergeWhenPipelineSucceedsService +          .new(merge_request.target_project, current_user) +          .cancel(merge_request) +      end -        desc 'Post a comment to a merge request' do -          detail 'Duplicate. DEPRECATED and WILL BE REMOVED in 9.0' -          success Entities::MRNote -        end -        params do -          requires :note, type: String, desc: 'The text of the comment' -        end -        post "#{path}/comments" do -          merge_request = find_merge_request_with_access(params[:merge_request_id], :create_note) +      desc 'Get the comments of a merge request' do +        success Entities::MRNote +      end +      params do +        use :pagination +      end +      get ':id/merge_requests/:merge_request_id/comments' do +        merge_request = find_merge_request_with_access(params[:merge_request_id]) +        present paginate(merge_request.notes.fresh), with: Entities::MRNote +      end -          opts = { -            note: params[:note], -            noteable_type: 'MergeRequest', -            noteable_id: merge_request.id -          } +      desc 'Post a comment to a merge request' do +        success Entities::MRNote +      end +      params do +        requires :note, type: String, desc: 'The text of the comment' +      end +      post ':id/merge_requests/:merge_request_id/comments' do +        merge_request = find_merge_request_with_access(params[:merge_request_id], :create_note) -          note = ::Notes::CreateService.new(user_project, current_user, opts).execute +        opts = { +          note: params[:note], +          noteable_type: 'MergeRequest', +          noteable_id: merge_request.id +        } -          if note.save -            present note, with: Entities::MRNote -          else -            render_api_error!("Failed to save note #{note.errors.messages}", 400) -          end -        end +        note = ::Notes::CreateService.new(user_project, current_user, opts).execute -        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 = find_merge_request_with_access(params[:merge_request_id]) -          issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) -          present paginate(issues), with: issue_entity(user_project), current_user: current_user +        if note.save +          present note, with: Entities::MRNote +        else +          render_api_error!("Failed to save note #{note.errors.messages}", 400)          end        end + +      desc 'List issues that will be closed on merge' do +        success Entities::MRNote +      end +      params do +        use :pagination +      end +      get ':id/merge_requests/:merge_request_id/closes_issues' do +        merge_request = find_merge_request_with_access(params[:merge_request_id]) +        issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) +        present paginate(issues), with: issue_entity(user_project), current_user: current_user +      end      end    end  end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 941f47114a4..92a70faf1c2 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -151,22 +151,6 @@ module API          present_projects Project.all, with: Entities::ProjectWithAccess, statistics: params[:statistics]        end -      desc 'Search for projects the current user has access to' do -        success Entities::Project -      end -      params do -        requires :query, type: String, desc: 'The project name to be searched' -        use :sort_params -        use :pagination -      end -      get "/search/:query", requirements: { query: /[^\/]+/ } do -        search_service = Search::GlobalService.new(current_user, search: params[:query]).execute -        projects = search_service.objects('projects', params[:page]) -        projects = projects.reorder(params[:order_by] => params[:sort]) - -        present paginate(projects), with: Entities::Project -      end -        desc 'Create new project' do          success Entities::Project        end diff --git a/lib/api/settings.rb b/lib/api/settings.rb index c5eff16a5de..a1d1c1432d3 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -107,6 +107,7 @@ module API          requires :housekeeping_full_repack_period, type: Integer, desc: "Number of Git pushes after which a full 'git repack' is run."          requires :housekeeping_gc_period, type: Integer, desc: "Number of Git pushes after which 'git gc' is run."        end +      optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'        at_least_one_of :default_branch_protection, :default_project_visibility, :default_snippet_visibility,                        :default_group_visibility, :restricted_visibility_levels, :import_sources,                        :enabled_git_access_protocol, :gravatar_enabled, :default_projects_limit, @@ -120,7 +121,7 @@ module API                        :akismet_enabled, :admin_notification_email, :sentry_enabled,                        :repository_storage, :repository_checks_enabled, :koding_enabled, :plantuml_enabled,                        :version_check_enabled, :email_author_in_body, :html_emails_enabled, -                      :housekeeping_enabled +                      :housekeeping_enabled, :terminal_max_session_time      end      put "application/settings" do        if current_settings.update_attributes(declared_params(include_missing: false)) diff --git a/lib/api/v3/issues.rb b/lib/api/v3/issues.rb new file mode 100644 index 00000000000..be3ecc29449 --- /dev/null +++ b/lib/api/v3/issues.rb @@ -0,0 +1,231 @@ +module API +  module V3 +    class Issues < Grape::API +      include PaginationParams + +      before { authenticate! } + +      helpers do +        def find_issues(args = {}) +          args = params.merge(args) + +          args.delete(:id) +          args[:milestone_title] = args.delete(:milestone) + +          match_all_labels = args.delete(:match_all_labels) +          labels = args.delete(:labels) +          args[:label_name] = labels if match_all_labels + +          args[:search] = "#{Issue.reference_prefix}#{args.delete(:iid)}" if args.key?(:iid) + +          issues = IssuesFinder.new(current_user, args).execute.inc_notes_with_associations + +          if !match_all_labels && labels.present? +            issues = issues.includes(:labels).where('labels.title' => labels.split(',')) +          end + +          issues.reorder(args[:order_by] => args[:sort]) +        end + +        params :issues_params do +          optional :labels, type: String, desc: 'Comma-separated list of label names' +          optional :milestone, type: String, desc: 'Milestone title' +          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.' +          optional :milestone, type: String, desc: 'Return issues for a specific milestone' +          use :pagination +        end + +        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' +        end +      end + +      resource :issues do +        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 = find_issues(scope: 'authored') + +          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 +        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]) + +          issues = find_issues(group_id: group.id, state: params[:state] || 'opened', match_all_labels: true) + +          present paginate(issues), with: Entities::Issue, current_user: current_user +        end +      end + +      params do +        requires :id, type: String, desc: 'The ID of a project' +      end +      resource :projects do +        include TimeTrackingEndpoints + +        desc 'Get a list of project issues' do +          detail 'iid filter is deprecated have been removed on V4' +          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: 'Return the issue having the given `iid`' +          use :issues_params +        end +        get ":id/issues" do +          project = find_project(params[:id]) + +          issues = find_issues(project_id: project.id) + +          present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project +        end + +        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 +        end + +        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 +          # 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 + +          issue_params = declared_params(include_missing: false) + +          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 + +          if issue.valid? +            present issue, with: Entities::Issue, current_user: current_user, project: user_project +          else +            render_validation_error!(issue) +          end +        end + +        desc 'Update an existing issue' do +          success Entities::Issue +        end +        params do +          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.' +          optional :state_event, type: String, values: %w[reopen close], desc: 'State of the issue' +          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.delete(:issue_id)) +          authorize! :update_issue, issue + +          # 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, +                                              declared_params(include_missing: false)).execute(issue) + +          if issue.valid? +            present issue, with: Entities::Issue, current_user: current_user, project: user_project +          else +            render_validation_error!(issue) +          end +        end + +        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 +          issue = user_project.issues.find_by(id: params[:issue_id]) +          not_found!('Issue') unless issue + +          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) +            present issue, with: Entities::Issue, current_user: current_user, project: user_project +          rescue ::Issues::MoveService::MoveError => error +            render_api_error!(error.message, 400) +          end +        end + +        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 +        end +      end +    end +  end +end diff --git a/lib/api/v3/merge_requests.rb b/lib/api/v3/merge_requests.rb new file mode 100644 index 00000000000..1af70cf58cc --- /dev/null +++ b/lib/api/v3/merge_requests.rb @@ -0,0 +1,280 @@ +module API +  module V3 +    class MergeRequests < Grape::API +      include PaginationParams + +      DEPRECATION_MESSAGE = 'This endpoint is deprecated and has been removed on V4'.freeze + +      before { authenticate! } + +      params do +        requires :id, type: String, desc: 'The ID of a project' +      end +      resource :projects do +        include TimeTrackingEndpoints + +        helpers do +          def handle_merge_request_errors!(errors) +            if errors[:project_access].any? +              error!(errors[:project_access], 422) +            elsif errors[:branch_conflict].any? +              error!(errors[:branch_conflict], 422) +            elsif errors[:validate_fork].any? +              error!(errors[:validate_fork], 422) +            elsif errors[:validate_branches].any? +              conflict!(errors[:validate_branches]) +            end + +            render_api_error!(errors, 400) +          end + +          params :optional_params do +            optional :description, type: String, desc: 'The description of the merge request' +            optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request' +            optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request' +            optional :labels, type: String, desc: 'Comma-separated list of label names' +            optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging' +          end +        end + +        desc 'List merge requests' do +          detail 'iid filter is deprecated have been removed on V4' +          success Entities::MergeRequest +        end +        params do +          optional :state, type: String, values: %w[opened closed merged all], default: 'all', +                           desc: 'Return opened, closed, merged, or all merge requests' +          optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at', +                              desc: 'Return merge requests ordered by `created_at` or `updated_at` fields.' +          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 + +          merge_requests = user_project.merge_requests.inc_notes_with_associations +          merge_requests = filter_by_iid(merge_requests, params[:iid]) if params[:iid].present? + +          merge_requests = +            case params[:state] +            when 'opened' then merge_requests.opened +            when 'closed' then merge_requests.closed +            when 'merged' then merge_requests.merged +            else merge_requests +            end + +          merge_requests = merge_requests.reorder(params[:order_by] => params[:sort]) +          present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user, project: user_project +        end + +        desc 'Create a merge request' do +          success Entities::MergeRequest +        end +        params do +          requires :title, type: String, desc: 'The title of the merge request' +          requires :source_branch, type: String, desc: 'The source branch' +          requires :target_branch, type: String, desc: 'The target branch' +          optional :target_project_id, type: Integer, +                                       desc: 'The target project of the merge request defaults to the :id of the project' +          use :optional_params +        end +        post ":id/merge_requests" do +          authorize! :create_merge_request, user_project + +          mr_params = declared_params(include_missing: false) +          mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present? + +          merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute + +          if merge_request.valid? +            present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project +          else +            handle_merge_request_errors! merge_request.errors +          end +        end + +        desc 'Delete a merge request' +        params do +          requires :merge_request_id, type: Integer, desc: 'The ID of a merge request' +        end +        delete ":id/merge_requests/:merge_request_id" do +          merge_request = find_project_merge_request(params[:merge_request_id]) + +          authorize!(:destroy_merge_request, merge_request) +          merge_request.destroy +        end + +        params do +          requires :merge_request_id, type: Integer, desc: 'The ID of a merge request' +        end +        { ":id/merge_request/:merge_request_id" => :deprecated, ":id/merge_requests/:merge_request_id" => :ok }.each do |path, status| +          desc 'Get a single merge request' do +            if status == :deprecated +              detail DEPRECATION_MESSAGE +            end +            success Entities::MergeRequest +          end +          get path do +            merge_request = find_merge_request_with_access(params[:merge_request_id]) + +            present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project +          end + +          desc 'Get the commits of a merge request' do +            success Entities::RepoCommit +          end +          get "#{path}/commits" do +            merge_request = find_merge_request_with_access(params[:merge_request_id]) + +            present merge_request.commits, with: Entities::RepoCommit +          end + +          desc 'Show the merge request changes' do +            success Entities::MergeRequestChanges +          end +          get "#{path}/changes" do +            merge_request = find_merge_request_with_access(params[:merge_request_id]) + +            present merge_request, with: Entities::MergeRequestChanges, current_user: current_user +          end + +          desc 'Update a merge request' do +            success Entities::MergeRequest +          end +          params do +            optional :title, type: String, allow_blank: false, desc: 'The title of the merge request' +            optional :target_branch, type: String, allow_blank: false, desc: 'The target branch' +            optional :state_event, type: String, values: %w[close reopen merge], +                                   desc: 'Status of the merge request' +            use :optional_params +            at_least_one_of :title, :target_branch, :description, :assignee_id, +                            :milestone_id, :labels, :state_event, +                            :remove_source_branch +          end +          put path do +            merge_request = find_merge_request_with_access(params.delete(:merge_request_id), :update_merge_request) + +            mr_params = declared_params(include_missing: false) +            mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present? + +            merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request) + +            if merge_request.valid? +              present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project +            else +              handle_merge_request_errors! merge_request.errors +            end +          end + +          desc 'Merge a merge request' do +            success Entities::MergeRequest +          end +          params do +            optional :merge_commit_message, type: String, desc: 'Custom merge commit message' +            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 pipeline succeeds' +            optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' +          end +          put "#{path}/merge" do +            merge_request = find_project_merge_request(params[:merge_request_id]) + +            # Merge request can not be merged +            # because user dont have permissions to push into target branch +            unauthorized! unless merge_request.can_be_merged_by?(current_user) + +            not_allowed! unless merge_request.mergeable_state? + +            render_api_error!('Branch cannot be merged', 406) unless merge_request.mergeable? + +            if params[:sha] && merge_request.diff_head_sha != params[:sha] +              render_api_error!("SHA does not match HEAD of source branch: #{merge_request.diff_head_sha}", 409) +            end + +            merge_params = { +              commit_message: params[:merge_commit_message], +              should_remove_source_branch: params[:should_remove_source_branch] +            } + +            if params[:merge_when_build_succeeds] && merge_request.head_pipeline && merge_request.head_pipeline.active? +              ::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) +            end + +            present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project +          end + +          desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do +            success Entities::MergeRequest +          end +          post "#{path}/cancel_merge_when_build_succeeds" do +            merge_request = find_project_merge_request(params[:merge_request_id]) + +            unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user) + +            ::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 HAS BEEN REMOVED in V4' +            success Entities::MRNote +          end +          params do +            use :pagination +          end +          get "#{path}/comments" do +            merge_request = find_merge_request_with_access(params[:merge_request_id]) +            present paginate(merge_request.notes.fresh), with: Entities::MRNote +          end + +          desc 'Post a comment to a merge request' do +            detail 'Duplicate. DEPRECATED and HAS BEEN REMOVED in V4' +            success Entities::MRNote +          end +          params do +            requires :note, type: String, desc: 'The text of the comment' +          end +          post "#{path}/comments" do +            merge_request = find_merge_request_with_access(params[:merge_request_id], :create_note) + +            opts = { +              note: params[:note], +              noteable_type: 'MergeRequest', +              noteable_id: merge_request.id +            } + +            note = ::Notes::CreateService.new(user_project, current_user, opts).execute + +            if note.save +              present note, with: Entities::MRNote +            else +              render_api_error!("Failed to save note #{note.errors.messages}", 400) +            end +          end + +          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 = find_merge_request_with_access(params[:merge_request_id]) +            issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) +            present paginate(issues), with: issue_entity(user_project), current_user: current_user +          end +        end +      end +    end +  end +end diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb new file mode 100644 index 00000000000..bac7d485a22 --- /dev/null +++ b/lib/api/v3/projects.rb @@ -0,0 +1,458 @@ +module API +  module V3 +    class Projects < Grape::API +      include PaginationParams + +      before { authenticate_non_get! } + +      helpers do +        params :optional_params do +          optional :description, type: String, desc: 'The description of the project' +          optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled' +          optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled' +          optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled' +          optional :builds_enabled, type: Boolean, desc: 'Flag indication if builds are enabled' +          optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled' +          optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project' +          optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project' +          optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project' +          optional :public, type: Boolean, desc: 'Create a public project. The same as visibility_level = 20.' +          optional :visibility_level, type: Integer, values: [ +            Gitlab::VisibilityLevel::PRIVATE, +            Gitlab::VisibilityLevel::INTERNAL, +            Gitlab::VisibilityLevel::PUBLIC ], desc: 'Create a public project. The same as visibility_level = 20.' +          optional :public_builds, type: Boolean, desc: 'Perform public builds' +          optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' +          optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed' +          optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved' +        end + +        def map_public_to_visibility_level(attrs) +          publik = attrs.delete(:public) +          if !publik.nil? && !attrs[:visibility_level].present? +            # Since setting the public attribute to private could mean either +            # private or internal, use the more conservative option, private. +            attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE +          end +          attrs +        end +      end + +      resource :projects do +        helpers do +          params :collection_params do +            use :sort_params +            use :filter_params +            use :pagination + +            optional :simple, type: Boolean, default: false, +                              desc: 'Return only the ID, URL, name, and path of each project' +          end + +          params :sort_params do +            optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at], +                                default: 'created_at', desc: 'Return projects ordered by field' +            optional :sort, type: String, values: %w[asc desc], default: 'desc', +                            desc: 'Return projects sorted in ascending and descending order' +          end + +          params :filter_params do +            optional :archived, type: Boolean, default: false, desc: 'Limit by archived status' +            optional :visibility, type: String, values: %w[public internal private], +                                  desc: 'Limit by visibility' +            optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria' +          end + +          params :statistics_params do +            optional :statistics, type: Boolean, default: false, desc: 'Include project statistics' +          end + +          params :create_params do +            optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.' +            optional :import_url, type: String, desc: 'URL from which the project is imported' +          end + +          def present_projects(projects, options = {}) +            options = options.reverse_merge( +              with: Entities::Project, +              current_user: current_user, +              simple: params[:simple], +            ) + +            projects = filter_projects(projects) +            projects = projects.with_statistics if options[:statistics] +            options[:with] = Entities::BasicProjectDetails if options[:simple] + +            present paginate(projects), options +          end +        end + +        desc 'Get a list of visible projects for authenticated user' do +          success Entities::BasicProjectDetails +        end +        params do +          use :collection_params +        end +        get '/visible' do +          entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails +          present_projects ProjectsFinder.new.execute(current_user), with: entity +        end + +        desc 'Get a projects list for authenticated user' do +          success Entities::BasicProjectDetails +        end +        params do +          use :collection_params +        end +        get do +          authenticate! + +          present_projects current_user.authorized_projects, +            with: Entities::ProjectWithAccess +        end + +        desc 'Get an owned projects list for authenticated user' do +          success Entities::BasicProjectDetails +        end +        params do +          use :collection_params +          use :statistics_params +        end +        get '/owned' do +          authenticate! + +          present_projects current_user.owned_projects, +            with: Entities::ProjectWithAccess, +            statistics: params[:statistics] +        end + +        desc 'Gets starred project for the authenticated user' do +          success Entities::BasicProjectDetails +        end +        params do +          use :collection_params +        end +        get '/starred' do +          authenticate! + +          present_projects current_user.viewable_starred_projects +        end + +        desc 'Get all projects for admin user' do +          success Entities::BasicProjectDetails +        end +        params do +          use :collection_params +          use :statistics_params +        end +        get '/all' do +          authenticated_as_admin! + +          present_projects Project.all, with: Entities::ProjectWithAccess, statistics: params[:statistics] +        end + +        desc 'Search for projects the current user has access to' do +          success Entities::Project +        end +        params do +          requires :query, type: String, desc: 'The project name to be searched' +          use :sort_params +          use :pagination +        end +        get "/search/:query", requirements: { query: /[^\/]+/ } do +          search_service = Search::GlobalService.new(current_user, search: params[:query]).execute +          projects = search_service.objects('projects', params[:page]) +          projects = projects.reorder(params[:order_by] => params[:sort]) + +          present paginate(projects), with: Entities::Project +        end + +        desc 'Create new project' do +          success Entities::Project +        end +        params do +          requires :name, type: String, desc: 'The name of the project' +          optional :path, type: String, desc: 'The path of the repository' +          use :optional_params +          use :create_params +        end +        post do +          attrs = map_public_to_visibility_level(declared_params(include_missing: false)) +          project = ::Projects::CreateService.new(current_user, attrs).execute + +          if project.saved? +            present project, with: Entities::Project, +                             user_can_admin_project: can?(current_user, :admin_project, project) +          else +            if project.errors[:limit_reached].present? +              error!(project.errors[:limit_reached], 403) +            end +            render_validation_error!(project) +          end +        end + +        desc 'Create new project for a specified user. Only available to admin users.' do +          success Entities::Project +        end +        params do +          requires :name, type: String, desc: 'The name of the project' +          requires :user_id, type: Integer, desc: 'The ID of a user' +          optional :default_branch, type: String, desc: 'The default branch of the project' +          use :optional_params +          use :create_params +        end +        post "user/:user_id" do +          authenticated_as_admin! +          user = User.find_by(id: params.delete(:user_id)) +          not_found!('User') unless user + +          attrs = map_public_to_visibility_level(declared_params(include_missing: false)) +          project = ::Projects::CreateService.new(user, attrs).execute + +          if project.saved? +            present project, with: Entities::Project, +                             user_can_admin_project: can?(current_user, :admin_project, project) +          else +            render_validation_error!(project) +          end +        end +      end + +      params do +        requires :id, type: String, desc: 'The ID of a project' +      end +      resource :projects, requirements: { id: /[^\/]+/ } do +        desc 'Get a single project' do +          success Entities::ProjectWithAccess +        end +        get ":id" do +          entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails +          present user_project, with: entity, current_user: current_user, +                                user_can_admin_project: can?(current_user, :admin_project, user_project) +        end + +        desc 'Get events for a single project' do +          success Entities::Event +        end +        params do +          use :pagination +        end +        get ":id/events" do +          present paginate(user_project.events.recent), with: Entities::Event +        end + +        desc 'Fork new project for the current user or provided namespace.' do +          success Entities::Project +        end +        params do +          optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into' +        end +        post 'fork/:id' do +          fork_params = declared_params(include_missing: false) +          namespace_id = fork_params[:namespace] + +          if namespace_id.present? +            fork_params[:namespace] = if namespace_id =~ /^\d+$/ +                                        Namespace.find_by(id: namespace_id) +                                      else +                                        Namespace.find_by_path_or_name(namespace_id) +                                      end + +            unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace]) +              not_found!('Target Namespace') +            end +          end + +          forked_project = ::Projects::ForkService.new(user_project, current_user, fork_params).execute + +          if forked_project.errors.any? +            conflict!(forked_project.errors.messages) +          else +            present forked_project, with: Entities::Project, +                                    user_can_admin_project: can?(current_user, :admin_project, forked_project) +          end +        end + +        desc 'Update an existing project' do +          success Entities::Project +        end +        params do +          optional :name, type: String, desc: 'The name of the project' +          optional :default_branch, type: String, desc: 'The default branch of the project' +          optional :path, type: String, desc: 'The path of the repository' +          use :optional_params +          at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled, +            :wiki_enabled, :builds_enabled, :snippets_enabled, +            :shared_runners_enabled, :container_registry_enabled, +            :lfs_enabled, :public, :visibility_level, :public_builds, +            :request_access_enabled, :only_allow_merge_if_build_succeeds, +            :only_allow_merge_if_all_discussions_are_resolved, :path, +            :default_branch +        end +        put ':id' do +          authorize_admin_project +          attrs = map_public_to_visibility_level(declared_params(include_missing: false)) +          authorize! :rename_project, user_project if attrs[:name].present? +          authorize! :change_visibility_level, user_project if attrs[:visibility_level].present? + +          result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute + +          if result[:status] == :success +            present user_project, with: Entities::Project, +                                  user_can_admin_project: can?(current_user, :admin_project, user_project) +          else +            render_validation_error!(user_project) +          end +        end + +        desc 'Archive a project' do +          success Entities::Project +        end +        post ':id/archive' do +          authorize!(:archive_project, user_project) + +          user_project.archive! + +          present user_project, with: Entities::Project +        end + +        desc 'Unarchive a project' do +          success Entities::Project +        end +        post ':id/unarchive' do +          authorize!(:archive_project, user_project) + +          user_project.unarchive! + +          present user_project, with: Entities::Project +        end + +        desc 'Star a project' do +          success Entities::Project +        end +        post ':id/star' do +          if current_user.starred?(user_project) +            not_modified! +          else +            current_user.toggle_star(user_project) +            user_project.reload + +            present user_project, with: Entities::Project +          end +        end + +        desc 'Unstar a project' do +          success Entities::Project +        end +        delete ':id/star' do +          if current_user.starred?(user_project) +            current_user.toggle_star(user_project) +            user_project.reload + +            present user_project, with: Entities::Project +          else +            not_modified! +          end +        end + +        desc 'Remove a project' +        delete ":id" do +          authorize! :remove_project, user_project +          ::Projects::DestroyService.new(user_project, current_user, {}).async_execute +        end + +        desc 'Mark this project as forked from another' +        params do +          requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from' +        end +        post ":id/fork/:forked_from_id" do +          authenticated_as_admin! + +          forked_from_project = find_project!(params[:forked_from_id]) +          not_found!("Source Project") unless forked_from_project + +          if user_project.forked_from_project.nil? +            user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id) +          else +            render_api_error!("Project already forked", 409) +          end +        end + +        desc 'Remove a forked_from relationship' +        delete ":id/fork" do +          authorize! :remove_fork_project, user_project + +          if user_project.forked? +            user_project.forked_project_link.destroy +          else +            not_modified! +          end +        end + +        desc 'Share the project with a group' do +          success Entities::ProjectGroupLink +        end +        params do +          requires :group_id, type: Integer, desc: 'The ID of a group' +          requires :group_access, type: Integer, values: Gitlab::Access.values, desc: 'The group access level' +          optional :expires_at, type: Date, desc: 'Share expiration date' +        end +        post ":id/share" do +          authorize! :admin_project, user_project +          group = Group.find_by_id(params[:group_id]) + +          unless group && can?(current_user, :read_group, group) +            not_found!('Group') +          end + +          unless user_project.allowed_to_share_with_group? +            return render_api_error!("The project sharing with group is disabled", 400) +          end + +          link = user_project.project_group_links.new(declared_params(include_missing: false)) + +          if link.save +            present link, with: Entities::ProjectGroupLink +          else +            render_api_error!(link.errors.full_messages.first, 409) +          end +        end + +        params do +          requires :group_id, type: Integer, desc: 'The ID of the group' +        end +        delete ":id/share/:group_id" do +          authorize! :admin_project, user_project + +          link = user_project.project_group_links.find_by(group_id: params[:group_id]) +          not_found!('Group Link') unless link + +          link.destroy +          no_content! +        end + +        desc 'Upload a file' +        params do +          requires :file, type: File, desc: 'The file to be uploaded' +        end +        post ":id/uploads" do +          ::Projects::UploadService.new(user_project, params[:file]).execute +        end + +        desc 'Get the users list of a project' do +          success Entities::UserBasic +        end +        params do +          optional :search, type: String, desc: 'Return list of users matching the search criteria' +          use :pagination +        end +        get ':id/users' do +          users = user_project.team.users +          users = users.search(params[:search]) if params[:search].present? + +          present paginate(users), with: Entities::UserBasic +        end +      end +    end +  end +end diff --git a/lib/gitlab/kubernetes.rb b/lib/gitlab/kubernetes.rb index 288771c1c12..3a7af363548 100644 --- a/lib/gitlab/kubernetes.rb +++ b/lib/gitlab/kubernetes.rb @@ -43,10 +43,10 @@ module Gitlab        end      end -    def add_terminal_auth(terminal, token, ca_pem = nil) +    def add_terminal_auth(terminal, token:, max_session_time:, ca_pem: nil)        terminal[:headers]['Authorization'] << "Bearer #{token}" +      terminal[:max_session_time] = max_session_time        terminal[:ca_pem] = ca_pem if ca_pem.present? -      terminal      end      def container_exec_url(api_url, namespace, pod_name, container_name) diff --git a/lib/gitlab/middleware/webpack_proxy.rb b/lib/gitlab/middleware/webpack_proxy.rb new file mode 100644 index 00000000000..3fe32adeade --- /dev/null +++ b/lib/gitlab/middleware/webpack_proxy.rb @@ -0,0 +1,24 @@ +# This Rack middleware is intended to proxy the webpack assets directory to the +# webpack-dev-server.  It is only intended for use in development. + +module Gitlab +  module Middleware +    class WebpackProxy < Rack::Proxy +      def initialize(app = nil, opts = {}) +        @proxy_host = opts.fetch(:proxy_host, 'localhost') +        @proxy_port = opts.fetch(:proxy_port, 3808) +        @proxy_path = opts[:proxy_path] if opts[:proxy_path] +        super(app, opts) +      end + +      def perform_request(env) +        unless @proxy_path && env['PATH_INFO'].start_with?("/#{@proxy_path}") +          return @app.call(env) +        end + +        env['HTTP_HOST'] = "#{@proxy_host}:#{@proxy_port}" +        super(env) +      end +    end +  end +end diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index a3b502ffd6a..c8872df8a93 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -107,7 +107,8 @@ module Gitlab            'Terminal' => {              'Subprotocols' => terminal[:subprotocols],              'Url' => terminal[:url], -            'Header' => terminal[:headers] +            'Header' => terminal[:headers], +            'MaxSessionTime' => terminal[:max_session_time],            }          }          details['Terminal']['CAPem'] = terminal[:ca_pem] if terminal.has_key?(:ca_pem) diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake index 5d884bf9f66..b6ef8260191 100644 --- a/lib/tasks/gitlab/assets.rake +++ b/lib/tasks/gitlab/assets.rake @@ -3,6 +3,7 @@ namespace :gitlab do      desc 'GitLab | Assets | Compile all frontend assets'      task :compile do        Rake::Task['assets:precompile'].invoke +      Rake::Task['webpack:compile'].invoke        Rake::Task['gitlab:assets:fix_urls'].invoke      end diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake index 4d4e746503a..84810b489ce 100644 --- a/lib/tasks/gitlab/test.rake +++ b/lib/tasks/gitlab/test.rake @@ -6,7 +6,7 @@ namespace :gitlab do        %W(rake rubocop),        %W(rake spinach),        %W(rake spec), -      %W(rake teaspoon) +      %W(rake karma)      ]      cmds.each do |cmd| diff --git a/lib/tasks/karma.rake b/lib/tasks/karma.rake new file mode 100644 index 00000000000..89812a179ec --- /dev/null +++ b/lib/tasks/karma.rake @@ -0,0 +1,25 @@ +unless Rails.env.production? +  Rake::Task['karma'].clear if Rake::Task.task_defined?('karma') + +  namespace :karma do +    desc 'GitLab | Karma | Generate fixtures for JavaScript tests' +    RSpec::Core::RakeTask.new(:fixtures) do |t| +      ENV['NO_KNAPSACK'] = 'true' +      t.pattern = 'spec/javascripts/fixtures/*.rb' +      t.rspec_opts = '--format documentation' +    end + +    desc 'GitLab | Karma | Run JavaScript tests' +    task :tests do +      sh "npm run karma" do |ok, res| +        abort('rake karma:tests failed') unless ok +      end +    end +  end + +  desc 'GitLab | Karma | Shortcut for karma:fixtures and karma:tests' +  task :karma do +    Rake::Task['karma:fixtures'].invoke +    Rake::Task['karma:tests'].invoke +  end +end diff --git a/lib/tasks/teaspoon.rake b/lib/tasks/teaspoon.rake deleted file mode 100644 index 08caedd7ff3..00000000000 --- a/lib/tasks/teaspoon.rake +++ /dev/null @@ -1,25 +0,0 @@ -unless Rails.env.production? -  Rake::Task['teaspoon'].clear if Rake::Task.task_defined?('teaspoon') - -  namespace :teaspoon do -    desc 'GitLab | Teaspoon | Generate fixtures for JavaScript tests' -    RSpec::Core::RakeTask.new(:fixtures) do |t| -      ENV['NO_KNAPSACK'] = 'true' -      t.pattern = 'spec/javascripts/fixtures/*.rb' -      t.rspec_opts = '--format documentation' -    end - -    desc 'GitLab | Teaspoon | Run JavaScript tests' -    task :tests do -      require "teaspoon/console" -      options = {} -      abort('rake teaspoon:tests failed') if Teaspoon::Console.new(options).failures? -    end -  end - -  desc 'GitLab | Teaspoon | Shortcut for teaspoon:fixtures and teaspoon:tests' -  task :teaspoon do -    Rake::Task['teaspoon:fixtures'].invoke -    Rake::Task['teaspoon:tests'].invoke -  end -end diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake index d3dcbd2c29b..3e01f91d32c 100644 --- a/lib/tasks/test.rake +++ b/lib/tasks/test.rake @@ -7,5 +7,5 @@ end  unless Rails.env.production?    desc "GitLab | Run all tests on CI with simplecov" -  task test_ci: [:rubocop, :brakeman, :teaspoon, :spinach, :spec] +  task test_ci: [:rubocop, :brakeman, :karma, :spinach, :spec]  end  | 
