diff options
41 files changed, 445 insertions, 290 deletions
diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md index e06a6fb0cff..1b6a1f87216 100644 --- a/.gitlab/issue_templates/Security developer workflow.md +++ b/.gitlab/issue_templates/Security developer workflow.md @@ -1,60 +1,59 @@ <!-- # Read me first! -Create this issue under https://dev.gitlab.org/gitlab/gitlabhq +Create this issue under https://gitlab.com/gitlab-org/security Set the title to: `Description of the original issue` --> -### Prior to starting the security release work +## Prior to starting the security release work - [ ] Read the [security process for developers] if you are not familiar with it. -- [ ] Link to the original issue adding it to the [links section](#links) -- [ ] Run `scripts/security-harness` in the CE, EE, and/or Omnibus to prevent pushing to any remote besides `dev.gitlab.org` -- [ ] Create a new branch prefixing it with `security-` -- [ ] Create a MR targeting `dev.gitlab.org` `master` -- [ ] Add a link to this issue in the original security issue on `gitlab.com`. +- [ ] Link this issue in the Security Release issue on GitLab.com. You can find this issue in the topic of the `#releases` channel. +- [ ] Add a link to the confidential `gitlab-org/gitlab` issue describing the vulnerability next to **Original issue** in the [links table](#links). +- [ ] Add a link to the confidential `gitlab-org/gitlab` Security release issue next to **Security release issue** in the [links table](#links). +- [ ] Run `scripts/security-harness` in your local repository to prevent accidentally pushing to any remote besides `gitlab.com/gitlab-org/security`. -#### Backports +## Development -- [ ] Once the MR is ready to be merged, create MRs targeting the latest 3 stable branches - - [ ] At this point, it might be easy to squash the commits from the MR into one - - You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation] - - [ ] Create each MR targeting the stable branch `X-Y-stable`, using the "Security Release" merge request template. - - Every merge request will have its own set of TODOs, so make sure to - complete those. -- [ ] Make sure all MRs have a link in the [links section](#links) +- [ ] Create a new branch prefixing it with `security-`. +- [ ] Create a merge request targeting `master` on `gitlab.com/gitlab-org/security` and use the [Security Release merge request template]. +- [ ] Follow the same [code review process]: Assign to a reviewer, then to a maintainer. -[secpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script +After your merge request has being approved according to our [approval guidelines], you're ready to prepare the backports + +## Backports -#### Documentation and final details +- [ ] Once the MR is ready to be merged, create MRs targeting the latest 3 stable branches + * At this point, it might be easy to squash the commits from the MR into one + * You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation] +- [ ] Create each MR targeting the stable branch `X-Y-stable`, using the [Security Release merge request template]. + * Every merge request will have its own set of TODOs, so make sure to complete those. +- [ ] Make sure all MRs are linked in the [Links section](#links) + +## Documentation and final details -- [ ] Check the topic on #releases to see when the next release is going to happen and add a link to the [links section](#links) -- [ ] Add links to this issue and your MRs in the description of the security release issue +- [ ] Ensure the [Links section](#links) is completed. - [ ] Find out the versions affected (the Git history of the files affected may help you with this) and add them to the [details section](#details) - [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details) - [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details) - [ ] Add the nickname of the external user who found the issue (and/or HackerOne profile) to the Thanks row in the [details section](#details) - [ ] Once your `master` MR is merged, comment on the original security issue with a link to that MR indicating the issue is fixed. -### Summary +## Summary -#### Links +### Links | Description | Link | | -------- | -------- | | Original issue | #TODO | | Security release issue | #TODO | | `master` MR | !TODO | -| `master` MR (EE) | !TODO | | `Backport X.Y` MR | !TODO | | `Backport X.Y` MR | !TODO | | `Backport X.Y` MR | !TODO | -| `Backport X.Y` MR (EE) | !TODO | -| `Backport X.Y` MR (EE) | !TODO | -| `Backport X.Y` MR (EE) | !TODO | -#### Details +### Details | Description | Details | Further details| | -------- | -------- | -------- | @@ -65,6 +64,9 @@ Set the title to: `Description of the original issue` | Thanks | | | [security process for developers]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md -[RM list]: https://about.gitlab.com/release-managers/ +[secpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script +[security Release merge request template]: https://gitlab.com/gitlab-org/security/gitlab/blob/master/.gitlab/merge_request_templates/Security%20Release.md +[code review process]: https://docs.gitlab.com/ee/development/code_review.html +[approval guidelines]: https://docs.gitlab.com/ee/development/code_review.html#approval-guidelines /label ~security diff --git a/.gitlab/merge_request_templates/Security Release.md b/.gitlab/merge_request_templates/Security Release.md index 42314f9b2dd..6556b9c9a72 100644 --- a/.gitlab/merge_request_templates/Security Release.md +++ b/.gitlab/merge_request_templates/Security Release.md @@ -1,31 +1,27 @@ <!-- # README first! -This MR should be created on `dev.gitlab.org`. +This MR should be created on `gitlab.com/gitlab-org/security/gitlab`. See [the general developer security release guidelines](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md). -This merge request _must not_ close the corresponding security issue _unless_ it -targets master. - -When submitting a merge request for CE, a corresponding EE merge request is -always required. This makes it easier to merge security merge requests, as -manually merging CE into EE is no longer required. - --> + ## Related issues <!-- Mention the issue(s) this MR is related to --> ## Developer checklist -- [ ] Link to the developer security workflow issue on `dev.gitlab.org` -- [ ] MR targets `master`, or `X-Y-stable` for backports -- [ ] Milestone is set for the version this MR applies to -- [ ] Title of this MR is the same as for all backports +- [ ] Link this MR in the `links` section of the related issue on [GitLab Security]. +- [ ] Merge request targets `master`, or `X-Y-stable` for backports. +- [ ] Milestone is set for the version this merge request applies to. +- [ ] Title of this merge request is the same as for all backports. - [ ] A [CHANGELOG entry](https://docs.gitlab.com/ee/development/changelog.html) is added without a `merge_request` value, with `type` set to `security` -- [ ] Add a link to this MR in the `links` section of related issue -- [ ] Set up an EE MR (always required for CE merge requests): EE_MR_LINK_HERE -- [ ] Assign to a reviewer (that is not a release manager) +- [ ] Assign to a reviewer and maintainer, per our [Code Review process]. +- [ ] If this merge request targets `master`, ensure it's approved according to our [Approval Guidelines]. +- [ ] Merge request _must not_ close the corresponding security issue, _unless_ it targets `master`. + +**Note:** Reviewer/maintainer should not be a Release Manager ## Reviewer checklist @@ -33,3 +29,7 @@ manually merging CE into EE is no longer required. - [ ] Assigned to `@gitlab-release-tools-bot` with passing CI pipelines /label ~security + +[GitLab Security]: https://gitlab.com/gitlab-org/security/gitlab +[approval guidelines]: https://docs.gitlab.com/ee/development/code_review.html#approval-guidelines +[Code Review process]: https://docs.gitlab.com/ee/development/code_review.html diff --git a/app/assets/javascripts/error_tracking_settings/store/actions.js b/app/assets/javascripts/error_tracking_settings/store/actions.js index 6b540ea7dfd..3f1ac426278 100644 --- a/app/assets/javascripts/error_tracking_settings/store/actions.js +++ b/app/assets/javascripts/error_tracking_settings/store/actions.js @@ -25,8 +25,8 @@ export const receiveProjectsError = ({ commit }) => { export const fetchProjects = ({ dispatch, state }) => { dispatch('requestProjects'); return axios - .post(state.listProjectsEndpoint, { - error_tracking_setting: { + .get(state.listProjectsEndpoint, { + params: { api_host: state.apiHost, token: state.token, }, diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 767832e242c..1b549c0a4f0 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -29,7 +29,7 @@ &:focus, &:active { background-color: $btn-active-gray; - box-shadow: $gl-btn-active-background; + box-shadow: none; } } diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb index 92655d593dd..b62ce940e9c 100644 --- a/app/controllers/projects/blame_controller.rb +++ b/app/controllers/projects/blame_controller.rb @@ -17,6 +17,7 @@ class Projects::BlameController < Projects::ApplicationController end environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit } + environment_params[:find_latest] = true @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last @blame_groups = Gitlab::Blame.new(@blob, @commit).groups diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 7c97f771a70..acd3ddf2d05 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -205,6 +205,7 @@ class Projects::BlobController < Projects::ApplicationController def show_html environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit } + environment_params[:find_latest] = true @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last @last_commit = @repository.last_commit_for_path(@commit.id, @blob.path) diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index afb670b687b..5c5bdb867bd 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -151,7 +151,7 @@ class Projects::CommitController < Projects::ApplicationController @diffs = commit.diffs(opts) @notes_count = commit.notes.count - @environment = EnvironmentsFinder.new(@project, current_user, commit: @commit).execute.last + @environment = EnvironmentsFinder.new(@project, current_user, commit: @commit, find_latest: true).execute.last end # rubocop: disable CodeReuse/ActiveRecord diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 5586c2fc631..943277afe95 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -101,6 +101,7 @@ class Projects::CompareController < Projects::ApplicationController def define_environment if compare environment_params = @repository.branch_exists?(head_ref) ? { ref: head_ref } : { commit: compare.commit } + environment_params[:find_latest] = true @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last end end diff --git a/app/controllers/projects/error_tracking/projects_controller.rb b/app/controllers/projects/error_tracking/projects_controller.rb new file mode 100644 index 00000000000..75a2c976d8b --- /dev/null +++ b/app/controllers/projects/error_tracking/projects_controller.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Projects + module ErrorTracking + class ProjectsController < Projects::ApplicationController + respond_to :json + + before_action :authorize_read_sentry_issue! + + def index + service = ::ErrorTracking::ListProjectsService.new( + project, + current_user, + list_projects_params + ) + result = service.execute + + if result[:status] == :success + render json: { projects: serialize_projects(result[:projects]) } + else + render( + status: result[:http_status] || :bad_request, + json: { message: result[:message] } + ) + end + end + + private + + def list_projects_params + { api_host: params[:api_host], token: params[:token] } + end + + def serialize_projects(projects) + ::ErrorTracking::ProjectSerializer + .new(project: project, user: current_user) + .represent(projects) + end + end + end +end diff --git a/app/controllers/projects/error_tracking_controller.rb b/app/controllers/projects/error_tracking_controller.rb index ba21ccfb169..d1253d85758 100644 --- a/app/controllers/projects/error_tracking_controller.rb +++ b/app/controllers/projects/error_tracking_controller.rb @@ -33,14 +33,6 @@ class Projects::ErrorTrackingController < Projects::ApplicationController end end - def list_projects - respond_to do |format| - format.json do - render_project_list_json - end - end - end - private def render_index_json @@ -84,28 +76,6 @@ class Projects::ErrorTrackingController < Projects::ApplicationController } end - def render_project_list_json - service = ErrorTracking::ListProjectsService.new( - project, - current_user, - list_projects_params - ) - result = service.execute - - if result[:status] == :success - render json: { - projects: serialize_projects(result[:projects]) - } - else - return render( - status: result[:http_status] || :bad_request, - json: { - message: result[:message] - } - ) - end - end - def handle_errors(result) unless result[:status] == :success render json: { message: result[:message] }, @@ -117,10 +87,6 @@ class Projects::ErrorTrackingController < Projects::ApplicationController params.permit(:search_term, :sort, :cursor) end - def list_projects_params - params.require(:error_tracking_setting).permit([:api_host, :token]) - end - def issue_details_params params.permit(:issue_id) end @@ -150,10 +116,4 @@ class Projects::ErrorTrackingController < Projects::ApplicationController .new(project: project, user: current_user) .represent(event) end - - def serialize_projects(projects) - ErrorTracking::ProjectSerializer - .new(project: project, user: current_user) - .represent(projects) - end end diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb index 78dc196b08e..c6473391477 100644 --- a/app/controllers/projects/merge_requests/creations_controller.rb +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -52,7 +52,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap @diff_notes_disabled = true - @environment = @merge_request.environments_for(current_user).last + @environment = @merge_request.environments_for(current_user, latest: true).last render json: { html: view_to_html_string('projects/merge_requests/creations/_diffs', diffs: @diffs, environment: @environment) } end diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb index 37d90ecdc00..c0c8474232a 100644 --- a/app/controllers/projects/merge_requests/diffs_controller.rb +++ b/app/controllers/projects/merge_requests/diffs_controller.rb @@ -51,7 +51,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic # Deprecated: https://gitlab.com/gitlab-org/gitlab/issues/37735 def render_diffs diffs = @compare.diffs(diff_options) - @environment = @merge_request.environments_for(current_user).last + @environment = @merge_request.environments_for(current_user, latest: true).last diffs.unfold_diff_files(note_positions.unfoldable) diffs.write_cache diff --git a/app/finders/environments_finder.rb b/app/finders/environments_finder.rb index d4e803beb4e..32942c46208 100644 --- a/app/finders/environments_finder.rb +++ b/app/finders/environments_finder.rb @@ -25,25 +25,13 @@ class EnvironmentsFinder .select(:environment_id) environments = project.environments.available - .where(id: environment_ids).order_by_last_deployed_at.to_a + .where(id: environment_ids) - environments.select! do |environment| - Ability.allowed?(current_user, :read_environment, environment) - end - - if ref && commit - environments.select! do |environment| - environment.includes_commit?(commit) - end - end - - if ref && params[:recently_updated] - environments.select! do |environment| - environment.recently_updated_on_branch?(ref) - end + if params[:find_latest] + find_one(environments.order_by_last_deployed_at_desc) + else + find_all(environments.order_by_last_deployed_at.to_a) end - - environments end # rubocop: enable CodeReuse/ActiveRecord @@ -62,6 +50,24 @@ class EnvironmentsFinder private + def find_one(environments) + [environments.find { |environment| valid_environment?(environment) }].compact + end + + def find_all(environments) + environments.select { |environment| valid_environment?(environment) } + end + + def valid_environment?(environment) + # Go in order of cost: SQL calls are cheaper than Gitaly calls + return false unless Ability.allowed?(current_user, :read_environment, environment) + + return false if ref && params[:recently_updated] && !environment.recently_updated_on_branch?(ref) + return false if ref && commit && !environment.includes_commit?(commit) + + true + end + def ref params[:ref].try(:to_s) end diff --git a/app/finders/events_finder.rb b/app/finders/events_finder.rb index 234b7090fd9..6d059e10d05 100644 --- a/app/finders/events_finder.rb +++ b/app/finders/events_finder.rb @@ -6,7 +6,7 @@ class EventsFinder MAX_PER_PAGE = 100 - attr_reader :source, :params, :current_user + attr_reader :source, :params, :current_user, :scope requires_cross_project_access unless: -> { source.is_a?(Project) }, model: Event @@ -15,6 +15,7 @@ class EventsFinder # Arguments: # source - which user or project to looks for events on # current_user - only return events for projects visible to this user + # scope - return all events across a user's projects # params: # action: string # target_type: string @@ -27,11 +28,12 @@ class EventsFinder def initialize(params = {}) @source = params.delete(:source) @current_user = params.delete(:current_user) + @scope = params.delete(:scope) @params = params end def execute - events = source.events + events = get_events events = by_current_user_access(events) events = by_action(events) @@ -47,6 +49,12 @@ class EventsFinder private + def get_events + return EventCollection.new(current_user.authorized_projects).all_project_events if scope == 'all' + + source.events + end + # rubocop: disable CodeReuse/ActiveRecord def by_current_user_access(events) events.merge(Project.public_or_visible_to_user(current_user)) diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index 679622897aa..99ede8c3557 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -58,6 +58,10 @@ module DashboardHelper links += [:activity, :milestones] end + if can?(current_user, :read_instance_statistics) + links << :analytics + end + links end end diff --git a/app/models/environment.rb b/app/models/environment.rb index b928dcb21a6..82bf0f9a615 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -48,13 +48,14 @@ class Environment < ApplicationRecord scope :available, -> { with_state(:available) } scope :stopped, -> { with_state(:stopped) } + scope :order_by_last_deployed_at, -> do - max_deployment_id_sql = - Deployment.select(Deployment.arel_table[:id].maximum) - .where(Deployment.arel_table[:environment_id].eq(arel_table[:id])) - .to_sql order(Gitlab::Database.nulls_first_order("(#{max_deployment_id_sql})", 'ASC')) end + scope :order_by_last_deployed_at_desc, -> do + order(Gitlab::Database.nulls_last_order("(#{max_deployment_id_sql})", 'DESC')) + end + scope :in_review_folder, -> { where(environment_type: "review") } scope :for_name, -> (name) { where(name: name) } scope :preload_cluster, -> { preload(last_deployment: :cluster) } @@ -90,6 +91,12 @@ class Environment < ApplicationRecord end end + def self.max_deployment_id_sql + Deployment.select(Deployment.arel_table[:id].maximum) + .where(Deployment.arel_table[:environment_id].eq(arel_table[:id])) + .to_sql + end + def self.pluck_names pluck(:name) end diff --git a/app/models/event_collection.rb b/app/models/event_collection.rb index 4778f74568e..4768506b8fa 100644 --- a/app/models/event_collection.rb +++ b/app/models/event_collection.rb @@ -30,17 +30,24 @@ class EventCollection relation = if groups project_and_group_events else - relation_with_join_lateral('project_id', projects) + project_events end relation = paginate_events(relation) relation.with_associations.to_a end + def all_project_events + Event.from_union([project_events]).recent + end + private + def project_events + relation_with_join_lateral('project_id', projects) + end + def project_and_group_events - project_events = relation_with_join_lateral('project_id', projects) group_events = relation_with_join_lateral('group_id', groups) Event.from_union([project_events, group_events]).recent diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 2280c5280d5..cdb6205cd51 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1122,22 +1122,18 @@ class MergeRequest < ApplicationRecord actual_head_pipeline.success? end - def environments_for(current_user) + def environments_for(current_user, latest: false) return [] unless diff_head_commit - @environments ||= Hash.new do |h, current_user| - envs = EnvironmentsFinder.new(target_project, current_user, - ref: target_branch, commit: diff_head_commit, with_tags: true).execute + envs = EnvironmentsFinder.new(target_project, current_user, + ref: target_branch, commit: diff_head_commit, with_tags: true, find_latest: latest).execute - if source_project - envs.concat EnvironmentsFinder.new(source_project, current_user, - ref: source_branch, commit: diff_head_commit).execute - end - - h[current_user] = envs.uniq + if source_project + envs.concat EnvironmentsFinder.new(source_project, current_user, + ref: source_branch, commit: diff_head_commit, find_latest: latest).execute end - @environments[current_user] + envs.uniq end ## diff --git a/app/services/projects/operations/update_service.rb b/app/services/projects/operations/update_service.rb index 0ca89664304..706a6f01a75 100644 --- a/app/services/projects/operations/update_service.rb +++ b/app/services/projects/operations/update_service.rb @@ -30,7 +30,7 @@ module Projects settings = params[:error_tracking_setting_attributes] return {} if settings.blank? - api_url = ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from( + api_url = ::ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from( api_host: settings[:api_host], project_slug: settings.dig(:project, :slug), organization_slug: settings.dig(:project, :organization_slug) diff --git a/app/views/layouts/instance_statistics.html.haml b/app/views/layouts/instance_statistics.html.haml index bebd9c4536f..1de6b385c86 100644 --- a/app/views/layouts/instance_statistics.html.haml +++ b/app/views/layouts/instance_statistics.html.haml @@ -1,5 +1,5 @@ -- page_title _('Instance Statistics') -- header_title _('Instance Statistics'), instance_statistics_root_path +- page_title _('Analytics') +- header_title _('Analytics'), instance_statistics_root_path - nav 'instance_statistics' - @left_sidebar = true diff --git a/app/views/layouts/nav/_analytics_link.html.haml b/app/views/layouts/nav/_analytics_link.html.haml new file mode 100644 index 00000000000..f481aeecc1b --- /dev/null +++ b/app/views/layouts/nav/_analytics_link.html.haml @@ -0,0 +1,4 @@ +- return unless dashboard_nav_link?(:analytics) += nav_link(controller: [:dev_ops_score, :cohorts], html_options: { class: "d-none d-xl-block"}) do + = link_to instance_statistics_root_path, class: 'chart-icon', title: _('Analytics'), aria: { label: _('Analytics') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = sprite_icon('chart', size: 18) diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 9a839765286..9ae06ae77f1 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -47,10 +47,7 @@ %li.dropdown = render_if_exists 'dashboard/nav_link_list' - - if can?(current_user, :read_instance_statistics) - = nav_link(controller: [:dev_ops_score, :cohorts]) do - = link_to instance_statistics_root_path do - = _('Instance Statistics') + - if current_user.admin? = nav_link(controller: 'admin/dashboard') do = link_to admin_root_path, class: 'admin-icon qa-admin-area-link d-xl-none' do @@ -69,6 +66,8 @@ = link_to sherlock_transactions_path, class: 'admin-icon' do = _('Sherlock Transactions') + = render_if_exists 'layouts/nav/analytics_link' + - if current_user.admin? = nav_link(controller: 'admin/dashboard', html_options: { class: "d-none d-xl-block"}) do = link_to admin_root_path, class: 'admin-icon qa-admin-area-link', title: _('Admin Area'), aria: { label: _('Admin Area') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do diff --git a/app/views/layouts/nav/sidebar/_analytics_link.html.haml b/app/views/layouts/nav/sidebar/_analytics_link.html.haml new file mode 100644 index 00000000000..9e5ae422e2d --- /dev/null +++ b/app/views/layouts/nav/sidebar/_analytics_link.html.haml @@ -0,0 +1,4 @@ +- return unless dashboard_nav_link?(:analytics) += nav_link(controller: [:dev_ops_score, :cohorts]) do + = link_to instance_statistics_root_path, class: 'd-xl-none' do + = _('Analytics') diff --git a/app/views/layouts/nav/sidebar/_instance_statistics.html.haml b/app/views/layouts/nav/sidebar/_instance_statistics.html.haml index 0a84e952442..979d98ec382 100644 --- a/app/views/layouts/nav/sidebar/_instance_statistics.html.haml +++ b/app/views/layouts/nav/sidebar/_instance_statistics.html.haml @@ -1,34 +1,11 @@ .nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) } .nav-sidebar-inner-scroll .context-header - = link_to instance_statistics_root_path, title: _('Instance Statistics') do + = link_to instance_statistics_root_path, title: _('Analytics') do .avatar-container.s40.settings-avatar = sprite_icon('chart', size: 24) - .sidebar-context-title= _('Instance Statistics') + .sidebar-context-title= _('Analytics') %ul.sidebar-top-level-items - = nav_link(controller: :dev_ops_score) do - = link_to instance_statistics_dev_ops_score_index_path do - .nav-icon-container - = sprite_icon('comment') - %span.nav-item-name - = _('DevOps Score') - %ul.sidebar-sub-level-items.is-fly-out-only - = nav_link(controller: :dev_ops_score, html_options: { class: "fly-out-top-item" } ) do - = link_to instance_statistics_dev_ops_score_index_path do - %strong.fly-out-top-item-name - = _('DevOps Score') - - - if Gitlab::CurrentSettings.usage_ping_enabled - = nav_link(controller: :cohorts) do - = link_to instance_statistics_cohorts_path do - .nav-icon-container - = sprite_icon('users') - %span.nav-item-name - = _('Cohorts') - %ul.sidebar-sub-level-items.is-fly-out-only - = nav_link(controller: :cohorts, html_options: { class: "fly-out-top-item" } ) do - = link_to instance_statistics_cohorts_path do - %strong.fly-out-top-item-name - = _('Cohorts') + = render 'layouts/nav/sidebar/instance_statistics_links' = render 'shared/sidebar_toggle_button' diff --git a/app/views/layouts/nav/sidebar/_instance_statistics_links.html.haml b/app/views/layouts/nav/sidebar/_instance_statistics_links.html.haml new file mode 100644 index 00000000000..580a25db171 --- /dev/null +++ b/app/views/layouts/nav/sidebar/_instance_statistics_links.html.haml @@ -0,0 +1,25 @@ +- return unless dashboard_nav_link?(:analytics) += nav_link(controller: :dev_ops_score) do + = link_to instance_statistics_dev_ops_score_index_path do + .nav-icon-container + = sprite_icon('comment') + %span.nav-item-name + = _('DevOps Score') + %ul.sidebar-sub-level-items.is-fly-out-only + = nav_link(controller: :dev_ops_score, html_options: { class: "fly-out-top-item" } ) do + = link_to instance_statistics_dev_ops_score_index_path do + %strong.fly-out-top-item-name + = _('DevOps Score') + +- if Gitlab::CurrentSettings.usage_ping_enabled + = nav_link(controller: :cohorts) do + = link_to instance_statistics_cohorts_path do + .nav-icon-container + = sprite_icon('users') + %span.nav-item-name + = _('Cohorts') + %ul.sidebar-sub-level-items.is-fly-out-only + = nav_link(controller: :cohorts, html_options: { class: "fly-out-top-item" } ) do + = link_to instance_statistics_cohorts_path do + %strong.fly-out-top-item-name + = _('Cohorts') diff --git a/app/views/projects/settings/operations/_error_tracking.html.haml b/app/views/projects/settings/operations/_error_tracking.html.haml index 589d3037eba..06b5243dfd9 100644 --- a/app/views/projects/settings/operations/_error_tracking.html.haml +++ b/app/views/projects/settings/operations/_error_tracking.html.haml @@ -12,7 +12,7 @@ = _('To link Sentry to GitLab, enter your Sentry URL and Auth Token.') = link_to _('More information'), help_page_path('user/project/operations/error_tracking'), target: '_blank', rel: 'noopener noreferrer' .settings-content - .js-error-tracking-form{ data: { list_projects_endpoint: list_projects_project_error_tracking_index_path(@project, format: :json), + .js-error-tracking-form{ data: { list_projects_endpoint: project_error_tracking_projects_path(@project, format: :json), operations_settings_endpoint: project_settings_operations_path(@project), project: error_tracking_setting_project_json, api_host: setting.api_host, diff --git a/changelogs/unreleased/18999-add-dashboard-activity-to-events-endpoint.yml b/changelogs/unreleased/18999-add-dashboard-activity-to-events-endpoint.yml new file mode 100644 index 00000000000..62f7b9d5602 --- /dev/null +++ b/changelogs/unreleased/18999-add-dashboard-activity-to-events-endpoint.yml @@ -0,0 +1,5 @@ +--- +title: Add activity across all projects to /events endpoint +merge_request: 19816 +author: briankabiro +type: changed diff --git a/changelogs/unreleased/36017-move-analytics-out-of-more-in-top-navbar.yml b/changelogs/unreleased/36017-move-analytics-out-of-more-in-top-navbar.yml new file mode 100644 index 00000000000..c4cf6d4c174 --- /dev/null +++ b/changelogs/unreleased/36017-move-analytics-out-of-more-in-top-navbar.yml @@ -0,0 +1,5 @@ +--- +title: Move instance statistics into analytics namespace +merge_request: 21112 +author: +type: changed diff --git a/changelogs/unreleased/sh-optimize-commit-is-ancestor-env.yml b/changelogs/unreleased/sh-optimize-commit-is-ancestor-env.yml new file mode 100644 index 00000000000..454303fdaab --- /dev/null +++ b/changelogs/unreleased/sh-optimize-commit-is-ancestor-env.yml @@ -0,0 +1,5 @@ +--- +title: Reduce CommitIsAncestor RPCs with environments +merge_request: 21778 +author: +type: performance diff --git a/config/routes/project.rb b/config/routes/project.rb index 6c2dcafd1ab..6ee6715facf 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -256,6 +256,10 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do end end + namespace :error_tracking do + resources :projects, only: :index + end + resources :error_tracking, only: [:index], controller: :error_tracking do collection do get ':issue_id/details', @@ -264,7 +268,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do get ':issue_id/stack_trace', to: 'error_tracking#stack_trace', as: 'stack_trace' - post :list_projects end end diff --git a/doc/api/events.md b/doc/api/events.md index 1cd7047b867..1dc0b054ee6 100644 --- a/doc/api/events.md +++ b/doc/api/events.md @@ -66,12 +66,13 @@ Parameters: | `target_type` | string | no | Include only events of a particular [target type][target-types] | | `before` | date | no | Include only events created before a particular date. Please see [here for the supported format][date-formatting] | | `after` | date | no | Include only events created after a particular date. Please see [here for the supported format][date-formatting] | +| `scope` | string | no | Include all events across a user's projects. | | `sort` | string | no | Sort events in `asc` or `desc` order by `created_at`. Default is `desc` | Example request: ```bash -curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/events?target_type=issue&action=created&after=2017-01-31&before=2017-03-01 +curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/events?target_type=issue&action=created&after=2017-01-31&before=2017-03-01&scope=all ``` Example response: diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3dbf9478be9..7c743709773 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9776,9 +9776,6 @@ msgid_plural "Instances" msgstr[0] "" msgstr[1] "" -msgid "Instance Statistics" -msgstr "" - msgid "Instance Statistics visibility" msgstr "" diff --git a/spec/controllers/projects/error_tracking/projects_controller_spec.rb b/spec/controllers/projects/error_tracking/projects_controller_spec.rb new file mode 100644 index 00000000000..e55495700c2 --- /dev/null +++ b/spec/controllers/projects/error_tracking/projects_controller_spec.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::ErrorTracking::ProjectsController do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + + before do + sign_in(user) + project.add_maintainer(user) + end + + describe 'POST #index' do + context 'with insufficient permissions' do + before do + project.add_guest(user) + end + + it 'returns 404' do + get :index, params: list_projects_params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'with an anonymous user' do + before do + sign_out(user) + end + + it 'redirects to sign-in page' do + get :index, params: list_projects_params + + expect(response).to have_gitlab_http_status(:redirect) + end + end + + context 'with authorized user' do + let(:list_projects_service) { spy(:list_projects_service) } + let(:sentry_project) { build(:error_tracking_project) } + + let(:query_params) do + list_projects_params.slice(:api_host, :token) + end + + before do + allow(ErrorTracking::ListProjectsService) + .to receive(:new).with(project, user, query_params) + .and_return(list_projects_service) + end + + context 'service result is successful' do + before do + expect(list_projects_service).to receive(:execute) + .and_return(status: :success, projects: [sentry_project]) + end + + it 'returns a list of projects' do + get :index, params: list_projects_params + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('error_tracking/list_projects') + expect(json_response['projects']).to eq([sentry_project].as_json) + end + end + + context 'service result is erroneous' do + let(:error_message) { 'error message' } + + context 'without http_status' do + before do + expect(list_projects_service).to receive(:execute) + .and_return(status: :error, message: error_message) + end + + it 'returns 400 with message' do + get :index, params: list_projects_params + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq(error_message) + end + end + + context 'with explicit http_status' do + let(:http_status) { :no_content } + + before do + expect(list_projects_service).to receive(:execute).and_return( + status: :error, + message: error_message, + http_status: http_status + ) + end + + it 'returns http_status with message' do + get :index, params: list_projects_params + + expect(response).to have_gitlab_http_status(http_status) + expect(json_response['message']).to eq(error_message) + end + end + end + end + + private + + def list_projects_params(opts = {}) + project_params( + format: :json, + api_host: 'gitlab.com', + token: 'token' + ) + end + end + + private + + def project_params(opts = {}) + opts.reverse_merge(namespace_id: project.namespace, project_id: project) + end +end diff --git a/spec/controllers/projects/error_tracking_controller_spec.rb b/spec/controllers/projects/error_tracking_controller_spec.rb index e5585d7b52d..b1f7c7178c1 100644 --- a/spec/controllers/projects/error_tracking_controller_spec.rb +++ b/spec/controllers/projects/error_tracking_controller_spec.rb @@ -179,113 +179,6 @@ describe Projects::ErrorTrackingController do end end - describe 'POST #list_projects' do - context 'with insufficient permissions' do - before do - project.add_guest(user) - end - - it 'returns 404' do - post :list_projects, params: list_projects_params - - expect(response).to have_gitlab_http_status(:not_found) - end - end - - context 'with an anonymous user' do - before do - sign_out(user) - end - - it 'redirects to sign-in page' do - post :list_projects, params: list_projects_params - - expect(response).to have_gitlab_http_status(:redirect) - end - end - - context 'with authorized user' do - let(:list_projects_service) { spy(:list_projects_service) } - let(:sentry_project) { build(:error_tracking_project) } - - let(:permitted_params) do - ActionController::Parameters.new( - list_projects_params[:error_tracking_setting] - ).permit! - end - - before do - allow(ErrorTracking::ListProjectsService) - .to receive(:new).with(project, user, permitted_params) - .and_return(list_projects_service) - end - - context 'service result is successful' do - before do - expect(list_projects_service).to receive(:execute) - .and_return(status: :success, projects: [sentry_project]) - end - - it 'returns a list of projects' do - post :list_projects, params: list_projects_params - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('error_tracking/list_projects') - expect(json_response['projects']).to eq([sentry_project].as_json) - end - end - - context 'service result is erroneous' do - let(:error_message) { 'error message' } - - context 'without http_status' do - before do - expect(list_projects_service).to receive(:execute) - .and_return(status: :error, message: error_message) - end - - it 'returns 400 with message' do - get :list_projects, params: list_projects_params - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to eq(error_message) - end - end - - context 'with explicit http_status' do - let(:http_status) { :no_content } - - before do - expect(list_projects_service).to receive(:execute).and_return( - status: :error, - message: error_message, - http_status: http_status - ) - end - - it 'returns http_status with message' do - get :list_projects, params: list_projects_params - - expect(response).to have_gitlab_http_status(http_status) - expect(json_response['message']).to eq(error_message) - end - end - end - end - - private - - def list_projects_params(opts = {}) - project_params( - format: :json, - error_tracking_setting: { - api_host: 'gitlab.com', - token: 'token' - } - ) - end - end - describe 'GET #issue_details' do let_it_be(:issue_id) { 1234 } diff --git a/spec/features/dashboard/instance_statistics_spec.rb b/spec/features/dashboard/instance_statistics_spec.rb index 21ee2796bd8..feb568d8ef4 100644 --- a/spec/features/dashboard/instance_statistics_spec.rb +++ b/spec/features/dashboard/instance_statistics_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe 'Showing instance statistics' do +describe 'Showing analytics' do before do sign_in user if user end @@ -13,10 +13,10 @@ describe 'Showing instance statistics' do context 'for unauthenticated users' do let(:user) { nil } - it 'does not show the instance statistics link' do + it 'does not show the Analytics link' do subject - expect(page).not_to have_link('Instance Statistics') + expect(page).not_to have_link('Analytics') end end @@ -28,10 +28,10 @@ describe 'Showing instance statistics' do stub_application_setting(instance_statistics_visibility_private: false) end - it 'shows the instance statistics link' do + it 'shows the analytics link' do subject - expect(page).to have_link('Instance Statistics') + expect(page).to have_link('Analytics') end end @@ -40,10 +40,14 @@ describe 'Showing instance statistics' do stub_application_setting(instance_statistics_visibility_private: true) end - it 'shows the instance statistics link' do + it 'does not show the analytics link' do subject - expect(page).not_to have_link('Instance Statistics') + # Skipping this test on EE as there is an EE specifc spec for this functionality + # ee/spec/features/dashboards/analytics_spec.rb + skip if Gitlab.ee? + + expect(page).not_to have_link('Analytics') end end end @@ -51,10 +55,10 @@ describe 'Showing instance statistics' do context 'for admins' do let(:user) { create(:admin) } - it 'shows the instance statistics link' do + it 'shows the analytics link' do subject - expect(page).to have_link('Instance Statistics') + expect(page).to have_link('Analytics') end end end diff --git a/spec/finders/environments_finder_spec.rb b/spec/finders/environments_finder_spec.rb index 69687eaa99f..7100376478a 100644 --- a/spec/finders/environments_finder_spec.rb +++ b/spec/finders/environments_finder_spec.rb @@ -13,17 +13,22 @@ describe EnvironmentsFinder do end context 'tagged deployment' do + let(:environment_two) { create(:environment, project: project) } + # Environments need to include commits, so rewind two commits to fit + let(:commit) { project.commit('HEAD~2') } + before do - create(:deployment, :success, environment: environment, ref: 'v1.1.0', tag: true, sha: project.commit.id) + create(:deployment, :success, environment: environment, ref: 'v1.0.0', tag: true, sha: project.commit.id) + create(:deployment, :success, environment: environment_two, ref: 'v1.1.0', tag: true, sha: project.commit('HEAD~1').id) end it 'returns environment when with_tags is set' do - expect(described_class.new(project, user, ref: 'master', commit: project.commit, with_tags: true).execute) - .to contain_exactly(environment) + expect(described_class.new(project, user, ref: 'master', commit: commit, with_tags: true).execute) + .to contain_exactly(environment, environment_two) end it 'does not return environment when no with_tags is set' do - expect(described_class.new(project, user, ref: 'master', commit: project.commit).execute) + expect(described_class.new(project, user, ref: 'master', commit: commit).execute) .to be_empty end @@ -31,6 +36,21 @@ describe EnvironmentsFinder do expect(described_class.new(project, user, ref: 'master', commit: project.commit('feature')).execute) .to be_empty end + + it 'returns environment when with_tags is set' do + expect(described_class.new(project, user, ref: 'master', commit: commit, with_tags: true).execute) + .to contain_exactly(environment, environment_two) + end + + # We expect two Gitaly calls: FindCommit, CommitIsAncestor + # This tests to ensure we don't call one CommitIsAncestor per environment + it 'only calls Gitaly twice when multiple environments are present', :request_store do + expect do + result = described_class.new(project, user, ref: 'master', commit: commit, with_tags: true, find_latest: true).execute + + expect(result).to contain_exactly(environment_two) + end.to change { Gitlab::GitalyClient.get_request_count }.by(2) + end end context 'branch deployment' do diff --git a/spec/finders/events_finder_spec.rb b/spec/finders/events_finder_spec.rb index 848030262cd..5c28b31e8c8 100644 --- a/spec/finders/events_finder_spec.rb +++ b/spec/finders/events_finder_spec.rb @@ -5,8 +5,10 @@ require 'spec_helper' describe EventsFinder do let(:user) { create(:user) } let(:other_user) { create(:user) } + let(:project1) { create(:project, :private, creator_id: user.id, namespace: user.namespace) } let(:project2) { create(:project, :private, creator_id: user.id, namespace: user.namespace) } + let(:closed_issue) { create(:closed_issue, project: project1, author: user) } let(:opened_merge_request) { create(:merge_request, source_project: project2, author: user) } let!(:closed_issue_event) { create(:event, project: project1, author: user, target: closed_issue, action: Event::CLOSED, created_at: Date.new(2016, 12, 30)) } @@ -15,6 +17,8 @@ describe EventsFinder do let(:opened_merge_request2) { create(:merge_request, source_project: project2, author: user) } let!(:closed_issue_event2) { create(:event, project: project1, author: user, target: closed_issue, action: Event::CLOSED, created_at: Date.new(2016, 2, 2)) } let!(:opened_merge_request_event2) { create(:event, project: project2, author: user, target: opened_merge_request, action: Event::CREATED, created_at: Date.new(2017, 2, 2)) } + let(:opened_merge_request3) { create(:merge_request, source_project: project1, author: other_user) } + let!(:other_developer_event) { create(:event, project: project1, author: other_user, target: opened_merge_request3, action: Event::CREATED) } let(:public_project) { create(:project, :public, creator_id: user.id, namespace: user.namespace) } let(:confidential_issue) { create(:closed_issue, confidential: true, project: public_project, author: user) } @@ -55,6 +59,28 @@ describe EventsFinder do end end + context 'dashboard events' do + before do + project1.add_developer(other_user) + end + + context 'scope is `all`' do + it 'includes activity of other users' do + events = described_class.new(source: user, current_user: user, scope: 'all').execute + + expect(events).to include(other_developer_event) + end + end + + context 'scope is not `all`' do + it 'does not include activity of other users' do + events = described_class.new(source: user, current_user: user, scope: '').execute + + expect(events).not_to include(other_developer_event) + end + end + end + context 'when targeting a project' do it 'returns project events between specified dates filtered on action and type' do events = described_class.new(source: project1, current_user: user, action: 'closed', target_type: 'issue', after: Date.new(2016, 12, 1), before: Date.new(2017, 1, 1)).execute diff --git a/spec/frontend/error_tracking_settings/store/actions_spec.js b/spec/frontend/error_tracking_settings/store/actions_spec.js index e12c4e20f58..b076e6ecd31 100644 --- a/spec/frontend/error_tracking_settings/store/actions_spec.js +++ b/spec/frontend/error_tracking_settings/store/actions_spec.js @@ -28,7 +28,7 @@ describe('error tracking settings actions', () => { }); it('should request and transform the project list', done => { - mock.onPost(TEST_HOST).reply(() => [200, { projects: projectList }]); + mock.onGet(TEST_HOST).reply(() => [200, { projects: projectList }]); testAction( actions.fetchProjects, null, @@ -42,14 +42,14 @@ describe('error tracking settings actions', () => { }, ], () => { - expect(mock.history.post.length).toBe(1); + expect(mock.history.get.length).toBe(1); done(); }, ); }); it('should handle a server error', done => { - mock.onPost(`${TEST_HOST}.json`).reply(() => [400]); + mock.onGet(`${TEST_HOST}.json`).reply(() => [400]); testAction( actions.fetchProjects, null, @@ -62,7 +62,7 @@ describe('error tracking settings actions', () => { }, ], () => { - expect(mock.history.post.length).toBe(1); + expect(mock.history.get.length).toBe(1); done(); }, ); diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 0537220fcd2..b82a9e9aa9d 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -36,9 +36,13 @@ describe Environment, :use_clean_rails_memory_store_caching do let!(:deployment2) { create(:deployment, environment: environment2) } let!(:deployment3) { create(:deployment, environment: environment1) } - it 'returns the environments in order of having been last deployed' do + it 'returns the environments in ascending order of having been last deployed' do expect(project.environments.order_by_last_deployed_at.to_a).to eq([environment3, environment2, environment1]) end + + it 'returns the environments in descending order of having been last deployed' do + expect(project.environments.order_by_last_deployed_at_desc.to_a).to eq([environment1, environment2, environment3]) + end end describe 'state machine' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index bf6fa20dc17..bf90fa53aba 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -2322,6 +2322,10 @@ describe MergeRequest do let(:project) { create(:project, :repository) } let(:user) { project.creator } let(:merge_request) { create(:merge_request, source_project: project) } + let(:source_branch) { merge_request.source_branch } + let(:target_branch) { merge_request.target_branch } + let(:source_oid) { project.commit(source_branch).id } + let(:target_oid) { project.commit(target_branch).id } before do merge_request.source_project.add_maintainer(user) @@ -2332,13 +2336,21 @@ describe MergeRequest do let(:environments) { create_list(:environment, 3, project: project) } before do - create(:deployment, :success, environment: environments.first, ref: 'master', sha: project.commit('master').id) - create(:deployment, :success, environment: environments.second, ref: 'feature', sha: project.commit('feature').id) + create(:deployment, :success, environment: environments.first, ref: source_branch, sha: source_oid) + create(:deployment, :success, environment: environments.second, ref: target_branch, sha: target_oid) end it 'selects deployed environments' do expect(merge_request.environments_for(user)).to contain_exactly(environments.first) end + + it 'selects latest deployed environment' do + latest_environment = create(:environment, project: project) + create(:deployment, :success, environment: latest_environment, ref: source_branch, sha: source_oid) + + expect(merge_request.environments_for(user)).to eq([environments.first, latest_environment]) + expect(merge_request.environments_for(user, latest: true)).to contain_exactly(latest_environment) + end end context 'with environments on source project' do diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb index 9f8d254a00c..240f9a02877 100644 --- a/spec/requests/api/events_spec.rb +++ b/spec/requests/api/events_spec.rb @@ -8,6 +8,8 @@ describe API::Events do let(:private_project) { create(:project, :private, creator_id: user.id, namespace: user.namespace) } let(:closed_issue) { create(:closed_issue, project: private_project, author: user) } let!(:closed_issue_event) { create(:event, project: private_project, author: user, target: closed_issue, action: Event::CLOSED, created_at: Date.new(2016, 12, 30)) } + let(:closed_issue2) { create(:closed_issue, project: private_project, author: non_member) } + let!(:closed_issue_event2) { create(:event, project: private_project, author: non_member, target: closed_issue2, action: Event::CLOSED, created_at: Date.new(2016, 12, 30)) } describe 'GET /events' do context 'when unauthenticated' do @@ -27,6 +29,19 @@ describe API::Events do expect(json_response).to be_an Array expect(json_response.size).to eq(1) end + + context 'when scope is passed' do + it 'returns all events across projects' do + private_project.add_developer(non_member) + + get api('/events?action=closed&target_type=issue&after=2016-12-1&before=2016-12-31&scope=all', user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.size).to eq(2) + end + end end context 'when the requesting token has "read_user" scope' do |