diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-05 18:09:06 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-05 18:09:06 +0000 |
commit | b042382bbf5a4977c5b5c6b0a9a33f4e8ca8d16d (patch) | |
tree | de31671ab7c6ca8c2a3721cbabd1f2a42b3d0194 | |
parent | eabf8fd774fef6a54903e5141138f47bdafeb331 (diff) | |
download | gitlab-ce-b042382bbf5a4977c5b5c6b0a9a33f4e8ca8d16d.tar.gz |
Add latest changes from gitlab-org/gitlab@master
43 files changed, 2322 insertions, 1531 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 7dc1c302304..7e29e112bb0 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -1,3 +1,7 @@ +# When adding a group as a code owner, make sure to invite the group to the +# project here: https://gitlab.com/gitlab-org/gitlab/-/project_members +# As described in https://docs.gitlab.com/ee/user/project/code_owners.html + # Backend Maintainers are the default for all ruby files *.rb @gitlab-org/maintainers/rails-backend *.rake @gitlab-org/maintainers/rails-backend @@ -28,9 +32,13 @@ lib/gitlab/github_import/ @gitlab-org/maintainers/database /ee/app/models/project_alias.rb @patrickbajao /ee/lib/api/project_aliases.rb @patrickbajao +# Quality owned files +/qa/ @gl-quality + # Engineering Productivity owned files /.gitlab-ci.yml @gl-quality/eng-prod /.gitlab/ci/ @gl-quality/eng-prod +/.gitlab/CODEOWNERS @gl-quality/eng-prod Dangerfile @gl-quality/eng-prod /danger/ @gl-quality/eng-prod /lib/gitlab/danger/ @gl-quality/eng-prod diff --git a/Gemfile.lock b/Gemfile.lock index 08143421c9b..c8912d70810 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1326,7 +1326,7 @@ DEPENDENCIES pry-rails (~> 0.3.9) rack (~> 2.0.7) rack-attack (~> 6.2.0) - rack-cors (~> 1.0.0) + rack-cors (~> 1.0.6) rack-oauth2 (~> 1.9.3) rack-proxy (~> 0.6.0) rack-timeout diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index fa88ca91170..7cb629dee21 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -37,6 +37,7 @@ class ApplicationController < ActionController::Base around_action :set_current_context around_action :set_locale around_action :set_session_storage + around_action :set_current_admin after_action :set_page_title_header, if: :json_request? after_action :limit_session_time, if: -> { !current_user } @@ -473,6 +474,13 @@ class ApplicationController < ActionController::Base response.headers['Page-Title'] = URI.escape(page_title('GitLab')) end + def set_current_admin(&block) + return yield unless Feature.enabled?(:user_mode_in_session) + return yield unless current_user + + Gitlab::Auth::CurrentUserMode.with_current_admin(current_user, &block) + end + def html_request? request.format.html? end diff --git a/app/controllers/concerns/cycle_analytics_params.rb b/app/controllers/concerns/cycle_analytics_params.rb index a78d803927c..3e67f1f54cb 100644 --- a/app/controllers/concerns/cycle_analytics_params.rb +++ b/app/controllers/concerns/cycle_analytics_params.rb @@ -10,9 +10,9 @@ module CycleAnalyticsParams end def cycle_analytics_group_params - return {} unless params[:cycle_analytics].present? + return {} unless params.present? - params[:cycle_analytics].permit(:start_date, :created_after, :created_before, project_ids: []) + params.permit(:group_id, :start_date, :created_after, :created_before, project_ids: []) end def options(params) diff --git a/app/services/merge_requests/migrate_external_diffs_service.rb b/app/services/merge_requests/migrate_external_diffs_service.rb index 9fa01e0a134..89b1e594c95 100644 --- a/app/services/merge_requests/migrate_external_diffs_service.rb +++ b/app/services/merge_requests/migrate_external_diffs_service.rb @@ -9,7 +9,10 @@ module MergeRequests def self.enqueue! ids = MergeRequestDiff.ids_for_external_storage_migration(limit: MAX_JOBS) - MigrateExternalDiffsWorker.bulk_perform_async(ids.map { |id| [id] }) # rubocop:disable Scalability/BulkPerformWithContext + # rubocop:disable Scalability/BulkPerformWithContext + # https://gitlab.com/gitlab-org/gitlab/issues/202100 + MigrateExternalDiffsWorker.bulk_perform_async(ids.map { |id| [id] }) + # rubocop:enable Scalability/BulkPerformWithContext end def initialize(merge_request_diff) diff --git a/app/services/projects/operations/update_service.rb b/app/services/projects/operations/update_service.rb index 706a6f01a75..3ee6c2467c2 100644 --- a/app/services/projects/operations/update_service.rb +++ b/app/services/projects/operations/update_service.rb @@ -30,6 +30,27 @@ module Projects settings = params[:error_tracking_setting_attributes] return {} if settings.blank? + if error_tracking_params_partial_updates?(settings) + error_tracking_params_for_partial_update(settings) + else + error_tracking_params_for_update(settings) + end + end + + def error_tracking_params_partial_updates?(settings) + # Help from @splattael :bow: + # Make sure we're converting to symbols because + # * ActionController::Parameters#keys returns a list of strings + # * in specs we're using hashes with symbols as keys + + settings.keys.map(&:to_sym) == %i[enabled] + end + + def error_tracking_params_for_partial_update(settings) + { error_tracking_setting_attributes: settings } + end + + def error_tracking_params_for_update(settings) api_url = ::ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from( api_host: settings[:api_host], project_slug: settings.dig(:project, :slug), diff --git a/app/workers/schedule_migrate_external_diffs_worker.rb b/app/workers/schedule_migrate_external_diffs_worker.rb index 4c6f19cbc8f..0e3c62cf282 100644 --- a/app/workers/schedule_migrate_external_diffs_worker.rb +++ b/app/workers/schedule_migrate_external_diffs_worker.rb @@ -2,7 +2,12 @@ class ScheduleMigrateExternalDiffsWorker include ApplicationWorker - include CronjobQueue # rubocop:disable Scalability/CronWorkerContext + # rubocop:disable Scalability/CronWorkerContext: + # This schedules the `MigrateExternalDiffsWorker` + # issue for adding context: https://gitlab.com/gitlab-org/gitlab/issues/202100 + include CronjobQueue + # rubocop:enable Scalability/CronWorkerContext: + include Gitlab::ExclusiveLeaseHelpers feature_category :source_code_management diff --git a/changelogs/unreleased/28352-group-issues-api-searches-archived-projects.yml b/changelogs/unreleased/28352-group-issues-api-searches-archived-projects.yml new file mode 100644 index 00000000000..887d992d27e --- /dev/null +++ b/changelogs/unreleased/28352-group-issues-api-searches-archived-projects.yml @@ -0,0 +1,5 @@ +--- +title: Add non_archived param to issues API endpoint to filter issues from archived projects +merge_request: 23785 +author: +type: added diff --git a/changelogs/unreleased/28352-group-merge-request-api-searches-archived-projects.yml b/changelogs/unreleased/28352-group-merge-request-api-searches-archived-projects.yml new file mode 100644 index 00000000000..58aa0afdf94 --- /dev/null +++ b/changelogs/unreleased/28352-group-merge-request-api-searches-archived-projects.yml @@ -0,0 +1,5 @@ +--- +title: Add non_archived param to group merge requests API endpoint to filter MRs from non archived projects +merge_request: 23809 +author: +type: added diff --git a/changelogs/unreleased/enable-disable-error-tracking-api.yml b/changelogs/unreleased/enable-disable-error-tracking-api.yml new file mode 100644 index 00000000000..90f28ef51fe --- /dev/null +++ b/changelogs/unreleased/enable-disable-error-tracking-api.yml @@ -0,0 +1,5 @@ +--- +title: Add API to enable and disable error tracking settings +merge_request: 24220 +author: Rajendra Kadam +type: added diff --git a/changelogs/unreleased/refactor-admin-mode-in-sidekiq-jobs.yml b/changelogs/unreleased/refactor-admin-mode-in-sidekiq-jobs.yml new file mode 100644 index 00000000000..dbb14c60842 --- /dev/null +++ b/changelogs/unreleased/refactor-admin-mode-in-sidekiq-jobs.yml @@ -0,0 +1,5 @@ +--- +title: Admin mode support in sidekiq jobs +merge_request: 24388 +author: Diego Louzán +type: changed diff --git a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md index 49f42ed1238..a8a6f7b04e1 100644 --- a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md +++ b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md @@ -941,7 +941,7 @@ projects.each do |p| container_repositories.each do |c| c.tags.each do |t| - project_total_size = project_total_size + t.total_size + project_total_size = project_total_size + t.total_size unless t.total_size.nil? end end diff --git a/doc/api/error_tracking.md b/doc/api/error_tracking.md index 6efafd4189a..e20b74d764b 100644 --- a/doc/api/error_tracking.md +++ b/doc/api/error_tracking.md @@ -30,3 +30,31 @@ Example response: "api_url": "https://sentry.io/api/0/projects/myawesomeproject/project" } ``` + +### Enable or disable the Error Tracking project settings + +The API allows you to enable or disable the Error Tracking settings for a project. Only for project maintainers. + +``` +PATCH /projects/:id/error_tracking/settings +``` + +| Attribute | Type | Required | Description | +| --------- | ------- | -------- | --------------------- | +| `id` | integer | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `active` | boolean | yes | Pass `true` to enable the already configured error tracking settings or `false` to disable it. | + +```shell +curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/error_tracking/settings?active=true +``` + +Example response: + +```json +{ + "active": true, + "project_name": "sample sentry project", + "sentry_external_url": "https://sentry.io/myawesomeproject/project", + "api_url": "https://sentry.io/api/0/projects/myawesomeproject/project" +} +``` diff --git a/doc/api/issues.md b/doc/api/issues.md index 68fcc55acf2..2bb25c6e7d5 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -222,6 +222,7 @@ GET /groups/:id/issues?confidential=true | `updated_before` | datetime | no | Return issues updated on or before the given time | | `confidential` | Boolean | no | Filter confidential or public issues. | | `not` | Hash | no | Return issues that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `my_reaction_emoji`, `search`, `in` | +| `non_archived` | Boolean | no | Return issues from non archived projects. Default is true. _(Introduced in [GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23785))_ | ```shell curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/4/issues diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 266f419e9be..750bdbcb050 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -396,7 +396,8 @@ Parameters: | `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ | | `source_branch` | string | no | Return merge requests with the given source branch | | `target_branch` | string | no | Return merge requests with the given target branch | -| `search` | string | no | Search merge requests against their `title` and `description` | +| `search` | string | no | Search merge requests against their `title` and `description` | +| `non_archived` | Boolean | no | Return merge requests from non archived projects only. Default is true. _(Introduced in [GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23809))_ | ```json [ diff --git a/doc/user/project/import/repo_by_url.md b/doc/user/project/import/repo_by_url.md index 96debcb00a8..c20b1cb7f5e 100644 --- a/doc/user/project/import/repo_by_url.md +++ b/doc/user/project/import/repo_by_url.md @@ -9,7 +9,4 @@ You can import your existing repositories by providing the Git URL: 1. Click **Create project** to begin the import process 1. Once complete, you will be redirected to your newly created project -NOTE: **Note:** -If your password has special characters, you will need to enter them URL encoded, please see the [GitLab issue](https://gitlab.com/gitlab-org/gitlab/issues/29952) for more information. - ![Import project by repo URL](img/import_projects_from_repo_url.png) diff --git a/lib/api/error_tracking.rb b/lib/api/error_tracking.rb index f92f1326daa..14888037f53 100644 --- a/lib/api/error_tracking.rb +++ b/lib/api/error_tracking.rb @@ -23,6 +23,34 @@ module API present setting, with: Entities::ErrorTracking::ProjectSetting end + + desc 'Enable or disable error tracking settings for the project' do + detail 'This feature was introduced in GitLab 12.8.' + success Entities::ErrorTracking::ProjectSetting + end + params do + requires :active, type: Boolean, desc: 'Specifying whether to enable or disable error tracking settings', allow_blank: false + end + + patch ':id/error_tracking/settings/' do + authorize! :admin_operations, user_project + + setting = user_project.error_tracking_setting + + not_found!('Error Tracking Setting') unless setting + + update_params = { + error_tracking_setting_attributes: { enabled: params[:active] } + } + + result = ::Projects::Operations::UpdateService.new(user_project, current_user, update_params).execute + + if result[:status] == :success + present setting, with: Entities::ErrorTracking::ProjectSetting + else + result + end + end end end end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 4e21815fa35..e5bfca13d66 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -120,6 +120,7 @@ module API end params do use :issues_params + optional :non_archived, type: Boolean, desc: 'Return issues from non archived projects', default: true end get ":id/issues" do issues = paginate(find_issues(group_id: user_group.id, include_subgroups: true)) diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index c4ab84c5200..2b1bcc855d2 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -141,6 +141,8 @@ module API end params do use :merge_requests_params + optional :non_archived, type: Boolean, desc: 'Return merge requests from non archived projects', + default: true end get ":id/merge_requests" do merge_requests = find_merge_requests(group_id: user_group.id, include_subgroups: true) diff --git a/lib/gitlab/auth/current_user_mode.rb b/lib/gitlab/auth/current_user_mode.rb index cb39baaa6cc..1ef95c03cfc 100644 --- a/lib/gitlab/auth/current_user_mode.rb +++ b/lib/gitlab/auth/current_user_mode.rb @@ -10,12 +10,54 @@ module Gitlab class CurrentUserMode NotRequestedError = Class.new(StandardError) + # RequestStore entries + CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY = { res: :current_user_mode, data: :bypass_session_admin_id }.freeze + CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY = { res: :current_user_mode, data: :current_admin }.freeze + + # SessionStore entries SESSION_STORE_KEY = :current_user_mode - ADMIN_MODE_START_TIME_KEY = 'admin_mode' - ADMIN_MODE_REQUESTED_TIME_KEY = 'admin_mode_requested' + ADMIN_MODE_START_TIME_KEY = :admin_mode + ADMIN_MODE_REQUESTED_TIME_KEY = :admin_mode_requested MAX_ADMIN_MODE_TIME = 6.hours ADMIN_MODE_REQUESTED_GRACE_PERIOD = 5.minutes + class << self + # Admin mode activation requires storing a flag in the user session. Using this + # method when scheduling jobs in Sidekiq will bypass the session check for a + # user that was already in admin mode + def bypass_session!(admin_id) + Gitlab::SafeRequestStore[CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY] = admin_id + + Gitlab::AppLogger.debug("Bypassing session in admin mode for: #{admin_id}") + + yield + ensure + Gitlab::SafeRequestStore.delete(CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY) + end + + def bypass_session_admin_id + Gitlab::SafeRequestStore[CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY] + end + + # Store in the current request the provided user model (only if in admin mode) + # and yield + def with_current_admin(admin) + return yield unless self.new(admin).admin_mode? + + Gitlab::SafeRequestStore[CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY] = admin + + Gitlab::AppLogger.debug("Admin mode active for: #{admin.username}") + + yield + ensure + Gitlab::SafeRequestStore.delete(CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY) + end + + def current_admin + Gitlab::SafeRequestStore[CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY] + end + end + def initialize(user) @user = user end @@ -42,7 +84,7 @@ module Gitlab raise NotRequestedError unless admin_mode_requested? - reset_request_store + reset_request_store_cache_entries current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = nil current_session_data[ADMIN_MODE_START_TIME_KEY] = Time.now @@ -55,7 +97,7 @@ module Gitlab def disable_admin_mode! return unless user&.admin? - reset_request_store + reset_request_store_cache_entries current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = nil current_session_data[ADMIN_MODE_START_TIME_KEY] = nil @@ -64,7 +106,7 @@ module Gitlab def request_admin_mode! return unless user&.admin? - reset_request_store + reset_request_store_cache_entries current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = Time.now end @@ -73,10 +115,12 @@ module Gitlab attr_reader :user + # RequestStore entry to cache #admin_mode? result def admin_mode_rs_key @admin_mode_rs_key ||= { res: :current_user_mode, user: user.id, method: :admin_mode? } end + # RequestStore entry to cache #admin_mode_requested? result def admin_mode_requested_rs_key @admin_mode_requested_rs_key ||= { res: :current_user_mode, user: user.id, method: :admin_mode_requested? } end @@ -86,6 +130,7 @@ module Gitlab end def any_session_with_admin_mode? + return true if bypass_session? return true if current_session_data.initiated? && current_session_data[ADMIN_MODE_START_TIME_KEY].to_i > MAX_ADMIN_MODE_TIME.ago.to_i all_sessions.any? do |session| @@ -103,7 +148,11 @@ module Gitlab current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY].to_i > ADMIN_MODE_REQUESTED_GRACE_PERIOD.ago.to_i end - def reset_request_store + def bypass_session? + user&.id && user.id == self.class.bypass_session_admin_id + end + + def reset_request_store_cache_entries Gitlab::SafeRequestStore.delete(admin_mode_rs_key) Gitlab::SafeRequestStore.delete(admin_mode_requested_rs_key) end diff --git a/lib/gitlab/sidekiq_middleware.rb b/lib/gitlab/sidekiq_middleware.rb index 439d45b7a14..6c27213df49 100644 --- a/lib/gitlab/sidekiq_middleware.rb +++ b/lib/gitlab/sidekiq_middleware.rb @@ -17,6 +17,7 @@ module Gitlab chain.add Gitlab::SidekiqMiddleware::BatchLoader chain.add Labkit::Middleware::Sidekiq::Server chain.add Gitlab::SidekiqMiddleware::InstrumentationLogger + chain.add Gitlab::SidekiqMiddleware::AdminMode::Server chain.add Gitlab::SidekiqStatus::ServerMiddleware chain.add Gitlab::SidekiqMiddleware::WorkerContext::Server end @@ -31,6 +32,7 @@ module Gitlab chain.add Gitlab::SidekiqMiddleware::ClientMetrics chain.add Gitlab::SidekiqMiddleware::WorkerContext::Client # needs to be before the Labkit middleware chain.add Labkit::Middleware::Sidekiq::Client + chain.add Gitlab::SidekiqMiddleware::AdminMode::Client end end end diff --git a/lib/gitlab/sidekiq_middleware/admin_mode/client.rb b/lib/gitlab/sidekiq_middleware/admin_mode/client.rb new file mode 100644 index 00000000000..e227ee654ee --- /dev/null +++ b/lib/gitlab/sidekiq_middleware/admin_mode/client.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Gitlab + module SidekiqMiddleware + module AdminMode + # Checks if admin mode is enabled for the request creating the sidekiq job + # by examining if admin mode has been enabled for the user + # If enabled then it injects a job field that persists through the job execution + class Client + def call(_worker_class, job, _queue, _redis_pool) + return yield unless Feature.enabled?(:user_mode_in_session) + + # Admin mode enabled in the original request or in a nested sidekiq job + admin_mode_user_id = find_admin_user_id + + if admin_mode_user_id + job['admin_mode_user_id'] ||= admin_mode_user_id + + Gitlab::AppLogger.debug("AdminMode::Client injected admin mode for job: #{job.inspect}") + end + + yield + end + + private + + def find_admin_user_id + Gitlab::Auth::CurrentUserMode.current_admin&.id || + Gitlab::Auth::CurrentUserMode.bypass_session_admin_id + end + end + end + end +end diff --git a/lib/gitlab/sidekiq_middleware/admin_mode/server.rb b/lib/gitlab/sidekiq_middleware/admin_mode/server.rb new file mode 100644 index 00000000000..6366867a0fa --- /dev/null +++ b/lib/gitlab/sidekiq_middleware/admin_mode/server.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Gitlab + module SidekiqMiddleware + module AdminMode + class Server + def call(_worker, job, _queue) + return yield unless Feature.enabled?(:user_mode_in_session) + + admin_mode_user_id = job['admin_mode_user_id'] + + # Do not bypass session if this job was not enabled with admin mode on + return yield unless admin_mode_user_id + + Gitlab::Auth::CurrentUserMode.bypass_session!(admin_mode_user_id) do + Gitlab::AppLogger.debug("AdminMode::Server bypasses session for admin mode in job: #{job.inspect}") + + yield + end + end + end + end + end +end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 07b82bdff04..1c58c2b5c97 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -96,7 +96,7 @@ describe GroupsController do User.where(id: [admin, owner, maintainer, developer, guest]).update_all(can_create_group: can_create_group_status) end - [:admin, :owner].each do |member_type| + [:admin, :owner, :maintainer].each do |member_type| context "and logged in as #{member_type.capitalize}" do it_behaves_like 'member with ability to create subgroups' do let(:member) { send(member_type) } @@ -104,7 +104,7 @@ describe GroupsController do end end - [:guest, :developer, :maintainer].each do |member_type| + [:guest, :developer].each do |member_type| context "and logged in as #{member_type.capitalize}" do it_behaves_like 'member without ability to create subgroups' do let(:member) { send(member_type) } diff --git a/spec/factories/project_error_tracking_settings.rb b/spec/factories/project_error_tracking_settings.rb index 5d3fb284eef..e09d58d293f 100644 --- a/spec/factories/project_error_tracking_settings.rb +++ b/spec/factories/project_error_tracking_settings.rb @@ -8,5 +8,9 @@ FactoryBot.define do token { 'access_token_123' } project_name { 'Sentry Project' } organization_name { 'Sentry Org' } + + trait :disabled do + enabled { false } + end end end diff --git a/spec/features/admin/admin_mode/workers_spec.rb b/spec/features/admin/admin_mode/workers_spec.rb new file mode 100644 index 00000000000..8c515b8d220 --- /dev/null +++ b/spec/features/admin/admin_mode/workers_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# Test an operation that triggers background jobs requiring administrative rights +describe 'Admin mode for workers', :do_not_mock_admin_mode, :request_store, :clean_gitlab_redis_shared_state do + let(:user) { create(:user) } + let(:user_to_delete) { create(:user) } + + before do + add_sidekiq_middleware + + sign_in(user) + end + + context 'as a regular user' do + it 'cannot delete user' do + visit admin_user_path(user_to_delete) + + expect(page).to have_gitlab_http_status(:not_found) + end + end + + context 'as an admin user' do + let(:user) { create(:admin) } + + context 'when admin mode disabled' do + it 'cannot delete user', :js do + visit admin_user_path(user_to_delete) + + expect(page).to have_content('Re-authentication required') + end + end + + context 'when admin mode enabled', :delete do + before do + gitlab_enable_admin_mode_sign_in(user) + end + + it 'can delete user', :sidekiq, :js do + visit admin_user_path(user_to_delete) + click_button 'Delete user' + + page.within '.modal-dialog' do + find("input[name='username']").send_keys(user_to_delete.name) + click_button 'Delete user' + + wait_for_requests + end + + expect(page).to have_content('The user is being deleted.') + + # Perform jobs while logged out so that admin mode is only enabled in job metadata + execute_jobs_signed_out(user) + + visit admin_user_path(user_to_delete) + + expect(page).to have_title('Not Found') + end + end + end + + def add_sidekiq_middleware + Sidekiq::Testing.server_middleware do |chain| + chain.add Gitlab::SidekiqMiddleware::AdminMode::Server + end + end + + def execute_jobs_signed_out(user) + gitlab_sign_out + + Sidekiq::Worker.drain_all + + sign_in(user) + gitlab_enable_admin_mode_sign_in(user) + end +end diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index 3e8197588ed..954773e766d 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -2,46 +2,64 @@ require 'spec_helper' -describe 'Admin uses repository checks' do +describe 'Admin uses repository checks', :request_store, :clean_gitlab_redis_shared_state, :do_not_mock_admin_mode do include StubENV + let(:admin) { create(:admin) } + before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') - sign_in(create(:admin)) + sign_in(admin) end - it 'to trigger a single check' do - project = create(:project) - visit_admin_project_page(project) + context 'when admin mode is disabled' do + it 'admin project page requires admin mode' do + project = create(:project) + visit_admin_project_page(project) - page.within('.repository-check') do - click_button 'Trigger repository check' + expect(page).not_to have_css('.repository-check') + expect(page).to have_content('Enter Admin Mode') end - - expect(page).to have_content('Repository check was triggered') end - it 'to see a single failed repository check', :js do - project = create(:project) - project.update_columns( - last_repository_check_failed: true, - last_repository_check_at: Time.now - ) - visit_admin_project_page(project) + context 'when admin mode is enabled' do + before do + gitlab_enable_admin_mode_sign_in(admin) + end + + it 'to trigger a single check', :js do + project = create(:project) + visit_admin_project_page(project) + + page.within('.repository-check') do + click_button 'Trigger repository check' + end - page.within('.alert') do - expect(page.text).to match(/Last repository check \(just now\) failed/) + expect(page).to have_content('Repository check was triggered') end - end - it 'to clear all repository checks', :js do - visit repository_admin_application_settings_path + it 'to see a single failed repository check', :js do + project = create(:project) + project.update_columns( + last_repository_check_failed: true, + last_repository_check_at: Time.now + ) + visit_admin_project_page(project) + + page.within('.alert') do + expect(page.text).to match(/Last repository check \(just now\) failed/) + end + end - expect(RepositoryCheck::ClearWorker).to receive(:perform_async) + it 'to clear all repository checks', :js do + visit repository_admin_application_settings_path - accept_confirm { find(:link, 'Clear all repository checks').send_keys(:return) } + expect(RepositoryCheck::ClearWorker).to receive(:perform_async) - expect(page).to have_content('Started asynchronous removal of all repository check states.') + accept_confirm { find(:link, 'Clear all repository checks').send_keys(:return) } + + expect(page).to have_content('Started asynchronous removal of all repository check states.') + end end def visit_admin_project_page(project) diff --git a/spec/frontend/helpers/dom_shims/index.js b/spec/frontend/helpers/dom_shims/index.js index 1fc5130cefc..95a28f8bfbd 100644 --- a/spec/frontend/helpers/dom_shims/index.js +++ b/spec/frontend/helpers/dom_shims/index.js @@ -1,2 +1,3 @@ import './get_client_rects'; import './inner_text'; +import './window_scroll_to'; diff --git a/spec/frontend/helpers/dom_shims/window_scroll_to.js b/spec/frontend/helpers/dom_shims/window_scroll_to.js new file mode 100644 index 00000000000..20ae1910bf3 --- /dev/null +++ b/spec/frontend/helpers/dom_shims/window_scroll_to.js @@ -0,0 +1 @@ +window.scrollTo = jest.fn(); diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/frontend/jobs/components/job_app_spec.js index 31b49c45908..8fa289bbe4d 100644 --- a/spec/javascripts/jobs/components/job_app_spec.js +++ b/spec/frontend/jobs/components/job_app_spec.js @@ -1,18 +1,20 @@ -import Vue from 'vue'; +import Vuex from 'vuex'; +import { mount, createLocalVue } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; -import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import { waitForMutation } from 'spec/helpers/vue_test_utils_helper'; +import { getJSONFixture } from 'helpers/fixtures'; import axios from '~/lib/utils/axios_utils'; -import jobApp from '~/jobs/components/job_app.vue'; +import JobApp from '~/jobs/components/job_app.vue'; import createStore from '~/jobs/store'; -import * as types from '~/jobs/store/mutation_types'; import job from '../mock_data'; -describe('Job App ', () => { +describe('Job App', () => { + const localVue = createLocalVue(); + localVue.use(Vuex); + const delayedJobFixture = getJSONFixture('jobs/delayed.json'); - const Component = Vue.extend(jobApp); + let store; - let vm; + let wrapper; let mock; const initSettings = { @@ -32,16 +34,24 @@ describe('Job App ', () => { subscriptionsMoreMinutesUrl: 'https://customers.gitlab.com/buy_pipeline_minutes', }; - const waitForJobReceived = () => waitForMutation(store, types.RECEIVE_JOB_SUCCESS); + const createComponent = () => { + wrapper = mount(JobApp, { propsData: { ...props }, store }); + }; + const setupAndMount = ({ jobData = {}, traceData = {} } = {}) => { mock.onGet(initSettings.endpoint).replyOnce(200, { ...job, ...jobData }); mock.onGet(`${initSettings.pagePath}/trace.json`).reply(200, traceData); - store.dispatch('init', initSettings); + const asyncInit = store.dispatch('init', initSettings); - vm = mountComponentWithStore(Component, { props, store }); + createComponent(); - return waitForJobReceived(); + return asyncInit + .then(() => { + jest.runOnlyPendingTimers(); + }) + .then(() => axios.waitForAll()) + .then(() => wrapper.vm.$nextTick()); }; beforeEach(() => { @@ -50,94 +60,81 @@ describe('Job App ', () => { }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); mock.restore(); }); describe('while loading', () => { beforeEach(() => { - setupAndMount(); + store.state.isLoading = true; + createComponent(); }); it('renders loading icon', () => { - expect(vm.$el.querySelector('.js-job-loading')).not.toBeNull(); - expect(vm.$el.querySelector('.js-job-sidebar')).toBeNull(); - expect(vm.$el.querySelector('.js-job-content')).toBeNull(); + expect(wrapper.find('.js-job-loading').exists()).toBe(true); + expect(wrapper.find('.js-job-sidebar').exists()).toBe(false); + expect(wrapper.find('.js-job-content').exists()).toBe(false); }); }); describe('with successful request', () => { describe('Header section', () => { describe('job callout message', () => { - it('should not render the reason when reason is absent', done => { - setupAndMount() - .then(() => { - expect(vm.shouldRenderCalloutMessage).toBe(false); - }) - .then(done) - .catch(done.fail); - }); + it('should not render the reason when reason is absent', () => + setupAndMount().then(() => { + expect(wrapper.vm.shouldRenderCalloutMessage).toBe(false); + })); - it('should render the reason when reason is present', done => { + it('should render the reason when reason is present', () => setupAndMount({ jobData: { callout_message: 'There is an unkown failure, please try again', }, - }) - .then(() => { - expect(vm.shouldRenderCalloutMessage).toBe(true); - }) - .then(done) - .catch(done.fail); - }); + }).then(() => { + expect(wrapper.vm.shouldRenderCalloutMessage).toBe(true); + })); }); describe('triggered job', () => { - beforeEach(done => { + beforeEach(() => { const aYearAgo = new Date(); aYearAgo.setFullYear(aYearAgo.getFullYear() - 1); - setupAndMount({ jobData: { started: aYearAgo.toISOString() } }) - .then(done) - .catch(done.fail); + return setupAndMount({ jobData: { started: aYearAgo.toISOString() } }); }); it('should render provided job information', () => { expect( - vm.$el - .querySelector('.header-main-content') - .textContent.replace(/\s+/g, ' ') + wrapper + .find('.header-main-content') + .text() + .replace(/\s+/g, ' ') .trim(), ).toContain('passed Job #4757 triggered 1 year ago by Root'); }); it('should render new issue link', () => { - expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual( - job.new_issue_path, - ); + expect(wrapper.find('.js-new-issue').attributes('href')).toEqual(job.new_issue_path); }); }); describe('created job', () => { - it('should render created key', done => { - setupAndMount() - .then(() => { - expect( - vm.$el - .querySelector('.header-main-content') - .textContent.replace(/\s+/g, ' ') - .trim(), - ).toContain('passed Job #4757 created 3 weeks ago by Root'); - }) - .then(done) - .catch(done.fail); - }); + it('should render created key', () => + setupAndMount().then(() => { + expect( + wrapper + .find('.header-main-content') + .text() + .replace(/\s+/g, ' ') + .trim(), + ).toContain('passed Job #4757 created 3 weeks ago by Root'); + })); }); }); describe('stuck block', () => { describe('without active runners availabl', () => { - it('renders stuck block when there are no runners', done => { + it('renders stuck block when there are no runners', () => setupAndMount({ jobData: { status: { @@ -154,20 +151,14 @@ describe('Job App ', () => { }, tags: [], }, - }) - .then(() => { - expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull(); - expect( - vm.$el.querySelector('.js-job-stuck .js-stuck-no-active-runner'), - ).not.toBeNull(); - }) - .then(done) - .catch(done.fail); - }); + }).then(() => { + expect(wrapper.find('.js-job-stuck').exists()).toBe(true); + expect(wrapper.find('.js-job-stuck .js-stuck-no-active-runner').exists()).toBe(true); + })); }); describe('when available runners can not run specified tag', () => { - it('renders tags in stuck block when there are no runners', done => { + it('renders tags in stuck block when there are no runners', () => setupAndMount({ jobData: { status: { @@ -183,18 +174,14 @@ describe('Job App ', () => { online: false, }, }, - }) - .then(() => { - expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]); - expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull(); - }) - .then(done) - .catch(done.fail); - }); + }).then(() => { + expect(wrapper.find('.js-job-stuck').text()).toContain(job.tags[0]); + expect(wrapper.find('.js-job-stuck .js-stuck-with-tags').exists()).toBe(true); + })); }); describe('when runners are offline and build has tags', () => { - it('renders message about job being stuck because of no runners with the specified tags', done => { + it('renders message about job being stuck because of no runners with the specified tags', () => setupAndMount({ jobData: { status: { @@ -210,32 +197,24 @@ describe('Job App ', () => { online: true, }, }, - }) - .then(() => { - expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]); - expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull(); - }) - .then(done) - .catch(done.fail); - }); + }).then(() => { + expect(wrapper.find('.js-job-stuck').text()).toContain(job.tags[0]); + expect(wrapper.find('.js-job-stuck .js-stuck-with-tags').exists()).toBe(true); + })); }); - it('does not renders stuck block when there are no runners', done => { + it('does not renders stuck block when there are no runners', () => setupAndMount({ jobData: { runners: { available: true }, }, - }) - .then(() => { - expect(vm.$el.querySelector('.js-job-stuck')).toBeNull(); - }) - .then(done) - .catch(done.fail); - }); + }).then(() => { + expect(wrapper.find('.js-job-stuck').exists()).toBe(false); + })); }); describe('unmet prerequisites block', () => { - it('renders unmet prerequisites block when there is an unmet prerequisites failure', done => { + it('renders unmet prerequisites block when there is an unmet prerequisites failure', () => setupAndMount({ jobData: { status: { @@ -258,17 +237,13 @@ describe('Job App ', () => { }, tags: [], }, - }) - .then(() => { - expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull(); - }) - .then(done) - .catch(done.fail); - }); + }).then(() => { + expect(wrapper.find('.js-job-failed').exists()).toBe(true); + })); }); describe('environments block', () => { - it('renders environment block when job has environment', done => { + it('renders environment block when job has environment', () => setupAndMount({ jobData: { deployment_status: { @@ -278,26 +253,18 @@ describe('Job App ', () => { }, }, }, - }) - .then(() => { - expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull(); - }) - .then(done) - .catch(done.fail); - }); - - it('does not render environment block when job has environment', done => { - setupAndMount() - .then(() => { - expect(vm.$el.querySelector('.js-job-environment')).toBeNull(); - }) - .then(done) - .catch(done.fail); - }); + }).then(() => { + expect(wrapper.find('.js-job-environment').exists()).toBe(true); + })); + + it('does not render environment block when job has environment', () => + setupAndMount().then(() => { + expect(wrapper.find('.js-job-environment').exists()).toBe(false); + })); }); describe('erased block', () => { - it('renders erased block when `erased` is true', done => { + it('renders erased block when `erased` is true', () => setupAndMount({ jobData: { erased_by: { @@ -306,30 +273,22 @@ describe('Job App ', () => { }, erased_at: '2016-11-07T11:11:16.525Z', }, - }) - .then(() => { - expect(vm.$el.querySelector('.js-job-erased-block')).not.toBeNull(); - }) - .then(done) - .catch(done.fail); - }); + }).then(() => { + expect(wrapper.find('.js-job-erased-block').exists()).toBe(true); + })); - it('does not render erased block when `erased` is false', done => { + it('does not render erased block when `erased` is false', () => setupAndMount({ jobData: { erased_at: null, }, - }) - .then(() => { - expect(vm.$el.querySelector('.js-job-erased-block')).toBeNull(); - }) - .then(done) - .catch(done.fail); - }); + }).then(() => { + expect(wrapper.find('.js-job-erased-block').exists()).toBe(false); + })); }); describe('empty states block', () => { - it('renders empty state when job does not have trace and is not running', done => { + it('renders empty state when job does not have trace and is not running', () => setupAndMount({ jobData: { has_trace: false, @@ -352,15 +311,11 @@ describe('Job App ', () => { }, }, }, - }) - .then(() => { - expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull(); - }) - .then(done) - .catch(done.fail); - }); + }).then(() => { + expect(wrapper.find('.js-job-empty-state').exists()).toBe(true); + })); - it('does not render empty state when job does not have trace but it is running', done => { + it('does not render empty state when job does not have trace but it is running', () => setupAndMount({ jobData: { has_trace: false, @@ -372,38 +327,29 @@ describe('Job App ', () => { details_path: 'path', }, }, - }) - .then(() => { - expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); - }) - .then(done) - .catch(done.fail); - }); + }).then(() => { + expect(wrapper.find('.js-job-empty-state').exists()).toBe(false); + })); - it('does not render empty state when job has trace but it is not running', done => { - setupAndMount({ jobData: { has_trace: true } }) - .then(() => { - expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); - }) - .then(done) - .catch(done.fail); - }); + it('does not render empty state when job has trace but it is not running', () => + setupAndMount({ jobData: { has_trace: true } }).then(() => { + expect(wrapper.find('.js-job-empty-state').exists()).toBe(false); + })); - it('displays remaining time for a delayed job', done => { + it('displays remaining time for a delayed job', () => { const oneHourInMilliseconds = 3600000; - spyOn(Date, 'now').and.callFake( - () => new Date(delayedJobFixture.scheduled_at).getTime() - oneHourInMilliseconds, - ); - setupAndMount({ jobData: delayedJobFixture }) - .then(() => { - expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull(); + jest + .spyOn(Date, 'now') + .mockImplementation( + () => new Date(delayedJobFixture.scheduled_at).getTime() - oneHourInMilliseconds, + ); + return setupAndMount({ jobData: delayedJobFixture }).then(() => { + expect(wrapper.find('.js-job-empty-state').exists()).toBe(true); - const title = vm.$el.querySelector('.js-job-empty-state-title'); + const title = wrapper.find('.js-job-empty-state-title').text(); - expect(title).toContainText('01:00:00'); - }) - .then(done) - .catch(done.fail); + expect(title).toEqual('This is a delayed job to run in 01:00:00'); + }); }); }); @@ -422,8 +368,11 @@ describe('Job App ', () => { }, }) .then(() => { - vm.$el.querySelectorAll('.blocks-container > *').forEach(block => { - expect(block.textContent.trim()).not.toBe(''); + const blocks = wrapper.findAll('.blocks-container > *').wrappers; + expect(blocks.length).toBeGreaterThan(0); + + blocks.forEach(block => { + expect(block.text().trim()).not.toBe(''); }); }) .then(done) @@ -433,32 +382,24 @@ describe('Job App ', () => { }); describe('archived job', () => { - beforeEach(done => { - setupAndMount({ jobData: { archived: true } }) - .then(done) - .catch(done.fail); - }); + beforeEach(() => setupAndMount({ jobData: { archived: true } })); it('renders warning about job being archived', () => { - expect(vm.$el.querySelector('.js-archived-job ')).not.toBeNull(); + expect(wrapper.find('.js-archived-job ').exists()).toBe(true); }); }); describe('non-archived job', () => { - beforeEach(done => { - setupAndMount() - .then(done) - .catch(done.fail); - }); + beforeEach(() => setupAndMount()); it('does not warning about job being archived', () => { - expect(vm.$el.querySelector('.js-archived-job ')).toBeNull(); + expect(wrapper.find('.js-archived-job ').exists()).toBe(false); }); }); describe('trace output', () => { describe('with append flag', () => { - it('appends the log content to the existing one', done => { + it('appends the log content to the existing one', () => setupAndMount({ traceData: { html: '<span>More<span>', @@ -469,20 +410,22 @@ describe('Job App ', () => { }, }) .then(() => { - vm.$store.state.trace = 'Update'; + store.state.trace = 'Update'; - return vm.$nextTick(); + return wrapper.vm.$nextTick(); }) .then(() => { - expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Update'); - }) - .then(done) - .catch(done.fail); - }); + expect( + wrapper + .find('.js-build-trace') + .text() + .trim(), + ).toEqual('Update'); + })); }); describe('without append flag', () => { - it('replaces the trace', done => { + it('replaces the trace', () => setupAndMount({ traceData: { html: '<span>Different<span>', @@ -490,24 +433,19 @@ describe('Job App ', () => { append: false, complete: true, }, - }) - .then(() => { - expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain( - 'Update', - ); - - expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain( - 'Different', - ); - }) - .then(done) - .catch(done.fail); - }); + }).then(() => { + expect( + wrapper + .find('.js-build-trace') + .text() + .trim(), + ).toEqual('Different'); + })); }); describe('truncated information', () => { describe('when size is less than total', () => { - it('shows information about truncated log', done => { + it('shows information about truncated log', () => { mock.onGet(`${props.pagePath}/trace.json`).reply(200, { html: '<span>Update</span>', status: 'success', @@ -517,7 +455,7 @@ describe('Job App ', () => { complete: true, }); - setupAndMount({ + return setupAndMount({ traceData: { html: '<span>Update</span>', status: 'success', @@ -526,19 +464,19 @@ describe('Job App ', () => { total: 100, complete: true, }, - }) - .then(() => { - expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).toContain( - '50 bytes', - ); - }) - .then(done) - .catch(done.fail); + }).then(() => { + expect( + wrapper + .find('.js-truncated-info') + .text() + .trim(), + ).toContain('Showing last 50 bytes'); + }); }); }); describe('when size is equal than total', () => { - it('does not show the truncated information', done => { + it('does not show the truncated information', () => setupAndMount({ traceData: { html: '<span>Update</span>', @@ -548,20 +486,19 @@ describe('Job App ', () => { total: 100, complete: true, }, - }) - .then(() => { - expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).not.toContain( - '50 bytes', - ); - }) - .then(done) - .catch(done.fail); - }); + }).then(() => { + expect( + wrapper + .find('.js-truncated-info') + .text() + .trim(), + ).toEqual(''); + })); }); }); describe('trace controls', () => { - beforeEach(done => { + beforeEach(() => setupAndMount({ traceData: { html: '<span>Update</span>', @@ -571,22 +508,20 @@ describe('Job App ', () => { total: 100, complete: true, }, - }) - .then(done) - .catch(done.fail); - }); + }), + ); it('should render scroll buttons', () => { - expect(vm.$el.querySelector('.js-scroll-top')).not.toBeNull(); - expect(vm.$el.querySelector('.js-scroll-bottom')).not.toBeNull(); + expect(wrapper.find('.js-scroll-top').exists()).toBe(true); + expect(wrapper.find('.js-scroll-bottom').exists()).toBe(true); }); it('should render link to raw ouput', () => { - expect(vm.$el.querySelector('.js-raw-link-controller')).not.toBeNull(); + expect(wrapper.find('.js-raw-link-controller').exists()).toBe(true); }); it('should render link to erase job', () => { - expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull(); + expect(wrapper.find('.js-erase-link').exists()).toBe(true); }); }); }); diff --git a/spec/frontend/jobs/mock_data.js b/spec/frontend/jobs/mock_data.js new file mode 100644 index 00000000000..3d40e94d219 --- /dev/null +++ b/spec/frontend/jobs/mock_data.js @@ -0,0 +1,1191 @@ +import { TEST_HOST } from 'spec/test_constants'; + +const threeWeeksAgo = new Date(); +threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21); + +export const stages = [ + { + name: 'build', + title: 'build: running', + groups: [ + { + name: 'build:linux', + size: 1, + status: { + icon: 'status_pending', + text: 'pending', + label: 'pending', + group: 'pending', + tooltip: 'pending', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/1180', + illustration: { + image: 'illustrations/pending_job_empty.svg', + size: 'svg-430', + title: 'This job has not started yet', + content: 'This job is in pending state and is waiting to be picked by a runner', + }, + favicon: + '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', + method: 'post', + }, + }, + jobs: [ + { + id: 1180, + name: 'build:linux', + started: false, + build_path: '/gitlab-org/gitlab-shell/-/jobs/1180', + cancel_path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', + playable: false, + created_at: '2018-09-28T11:09:57.229Z', + updated_at: '2018-09-28T11:09:57.503Z', + status: { + icon: 'status_pending', + text: 'pending', + label: 'pending', + group: 'pending', + tooltip: 'pending', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/1180', + illustration: { + image: 'illustrations/pending_job_empty.svg', + size: 'svg-430', + title: 'This job has not started yet', + content: 'This job is in pending state and is waiting to be picked by a runner', + }, + favicon: + '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'build:osx', + size: 1, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/444', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', + method: 'post', + }, + }, + jobs: [ + { + id: 444, + name: 'build:osx', + started: '2018-05-18T05:32:20.655Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/444', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', + playable: false, + created_at: '2018-05-18T15:32:54.364Z', + updated_at: '2018-05-18T15:32:54.364Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/444', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', + method: 'post', + }, + }, + }, + ], + }, + ], + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + tooltip: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/pipelines/27#build', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png', + }, + path: '/gitlab-org/gitlab-shell/pipelines/27#build', + dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build', + }, + { + name: 'test', + title: 'test: passed with warnings', + groups: [ + { + name: 'jenkins', + size: 1, + status: { + icon: 'status_success', + text: 'passed', + label: null, + group: 'success', + tooltip: null, + has_details: false, + details_path: null, + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + jobs: [ + { + id: 459, + name: 'jenkins', + started: '2018-05-18T09:32:20.658Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/459', + playable: false, + created_at: '2018-05-18T15:32:55.330Z', + updated_at: '2018-05-18T15:32:55.330Z', + status: { + icon: 'status_success', + text: 'passed', + label: null, + group: 'success', + tooltip: null, + has_details: false, + details_path: null, + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + }, + ], + }, + { + name: 'rspec:linux', + size: 3, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: false, + details_path: null, + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + jobs: [ + { + id: 445, + name: 'rspec:linux 0 3', + started: '2018-05-18T07:32:20.655Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/445', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/445/retry', + playable: false, + created_at: '2018-05-18T15:32:54.425Z', + updated_at: '2018-05-18T15:32:54.425Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/445', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/445/retry', + method: 'post', + }, + }, + }, + { + id: 446, + name: 'rspec:linux 1 3', + started: '2018-05-18T07:32:20.655Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/446', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/446/retry', + playable: false, + created_at: '2018-05-18T15:32:54.506Z', + updated_at: '2018-05-18T15:32:54.506Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/446', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/446/retry', + method: 'post', + }, + }, + }, + { + id: 447, + name: 'rspec:linux 2 3', + started: '2018-05-18T07:32:20.656Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/447', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/447/retry', + playable: false, + created_at: '2018-05-18T15:32:54.572Z', + updated_at: '2018-05-18T15:32:54.572Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/447', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/447/retry', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'rspec:osx', + size: 1, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/452', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/452/retry', + method: 'post', + }, + }, + jobs: [ + { + id: 452, + name: 'rspec:osx', + started: '2018-05-18T07:32:20.657Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/452', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/452/retry', + playable: false, + created_at: '2018-05-18T15:32:54.920Z', + updated_at: '2018-05-18T15:32:54.920Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/452', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/452/retry', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'rspec:windows', + size: 3, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: false, + details_path: null, + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + jobs: [ + { + id: 448, + name: 'rspec:windows 0 3', + started: '2018-05-18T07:32:20.656Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/448', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/448/retry', + playable: false, + created_at: '2018-05-18T15:32:54.639Z', + updated_at: '2018-05-18T15:32:54.639Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/448', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/448/retry', + method: 'post', + }, + }, + }, + { + id: 449, + name: 'rspec:windows 1 3', + started: '2018-05-18T07:32:20.656Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/449', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/449/retry', + playable: false, + created_at: '2018-05-18T15:32:54.703Z', + updated_at: '2018-05-18T15:32:54.703Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/449', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/449/retry', + method: 'post', + }, + }, + }, + { + id: 451, + name: 'rspec:windows 2 3', + started: '2018-05-18T07:32:20.657Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/451', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/451/retry', + playable: false, + created_at: '2018-05-18T15:32:54.853Z', + updated_at: '2018-05-18T15:32:54.853Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/451', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/451/retry', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'spinach:linux', + size: 1, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/453', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/453/retry', + method: 'post', + }, + }, + jobs: [ + { + id: 453, + name: 'spinach:linux', + started: '2018-05-18T07:32:20.657Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/453', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/453/retry', + playable: false, + created_at: '2018-05-18T15:32:54.993Z', + updated_at: '2018-05-18T15:32:54.993Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/453', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/453/retry', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'spinach:osx', + size: 1, + status: { + icon: 'status_warning', + text: 'failed', + label: 'failed (allowed to fail)', + group: 'failed-with-warnings', + tooltip: 'failed - (unknown failure) (allowed to fail)', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/454', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', + method: 'post', + }, + }, + jobs: [ + { + id: 454, + name: 'spinach:osx', + started: '2018-05-18T07:32:20.657Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/454', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', + playable: false, + created_at: '2018-05-18T15:32:55.053Z', + updated_at: '2018-05-18T15:32:55.053Z', + status: { + icon: 'status_warning', + text: 'failed', + label: 'failed (allowed to fail)', + group: 'failed-with-warnings', + tooltip: 'failed - (unknown failure) (allowed to fail)', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/454', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', + method: 'post', + }, + }, + callout_message: 'There is an unknown failure, please try again', + recoverable: true, + }, + ], + }, + ], + status: { + icon: 'status_warning', + text: 'passed', + label: 'passed with warnings', + group: 'success-with-warnings', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/pipelines/27#test', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + path: '/gitlab-org/gitlab-shell/pipelines/27#test', + dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=test', + }, + { + name: 'deploy', + title: 'deploy: running', + groups: [ + { + name: 'production', + size: 1, + status: { + icon: 'status_created', + text: 'created', + label: 'created', + group: 'created', + tooltip: 'created', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/457', + illustration: { + image: 'illustrations/job_not_triggered.svg', + size: 'svg-306', + title: 'This job has not been triggered yet', + content: + 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', + }, + favicon: + '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel', + method: 'post', + }, + }, + jobs: [ + { + id: 457, + name: 'production', + started: false, + build_path: '/gitlab-org/gitlab-shell/-/jobs/457', + cancel_path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel', + playable: false, + created_at: '2018-05-18T15:32:55.259Z', + updated_at: '2018-09-28T11:09:57.454Z', + status: { + icon: 'status_created', + text: 'created', + label: 'created', + group: 'created', + tooltip: 'created', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/457', + illustration: { + image: 'illustrations/job_not_triggered.svg', + size: 'svg-306', + title: 'This job has not been triggered yet', + content: + 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', + }, + favicon: + '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'staging', + size: 1, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/455', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/455/retry', + method: 'post', + }, + }, + jobs: [ + { + id: 455, + name: 'staging', + started: '2018-05-18T09:32:20.658Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/455', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/455/retry', + playable: false, + created_at: '2018-05-18T15:32:55.119Z', + updated_at: '2018-05-18T15:32:55.119Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/455', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/455/retry', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'stop staging', + size: 1, + status: { + icon: 'status_created', + text: 'created', + label: 'created', + group: 'created', + tooltip: 'created', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/456', + illustration: { + image: 'illustrations/job_not_triggered.svg', + size: 'svg-306', + title: 'This job has not been triggered yet', + content: + 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', + }, + favicon: + '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel', + method: 'post', + }, + }, + jobs: [ + { + id: 456, + name: 'stop staging', + started: false, + build_path: '/gitlab-org/gitlab-shell/-/jobs/456', + cancel_path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel', + playable: false, + created_at: '2018-05-18T15:32:55.205Z', + updated_at: '2018-09-28T11:09:57.396Z', + status: { + icon: 'status_created', + text: 'created', + label: 'created', + group: 'created', + tooltip: 'created', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/456', + illustration: { + image: 'illustrations/job_not_triggered.svg', + size: 'svg-306', + title: 'This job has not been triggered yet', + content: + 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', + }, + favicon: + '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel', + method: 'post', + }, + }, + }, + ], + }, + ], + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + tooltip: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/pipelines/27#deploy', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png', + }, + path: '/gitlab-org/gitlab-shell/pipelines/27#deploy', + dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=deploy', + }, + { + name: 'notify', + title: 'notify: manual action', + groups: [ + { + name: 'slack', + size: 1, + status: { + icon: 'status_manual', + text: 'manual', + label: 'manual play action', + group: 'manual', + tooltip: 'manual action', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/458', + illustration: { + image: 'illustrations/manual_action.svg', + size: 'svg-394', + title: 'This job requires a manual action', + content: + 'This job depends on a user to trigger its process. Often they are used to deploy code to production environments', + }, + favicon: + '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', + action: { + icon: 'play', + title: 'Play', + path: '/gitlab-org/gitlab-shell/-/jobs/458/play', + method: 'post', + }, + }, + jobs: [ + { + id: 458, + name: 'slack', + started: null, + build_path: '/gitlab-org/gitlab-shell/-/jobs/458', + play_path: '/gitlab-org/gitlab-shell/-/jobs/458/play', + playable: true, + created_at: '2018-05-18T15:32:55.303Z', + updated_at: '2018-05-18T15:34:08.535Z', + status: { + icon: 'status_manual', + text: 'manual', + label: 'manual play action', + group: 'manual', + tooltip: 'manual action', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/458', + illustration: { + image: 'illustrations/manual_action.svg', + size: 'svg-394', + title: 'This job requires a manual action', + content: + 'This job depends on a user to trigger its process. Often they are used to deploy code to production environments', + }, + favicon: + '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', + action: { + icon: 'play', + title: 'Play', + path: '/gitlab-org/gitlab-shell/-/jobs/458/play', + method: 'post', + }, + }, + }, + ], + }, + ], + status: { + icon: 'status_manual', + text: 'manual', + label: 'manual action', + group: 'manual', + tooltip: 'manual action', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/pipelines/27#notify', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', + }, + path: '/gitlab-org/gitlab-shell/pipelines/27#notify', + dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=notify', + }, +]; + +export default { + id: 4757, + name: 'test', + build_path: '/root/ci-mock/-/jobs/4757', + retry_path: '/root/ci-mock/-/jobs/4757/retry', + cancel_path: '/root/ci-mock/-/jobs/4757/cancel', + new_issue_path: '/root/ci-mock/issues/new', + playable: false, + created_at: threeWeeksAgo.toISOString(), + updated_at: threeWeeksAgo.toISOString(), + finished_at: threeWeeksAgo.toISOString(), + queued: 9.54, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + has_details: true, + details_path: `${TEST_HOST}/root/ci-mock/-/jobs/4757`, + favicon: + '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/root/ci-mock/-/jobs/4757/retry', + method: 'post', + }, + }, + coverage: 20, + erased_at: threeWeeksAgo.toISOString(), + erased: false, + duration: 6.785563, + tags: ['tag'], + user: { + name: 'Root', + username: 'root', + id: 1, + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + erase_path: '/root/ci-mock/-/jobs/4757/erase', + artifacts: [null], + runner: { + id: 1, + description: 'local ci runner', + edit_path: '/root/ci-mock/runners/1/edit', + }, + pipeline: { + id: 140, + user: { + name: 'Root', + username: 'root', + id: 1, + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + active: false, + coverage: null, + source: 'unknown', + created_at: '2017-05-24T09:59:58.634Z', + updated_at: '2017-06-01T17:32:00.062Z', + path: '/root/ci-mock/pipelines/140', + flags: { + latest: true, + stuck: false, + yaml_errors: false, + retryable: false, + cancelable: false, + }, + details: { + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + has_details: true, + details_path: '/root/ci-mock/pipelines/140', + favicon: + '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', + }, + duration: 6, + finished_at: '2017-06-01T17:32:00.042Z', + stages: [ + { + dropdown_path: '/jashkenas/underscore/pipelines/16/stage.json?stage=build', + name: 'build', + path: '/jashkenas/underscore/pipelines/16#build', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + }, + title: 'build: passed', + }, + { + dropdown_path: '/jashkenas/underscore/pipelines/16/stage.json?stage=test', + name: 'test', + path: '/jashkenas/underscore/pipelines/16#test', + status: { + icon: 'status_warning', + text: 'passed', + label: 'passed with warnings', + group: 'success-with-warnings', + }, + title: 'test: passed with warnings', + }, + ], + }, + ref: { + name: 'abc', + path: '/root/ci-mock/commits/abc', + tag: false, + branch: true, + }, + commit: { + id: 'c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', + short_id: 'c5864777', + title: 'Add new file', + created_at: '2017-05-24T10:59:52.000+01:00', + parent_ids: ['798e5f902592192afaba73f4668ae30e56eae492'], + message: 'Add new file', + author_name: 'Root', + author_email: 'admin@example.com', + authored_date: '2017-05-24T10:59:52.000+01:00', + committer_name: 'Root', + committer_email: 'admin@example.com', + committed_date: '2017-05-24T10:59:52.000+01:00', + author: { + name: 'Root', + username: 'root', + id: 1, + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + author_gravatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + commit_url: + 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', + commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', + }, + }, + metadata: { + timeout_human_readable: '1m 40s', + timeout_source: 'runner', + }, + merge_request: { + iid: 2, + path: '/root/ci-mock/merge_requests/2', + }, + raw_path: '/root/ci-mock/builds/4757/raw', + has_trace: true, +}; + +export const jobsInStage = { + name: 'build', + title: 'build: running', + latest_statuses: [ + { + id: 1180, + name: 'build:linux', + started: false, + build_path: '/gitlab-org/gitlab-shell/-/jobs/1180', + cancel_path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', + playable: false, + created_at: '2018-09-28T11:09:57.229Z', + updated_at: '2018-09-28T11:09:57.503Z', + status: { + icon: 'status_pending', + text: 'pending', + label: 'pending', + group: 'pending', + tooltip: 'pending', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/1180', + illustration: { + image: 'illustrations/pending_job_empty.svg', + size: 'svg-430', + title: 'This job has not started yet', + content: 'This job is in pending state and is waiting to be picked by a runner', + }, + favicon: + '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', + method: 'post', + }, + }, + }, + { + id: 444, + name: 'build:osx', + started: '2018-05-18T05:32:20.655Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/444', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', + playable: false, + created_at: '2018-05-18T15:32:54.364Z', + updated_at: '2018-05-18T15:32:54.364Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/444', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', + method: 'post', + }, + }, + }, + ], + retried: [ + { + id: 443, + name: 'build:linux', + started: '2018-05-18T06:32:20.655Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/443', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/443/retry', + playable: false, + created_at: '2018-05-18T15:32:54.296Z', + updated_at: '2018-05-18T15:32:54.296Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed (retried)', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/443', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/443/retry', + method: 'post', + }, + }, + }, + ], + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + tooltip: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/pipelines/27#build', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png', + }, + path: '/gitlab-org/gitlab-shell/pipelines/27#build', + dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build', +}; diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js index 3d40e94d219..f0ba46c058a 100644 --- a/spec/javascripts/jobs/mock_data.js +++ b/spec/javascripts/jobs/mock_data.js @@ -1,1191 +1,2 @@ -import { TEST_HOST } from 'spec/test_constants'; - -const threeWeeksAgo = new Date(); -threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21); - -export const stages = [ - { - name: 'build', - title: 'build: running', - groups: [ - { - name: 'build:linux', - size: 1, - status: { - icon: 'status_pending', - text: 'pending', - label: 'pending', - group: 'pending', - tooltip: 'pending', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/1180', - illustration: { - image: 'illustrations/pending_job_empty.svg', - size: 'svg-430', - title: 'This job has not started yet', - content: 'This job is in pending state and is waiting to be picked by a runner', - }, - favicon: - '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', - action: { - icon: 'cancel', - title: 'Cancel', - path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', - method: 'post', - }, - }, - jobs: [ - { - id: 1180, - name: 'build:linux', - started: false, - build_path: '/gitlab-org/gitlab-shell/-/jobs/1180', - cancel_path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', - playable: false, - created_at: '2018-09-28T11:09:57.229Z', - updated_at: '2018-09-28T11:09:57.503Z', - status: { - icon: 'status_pending', - text: 'pending', - label: 'pending', - group: 'pending', - tooltip: 'pending', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/1180', - illustration: { - image: 'illustrations/pending_job_empty.svg', - size: 'svg-430', - title: 'This job has not started yet', - content: 'This job is in pending state and is waiting to be picked by a runner', - }, - favicon: - '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', - action: { - icon: 'cancel', - title: 'Cancel', - path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', - method: 'post', - }, - }, - }, - ], - }, - { - name: 'build:osx', - size: 1, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/444', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', - method: 'post', - }, - }, - jobs: [ - { - id: 444, - name: 'build:osx', - started: '2018-05-18T05:32:20.655Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/444', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', - playable: false, - created_at: '2018-05-18T15:32:54.364Z', - updated_at: '2018-05-18T15:32:54.364Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/444', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', - method: 'post', - }, - }, - }, - ], - }, - ], - status: { - icon: 'status_running', - text: 'running', - label: 'running', - group: 'running', - tooltip: 'running', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/pipelines/27#build', - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png', - }, - path: '/gitlab-org/gitlab-shell/pipelines/27#build', - dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build', - }, - { - name: 'test', - title: 'test: passed with warnings', - groups: [ - { - name: 'jenkins', - size: 1, - status: { - icon: 'status_success', - text: 'passed', - label: null, - group: 'success', - tooltip: null, - has_details: false, - details_path: null, - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - }, - jobs: [ - { - id: 459, - name: 'jenkins', - started: '2018-05-18T09:32:20.658Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/459', - playable: false, - created_at: '2018-05-18T15:32:55.330Z', - updated_at: '2018-05-18T15:32:55.330Z', - status: { - icon: 'status_success', - text: 'passed', - label: null, - group: 'success', - tooltip: null, - has_details: false, - details_path: null, - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - }, - }, - ], - }, - { - name: 'rspec:linux', - size: 3, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: false, - details_path: null, - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - }, - jobs: [ - { - id: 445, - name: 'rspec:linux 0 3', - started: '2018-05-18T07:32:20.655Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/445', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/445/retry', - playable: false, - created_at: '2018-05-18T15:32:54.425Z', - updated_at: '2018-05-18T15:32:54.425Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/445', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/445/retry', - method: 'post', - }, - }, - }, - { - id: 446, - name: 'rspec:linux 1 3', - started: '2018-05-18T07:32:20.655Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/446', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/446/retry', - playable: false, - created_at: '2018-05-18T15:32:54.506Z', - updated_at: '2018-05-18T15:32:54.506Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/446', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/446/retry', - method: 'post', - }, - }, - }, - { - id: 447, - name: 'rspec:linux 2 3', - started: '2018-05-18T07:32:20.656Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/447', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/447/retry', - playable: false, - created_at: '2018-05-18T15:32:54.572Z', - updated_at: '2018-05-18T15:32:54.572Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/447', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/447/retry', - method: 'post', - }, - }, - }, - ], - }, - { - name: 'rspec:osx', - size: 1, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/452', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/452/retry', - method: 'post', - }, - }, - jobs: [ - { - id: 452, - name: 'rspec:osx', - started: '2018-05-18T07:32:20.657Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/452', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/452/retry', - playable: false, - created_at: '2018-05-18T15:32:54.920Z', - updated_at: '2018-05-18T15:32:54.920Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/452', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/452/retry', - method: 'post', - }, - }, - }, - ], - }, - { - name: 'rspec:windows', - size: 3, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: false, - details_path: null, - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - }, - jobs: [ - { - id: 448, - name: 'rspec:windows 0 3', - started: '2018-05-18T07:32:20.656Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/448', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/448/retry', - playable: false, - created_at: '2018-05-18T15:32:54.639Z', - updated_at: '2018-05-18T15:32:54.639Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/448', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/448/retry', - method: 'post', - }, - }, - }, - { - id: 449, - name: 'rspec:windows 1 3', - started: '2018-05-18T07:32:20.656Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/449', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/449/retry', - playable: false, - created_at: '2018-05-18T15:32:54.703Z', - updated_at: '2018-05-18T15:32:54.703Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/449', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/449/retry', - method: 'post', - }, - }, - }, - { - id: 451, - name: 'rspec:windows 2 3', - started: '2018-05-18T07:32:20.657Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/451', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/451/retry', - playable: false, - created_at: '2018-05-18T15:32:54.853Z', - updated_at: '2018-05-18T15:32:54.853Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/451', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/451/retry', - method: 'post', - }, - }, - }, - ], - }, - { - name: 'spinach:linux', - size: 1, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/453', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/453/retry', - method: 'post', - }, - }, - jobs: [ - { - id: 453, - name: 'spinach:linux', - started: '2018-05-18T07:32:20.657Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/453', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/453/retry', - playable: false, - created_at: '2018-05-18T15:32:54.993Z', - updated_at: '2018-05-18T15:32:54.993Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/453', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/453/retry', - method: 'post', - }, - }, - }, - ], - }, - { - name: 'spinach:osx', - size: 1, - status: { - icon: 'status_warning', - text: 'failed', - label: 'failed (allowed to fail)', - group: 'failed-with-warnings', - tooltip: 'failed - (unknown failure) (allowed to fail)', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/454', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', - method: 'post', - }, - }, - jobs: [ - { - id: 454, - name: 'spinach:osx', - started: '2018-05-18T07:32:20.657Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/454', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', - playable: false, - created_at: '2018-05-18T15:32:55.053Z', - updated_at: '2018-05-18T15:32:55.053Z', - status: { - icon: 'status_warning', - text: 'failed', - label: 'failed (allowed to fail)', - group: 'failed-with-warnings', - tooltip: 'failed - (unknown failure) (allowed to fail)', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/454', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', - method: 'post', - }, - }, - callout_message: 'There is an unknown failure, please try again', - recoverable: true, - }, - ], - }, - ], - status: { - icon: 'status_warning', - text: 'passed', - label: 'passed with warnings', - group: 'success-with-warnings', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/pipelines/27#test', - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - }, - path: '/gitlab-org/gitlab-shell/pipelines/27#test', - dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=test', - }, - { - name: 'deploy', - title: 'deploy: running', - groups: [ - { - name: 'production', - size: 1, - status: { - icon: 'status_created', - text: 'created', - label: 'created', - group: 'created', - tooltip: 'created', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/457', - illustration: { - image: 'illustrations/job_not_triggered.svg', - size: 'svg-306', - title: 'This job has not been triggered yet', - content: - 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', - }, - favicon: - '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', - action: { - icon: 'cancel', - title: 'Cancel', - path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel', - method: 'post', - }, - }, - jobs: [ - { - id: 457, - name: 'production', - started: false, - build_path: '/gitlab-org/gitlab-shell/-/jobs/457', - cancel_path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel', - playable: false, - created_at: '2018-05-18T15:32:55.259Z', - updated_at: '2018-09-28T11:09:57.454Z', - status: { - icon: 'status_created', - text: 'created', - label: 'created', - group: 'created', - tooltip: 'created', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/457', - illustration: { - image: 'illustrations/job_not_triggered.svg', - size: 'svg-306', - title: 'This job has not been triggered yet', - content: - 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', - }, - favicon: - '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', - action: { - icon: 'cancel', - title: 'Cancel', - path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel', - method: 'post', - }, - }, - }, - ], - }, - { - name: 'staging', - size: 1, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/455', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/455/retry', - method: 'post', - }, - }, - jobs: [ - { - id: 455, - name: 'staging', - started: '2018-05-18T09:32:20.658Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/455', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/455/retry', - playable: false, - created_at: '2018-05-18T15:32:55.119Z', - updated_at: '2018-05-18T15:32:55.119Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/455', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/455/retry', - method: 'post', - }, - }, - }, - ], - }, - { - name: 'stop staging', - size: 1, - status: { - icon: 'status_created', - text: 'created', - label: 'created', - group: 'created', - tooltip: 'created', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/456', - illustration: { - image: 'illustrations/job_not_triggered.svg', - size: 'svg-306', - title: 'This job has not been triggered yet', - content: - 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', - }, - favicon: - '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', - action: { - icon: 'cancel', - title: 'Cancel', - path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel', - method: 'post', - }, - }, - jobs: [ - { - id: 456, - name: 'stop staging', - started: false, - build_path: '/gitlab-org/gitlab-shell/-/jobs/456', - cancel_path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel', - playable: false, - created_at: '2018-05-18T15:32:55.205Z', - updated_at: '2018-09-28T11:09:57.396Z', - status: { - icon: 'status_created', - text: 'created', - label: 'created', - group: 'created', - tooltip: 'created', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/456', - illustration: { - image: 'illustrations/job_not_triggered.svg', - size: 'svg-306', - title: 'This job has not been triggered yet', - content: - 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', - }, - favicon: - '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', - action: { - icon: 'cancel', - title: 'Cancel', - path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel', - method: 'post', - }, - }, - }, - ], - }, - ], - status: { - icon: 'status_running', - text: 'running', - label: 'running', - group: 'running', - tooltip: 'running', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/pipelines/27#deploy', - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png', - }, - path: '/gitlab-org/gitlab-shell/pipelines/27#deploy', - dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=deploy', - }, - { - name: 'notify', - title: 'notify: manual action', - groups: [ - { - name: 'slack', - size: 1, - status: { - icon: 'status_manual', - text: 'manual', - label: 'manual play action', - group: 'manual', - tooltip: 'manual action', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/458', - illustration: { - image: 'illustrations/manual_action.svg', - size: 'svg-394', - title: 'This job requires a manual action', - content: - 'This job depends on a user to trigger its process. Often they are used to deploy code to production environments', - }, - favicon: - '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', - action: { - icon: 'play', - title: 'Play', - path: '/gitlab-org/gitlab-shell/-/jobs/458/play', - method: 'post', - }, - }, - jobs: [ - { - id: 458, - name: 'slack', - started: null, - build_path: '/gitlab-org/gitlab-shell/-/jobs/458', - play_path: '/gitlab-org/gitlab-shell/-/jobs/458/play', - playable: true, - created_at: '2018-05-18T15:32:55.303Z', - updated_at: '2018-05-18T15:34:08.535Z', - status: { - icon: 'status_manual', - text: 'manual', - label: 'manual play action', - group: 'manual', - tooltip: 'manual action', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/458', - illustration: { - image: 'illustrations/manual_action.svg', - size: 'svg-394', - title: 'This job requires a manual action', - content: - 'This job depends on a user to trigger its process. Often they are used to deploy code to production environments', - }, - favicon: - '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', - action: { - icon: 'play', - title: 'Play', - path: '/gitlab-org/gitlab-shell/-/jobs/458/play', - method: 'post', - }, - }, - }, - ], - }, - ], - status: { - icon: 'status_manual', - text: 'manual', - label: 'manual action', - group: 'manual', - tooltip: 'manual action', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/pipelines/27#notify', - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', - }, - path: '/gitlab-org/gitlab-shell/pipelines/27#notify', - dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=notify', - }, -]; - -export default { - id: 4757, - name: 'test', - build_path: '/root/ci-mock/-/jobs/4757', - retry_path: '/root/ci-mock/-/jobs/4757/retry', - cancel_path: '/root/ci-mock/-/jobs/4757/cancel', - new_issue_path: '/root/ci-mock/issues/new', - playable: false, - created_at: threeWeeksAgo.toISOString(), - updated_at: threeWeeksAgo.toISOString(), - finished_at: threeWeeksAgo.toISOString(), - queued: 9.54, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - has_details: true, - details_path: `${TEST_HOST}/root/ci-mock/-/jobs/4757`, - favicon: - '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/root/ci-mock/-/jobs/4757/retry', - method: 'post', - }, - }, - coverage: 20, - erased_at: threeWeeksAgo.toISOString(), - erased: false, - duration: 6.785563, - tags: ['tag'], - user: { - name: 'Root', - username: 'root', - id: 1, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', - web_url: 'http://localhost:3000/root', - }, - erase_path: '/root/ci-mock/-/jobs/4757/erase', - artifacts: [null], - runner: { - id: 1, - description: 'local ci runner', - edit_path: '/root/ci-mock/runners/1/edit', - }, - pipeline: { - id: 140, - user: { - name: 'Root', - username: 'root', - id: 1, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', - web_url: 'http://localhost:3000/root', - }, - active: false, - coverage: null, - source: 'unknown', - created_at: '2017-05-24T09:59:58.634Z', - updated_at: '2017-06-01T17:32:00.062Z', - path: '/root/ci-mock/pipelines/140', - flags: { - latest: true, - stuck: false, - yaml_errors: false, - retryable: false, - cancelable: false, - }, - details: { - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - has_details: true, - details_path: '/root/ci-mock/pipelines/140', - favicon: - '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', - }, - duration: 6, - finished_at: '2017-06-01T17:32:00.042Z', - stages: [ - { - dropdown_path: '/jashkenas/underscore/pipelines/16/stage.json?stage=build', - name: 'build', - path: '/jashkenas/underscore/pipelines/16#build', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - }, - title: 'build: passed', - }, - { - dropdown_path: '/jashkenas/underscore/pipelines/16/stage.json?stage=test', - name: 'test', - path: '/jashkenas/underscore/pipelines/16#test', - status: { - icon: 'status_warning', - text: 'passed', - label: 'passed with warnings', - group: 'success-with-warnings', - }, - title: 'test: passed with warnings', - }, - ], - }, - ref: { - name: 'abc', - path: '/root/ci-mock/commits/abc', - tag: false, - branch: true, - }, - commit: { - id: 'c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', - short_id: 'c5864777', - title: 'Add new file', - created_at: '2017-05-24T10:59:52.000+01:00', - parent_ids: ['798e5f902592192afaba73f4668ae30e56eae492'], - message: 'Add new file', - author_name: 'Root', - author_email: 'admin@example.com', - authored_date: '2017-05-24T10:59:52.000+01:00', - committer_name: 'Root', - committer_email: 'admin@example.com', - committed_date: '2017-05-24T10:59:52.000+01:00', - author: { - name: 'Root', - username: 'root', - id: 1, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', - web_url: 'http://localhost:3000/root', - }, - author_gravatar_url: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', - commit_url: - 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', - commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', - }, - }, - metadata: { - timeout_human_readable: '1m 40s', - timeout_source: 'runner', - }, - merge_request: { - iid: 2, - path: '/root/ci-mock/merge_requests/2', - }, - raw_path: '/root/ci-mock/builds/4757/raw', - has_trace: true, -}; - -export const jobsInStage = { - name: 'build', - title: 'build: running', - latest_statuses: [ - { - id: 1180, - name: 'build:linux', - started: false, - build_path: '/gitlab-org/gitlab-shell/-/jobs/1180', - cancel_path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', - playable: false, - created_at: '2018-09-28T11:09:57.229Z', - updated_at: '2018-09-28T11:09:57.503Z', - status: { - icon: 'status_pending', - text: 'pending', - label: 'pending', - group: 'pending', - tooltip: 'pending', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/1180', - illustration: { - image: 'illustrations/pending_job_empty.svg', - size: 'svg-430', - title: 'This job has not started yet', - content: 'This job is in pending state and is waiting to be picked by a runner', - }, - favicon: - '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', - action: { - icon: 'cancel', - title: 'Cancel', - path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', - method: 'post', - }, - }, - }, - { - id: 444, - name: 'build:osx', - started: '2018-05-18T05:32:20.655Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/444', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', - playable: false, - created_at: '2018-05-18T15:32:54.364Z', - updated_at: '2018-05-18T15:32:54.364Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/444', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', - method: 'post', - }, - }, - }, - ], - retried: [ - { - id: 443, - name: 'build:linux', - started: '2018-05-18T06:32:20.655Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/443', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/443/retry', - playable: false, - created_at: '2018-05-18T15:32:54.296Z', - updated_at: '2018-05-18T15:32:54.296Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed (retried)', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/443', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/443/retry', - method: 'post', - }, - }, - }, - ], - status: { - icon: 'status_running', - text: 'running', - label: 'running', - group: 'running', - tooltip: 'running', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/pipelines/27#build', - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png', - }, - path: '/gitlab-org/gitlab-shell/pipelines/27#build', - dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build', -}; +export { default } from '../../frontend/jobs/mock_data'; +export * from '../../frontend/jobs/mock_data'; diff --git a/spec/lib/gitlab/auth/current_user_mode_spec.rb b/spec/lib/gitlab/auth/current_user_mode_spec.rb index 3b3db0f7315..7c2fdac6c25 100644 --- a/spec/lib/gitlab/auth/current_user_mode_spec.rb +++ b/spec/lib/gitlab/auth/current_user_mode_spec.rb @@ -2,10 +2,10 @@ require 'spec_helper' -describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do +describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store do include_context 'custom session' - let(:user) { build(:user) } + let(:user) { build_stubbed(:user) } subject { described_class.new(user) } @@ -13,54 +13,66 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session]) end - describe '#admin_mode?', :request_store do - context 'when the user is a regular user' do - it 'is false by default' do - expect(subject.admin_mode?).to be(false) - end + shared_examples 'admin mode cannot be enabled' do + it 'is false by default' do + expect(subject.admin_mode?).to be(false) + end - it 'cannot be enabled with a valid password' do - subject.enable_admin_mode!(password: user.password) + it 'cannot be enabled with a valid password' do + subject.enable_admin_mode!(password: user.password) - expect(subject.admin_mode?).to be(false) - end + expect(subject.admin_mode?).to be(false) + end - it 'cannot be enabled with an invalid password' do - subject.enable_admin_mode!(password: nil) + it 'cannot be enabled with an invalid password' do + subject.enable_admin_mode!(password: nil) - expect(subject.admin_mode?).to be(false) - end + expect(subject.admin_mode?).to be(false) + end - it 'cannot be enabled with empty params' do - subject.enable_admin_mode! + it 'cannot be enabled with empty params' do + subject.enable_admin_mode! - expect(subject.admin_mode?).to be(false) - end + expect(subject.admin_mode?).to be(false) + end - it 'disable has no effect' do - subject.enable_admin_mode! - subject.disable_admin_mode! + it 'disable has no effect' do + subject.enable_admin_mode! + subject.disable_admin_mode! + + expect(subject.admin_mode?).to be(false) + end + + context 'skipping password validation' do + it 'cannot be enabled with a valid password' do + subject.enable_admin_mode!(password: user.password, skip_password_validation: true) expect(subject.admin_mode?).to be(false) end - context 'skipping password validation' do - it 'cannot be enabled with a valid password' do - subject.enable_admin_mode!(password: user.password, skip_password_validation: true) + it 'cannot be enabled with an invalid password' do + subject.enable_admin_mode!(skip_password_validation: true) - expect(subject.admin_mode?).to be(false) - end + expect(subject.admin_mode?).to be(false) + end + end + end - it 'cannot be enabled with an invalid password' do - subject.enable_admin_mode!(skip_password_validation: true) + describe '#admin_mode?' do + context 'when the user is a regular user' do + it_behaves_like 'admin mode cannot be enabled' - expect(subject.admin_mode?).to be(false) + context 'bypassing session' do + it_behaves_like 'admin mode cannot be enabled' do + around do |example| + described_class.bypass_session!(user.id) { example.run } + end end end end context 'when the user is an admin' do - let(:user) { build(:user, :admin) } + let(:user) { build_stubbed(:user, :admin) } context 'when admin mode not requested' do it 'is false by default' do @@ -148,11 +160,36 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do end end end + + context 'bypassing session' do + it 'is active by default' do + described_class.bypass_session!(user.id) do + expect(subject.admin_mode?).to be(true) + end + end + + it 'enable has no effect' do + described_class.bypass_session!(user.id) do + subject.request_admin_mode! + subject.enable_admin_mode!(password: user.password) + + expect(subject.admin_mode?).to be(true) + end + end + + it 'disable has no effect' do + described_class.bypass_session!(user.id) do + subject.disable_admin_mode! + + expect(subject.admin_mode?).to be(true) + end + end + end end end describe '#enable_admin_mode!' do - let(:user) { build(:user, :admin) } + let(:user) { build_stubbed(:user, :admin) } it 'creates a timestamp in the session' do subject.request_admin_mode! @@ -163,7 +200,7 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do end describe '#enable_sessionless_admin_mode!' do - let(:user) { build(:user, :admin) } + let(:user) { build_stubbed(:user, :admin) } it 'enabled admin mode without password' do subject.enable_sessionless_admin_mode! @@ -173,7 +210,7 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do end describe '#disable_admin_mode!' do - let(:user) { build(:user, :admin) } + let(:user) { build_stubbed(:user, :admin) } it 'sets the session timestamp to nil' do subject.request_admin_mode! @@ -183,6 +220,73 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do end end + describe '.bypass_session!' do + context 'with a regular user' do + it 'admin mode is false' do + described_class.bypass_session!(user.id) do + expect(subject.admin_mode?).to be(false) + expect(described_class.bypass_session_admin_id).to be(user.id) + end + + expect(described_class.bypass_session_admin_id).to be_nil + end + end + + context 'with an admin user' do + let(:user) { build_stubbed(:user, :admin) } + + it 'admin mode is true' do + described_class.bypass_session!(user.id) do + expect(subject.admin_mode?).to be(true) + expect(described_class.bypass_session_admin_id).to be(user.id) + end + + expect(described_class.bypass_session_admin_id).to be_nil + end + end + end + + describe '.with_current_request_admin_mode' do + context 'with a regular user' do + it 'user is not available inside nor outside the yielded block' do + described_class.with_current_admin(user) do + expect(described_class.current_admin).to be_nil + end + + expect(described_class.bypass_session_admin_id).to be_nil + end + end + + context 'with an admin user' do + let(:user) { build_stubbed(:user, :admin) } + + context 'admin mode is disabled' do + it 'user is not available inside nor outside the yielded block' do + described_class.with_current_admin(user) do + expect(described_class.current_admin).to be_nil + end + + expect(described_class.bypass_session_admin_id).to be_nil + end + end + + context 'admin mode is enabled' do + before do + subject.request_admin_mode! + subject.enable_admin_mode!(password: user.password) + end + + it 'user is available only inside the yielded block' do + described_class.with_current_admin(user) do + expect(described_class.current_admin).to be(user) + end + + expect(described_class.current_admin).to be_nil + end + end + end + end + def expected_session_entry(value_matcher) { Gitlab::Auth::CurrentUserMode::SESSION_STORE_KEY => a_hash_including( diff --git a/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb new file mode 100644 index 00000000000..f6449bae8c3 --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SidekiqMiddleware::AdminMode::Client, :do_not_mock_admin_mode, :request_store do + include AdminModeHelper + + let(:worker) do + Class.new do + def perform; end + end + end + + let(:job) { {} } + let(:queue) { :test } + + it 'yields block' do + expect do |b| + subject.call(worker, job, queue, nil, &b) + end.to yield_control.once + end + + context 'user is a regular user' do + it 'no admin mode field in payload' do + subject.call(worker, job, queue, nil) { nil } + + expect(job).not_to include('admin_mode_user_id') + end + end + + context 'user is an administrator' do + let(:admin) { create(:admin) } + + context 'admin mode disabled' do + it 'no admin mode field in payload' do + subject.call(worker, job, queue, nil) { nil } + + expect(job).not_to include('admin_mode_user_id') + end + end + + context 'admin mode enabled' do + before do + enable_admin_mode!(admin) + end + + context 'when sidekiq required context not set' do + it 'no admin mode field in payload' do + subject.call(worker, job, queue, nil) { nil } + + expect(job).not_to include('admin_mode_user_id') + end + end + + context 'when user stored in current request' do + it 'has admin mode field in payload' do + Gitlab::Auth::CurrentUserMode.with_current_admin(admin) do + subject.call(worker, job, queue, nil) { nil } + + expect(job).to include('admin_mode_user_id' => admin.id) + end + end + end + + context 'when bypassing session' do + it 'has admin mode field in payload' do + Gitlab::Auth::CurrentUserMode.bypass_session!(admin.id) do + subject.call(worker, job, queue, nil) { nil } + + expect(job).to include('admin_mode_user_id' => admin.id) + end + end + end + end + end + + context 'admin mode feature disabled' do + before do + stub_feature_flags(user_mode_in_session: false) + end + + it 'yields block' do + expect do |b| + subject.call(worker, job, queue, nil, &b) + end.to yield_control.once + end + + it 'no admin mode field in payload' do + subject.call(worker, job, queue, nil) { nil } + + expect(job).not_to include('admin_mode_user_id') + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb new file mode 100644 index 00000000000..60475f0e403 --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SidekiqMiddleware::AdminMode::Server, :do_not_mock_admin_mode, :request_store do + include AdminModeHelper + + let(:worker) do + Class.new do + def perform; end + end + end + + let(:job) { {} } + let(:queue) { :test } + + it 'yields block' do + expect do |b| + subject.call(worker, job, queue, &b) + end.to yield_control.once + end + + context 'job has no admin mode field' do + it 'session is not bypassed' do + subject.call(worker, job, queue) do + expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be_nil + end + end + end + + context 'job has admin mode field' do + let(:admin) { create(:admin) } + + context 'nil admin mode id' do + let(:job) { { 'admin_mode_user_id' => nil } } + + it 'session is not bypassed' do + subject.call(worker, job, queue) do + expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be_nil + end + end + end + + context 'valid admin mode id' do + let(:job) { { 'admin_mode_user_id' => admin.id } } + + it 'session is bypassed' do + subject.call(worker, job, queue) do + expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be(admin.id) + end + end + end + end + + context 'admin mode feature disabled' do + before do + stub_feature_flags(user_mode_in_session: false) + end + + it 'yields block' do + expect do |b| + subject.call(worker, job, queue, &b) + end.to yield_control.once + end + + it 'session is not bypassed' do + subject.call(worker, job, queue) do + expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be_nil + end + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware_spec.rb b/spec/lib/gitlab/sidekiq_middleware_spec.rb index e8dcbbd2ee1..19242d25e27 100644 --- a/spec/lib/gitlab/sidekiq_middleware_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware_spec.rb @@ -45,7 +45,8 @@ describe Gitlab::SidekiqMiddleware do Gitlab::SidekiqMiddleware::ArgumentsLogger, Gitlab::SidekiqMiddleware::MemoryKiller, Gitlab::SidekiqMiddleware::RequestStoreMiddleware, - Gitlab::SidekiqMiddleware::WorkerContext::Server + Gitlab::SidekiqMiddleware::WorkerContext::Server, + Gitlab::SidekiqMiddleware::AdminMode::Server ] end let(:enabled_sidekiq_middlewares) { all_sidekiq_middlewares - disabled_sidekiq_middlewares } @@ -115,7 +116,8 @@ describe Gitlab::SidekiqMiddleware do Gitlab::SidekiqStatus::ClientMiddleware, Gitlab::SidekiqMiddleware::ClientMetrics, Gitlab::SidekiqMiddleware::WorkerContext::Client, - Labkit::Middleware::Sidekiq::Client + Labkit::Middleware::Sidekiq::Client, + Gitlab::SidekiqMiddleware::AdminMode::Client ] end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f2b95e00b5e..74e38e79616 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2985,9 +2985,9 @@ describe User, :do_not_mock_admin_mode do end end - describe '#can_read_all_resources?' do + describe '#can_read_all_resources?', :request_store do it 'returns false for regular user' do - user = build(:user) + user = build_stubbed(:user) expect(user.can_read_all_resources?).to be_falsy end @@ -2995,7 +2995,7 @@ describe User, :do_not_mock_admin_mode do context 'for admin user' do include_context 'custom session' - let(:user) { build(:user, :admin) } + let(:user) { build_stubbed(:user, :admin) } context 'when admin mode is disabled' do it 'returns false' do diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb index 81aee4cfcac..ae5af9e0f29 100644 --- a/spec/policies/base_policy_spec.rb +++ b/spec/policies/base_policy_spec.rb @@ -23,8 +23,8 @@ describe BasePolicy, :do_not_mock_admin_mode do end describe 'read cross project' do - let(:current_user) { create(:user) } - let(:user) { create(:user) } + let(:current_user) { build_stubbed(:user) } + let(:user) { build_stubbed(:user) } subject { described_class.new(current_user, [user]) } @@ -38,7 +38,7 @@ describe BasePolicy, :do_not_mock_admin_mode do it { is_expected.not_to be_allowed(:read_cross_project) } context 'for admins' do - let(:current_user) { build(:admin) } + let(:current_user) { build_stubbed(:admin) } subject { described_class.new(current_user, nil) } @@ -56,14 +56,14 @@ describe BasePolicy, :do_not_mock_admin_mode do end describe 'full private access' do - let(:current_user) { create(:user) } + let(:current_user) { build_stubbed(:user) } subject { described_class.new(current_user, nil) } it { is_expected.not_to be_allowed(:read_all_resources) } context 'for admins' do - let(:current_user) { build(:admin) } + let(:current_user) { build_stubbed(:admin) } it 'allowed when in admin mode' do enable_admin_mode!(current_user) diff --git a/spec/requests/api/error_tracking_spec.rb b/spec/requests/api/error_tracking_spec.rb index 48ddc7f5a75..059744898b8 100644 --- a/spec/requests/api/error_tracking_spec.rb +++ b/spec/requests/api/error_tracking_spec.rb @@ -3,11 +3,129 @@ require 'spec_helper' describe API::ErrorTracking do - describe "GET /projects/:id/error_tracking/settings" do - let(:user) { create(:user) } - let(:setting) { create(:project_error_tracking_setting) } - let(:project) { setting.project } + let(:user) { create(:user) } + let(:setting) { create(:project_error_tracking_setting) } + let(:project) { setting.project } + + shared_examples 'returns project settings' do + it 'returns correct project settings' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to eq( + 'active' => setting.reload.enabled, + 'project_name' => setting.project_name, + 'sentry_external_url' => setting.sentry_external_url, + 'api_url' => setting.api_url + ) + end + end + + shared_examples 'returns 404' do + it 'returns correct project settings' do + subject + + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']) + .to eq('404 Error Tracking Setting Not Found') + end + end + + describe "PATCH /projects/:id/error_tracking/settings" do + def make_patch_request(**params) + patch api("/projects/#{project.id}/error_tracking/settings", user), params: params + end + + context 'when authenticated as maintainer' do + before do + project.add_maintainer(user) + end + + context 'patch settings' do + subject do + make_patch_request(active: false) + end + + it_behaves_like 'returns project settings' + + it 'returns active is invalid if non boolean' do + make_patch_request(active: "randomstring") + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']) + .to eq('active is invalid') + end + + it 'returns 400 if active is empty' do + make_patch_request(active: '') + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']) + .to eq('active is empty') + end + end + + context 'without a project setting' do + let(:project) { create(:project) } + + before do + project.add_maintainer(user) + end + + context 'patch settings' do + subject do + make_patch_request(active: true) + end + + it_behaves_like 'returns 404' + end + end + end + + context 'when authenticated as reporter' do + before do + project.add_reporter(user) + end + + it 'returns 403 for update request' do + make_patch_request(active: true) + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when authenticated as developer' do + before do + project.add_developer(user) + end + + it 'returns 403 for update request' do + make_patch_request(active: true) + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when authenticated as non-member' do + it 'returns 404 for update request' do + make_patch_request(active: false) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when unauthenticated' do + let(:user) { nil } + + it 'returns 401 for update request' do + make_patch_request(active: true) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + end + + describe "GET /projects/:id/error_tracking/settings" do def make_request get api("/projects/#{project.id}/error_tracking/settings", user) end @@ -17,16 +135,12 @@ describe API::ErrorTracking do project.add_maintainer(user) end - it 'returns project settings' do - make_request + context 'get settings' do + subject do + make_request + end - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to eq( - 'active' => setting.enabled, - 'project_name' => setting.project_name, - 'sentry_external_url' => setting.sentry_external_url, - 'api_url' => setting.api_url - ) + it_behaves_like 'returns project settings' end end @@ -37,12 +151,12 @@ describe API::ErrorTracking do project.add_maintainer(user) end - it 'returns 404' do - make_request + context 'get settings' do + subject do + make_request + end - expect(response).to have_gitlab_http_status(:not_found) - expect(json_response['message']) - .to eq('404 Error Tracking Setting Not Found') + it_behaves_like 'returns 404' end end @@ -58,6 +172,18 @@ describe API::ErrorTracking do end end + context 'when authenticated as developer' do + before do + project.add_developer(user) + end + + it 'returns 403' do + make_request + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + context 'when authenticated as non-member' do it 'returns 404' do make_request diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb index a3538aa98b1..09e005398a9 100644 --- a/spec/requests/api/issues/issues_spec.rb +++ b/spec/requests/api/issues/issues_spec.rb @@ -778,6 +778,32 @@ describe API::Issues do expect(json_response["error"]).to include("mutually exclusive") end end + + context 'filtering by non_archived' do + let_it_be(:group1) { create(:group) } + let_it_be(:archived_project) { create(:project, :archived, namespace: group1) } + let_it_be(:active_project) { create(:project, namespace: group1) } + let_it_be(:issue1) { create(:issue, project: active_project) } + let_it_be(:issue2) { create(:issue, project: active_project) } + let_it_be(:issue3) { create(:issue, project: archived_project) } + + before do + archived_project.add_developer(user) + active_project.add_developer(user) + end + + it 'returns issues from non archived projects only by default' do + get api("/groups/#{group1.id}/issues", user), params: { scope: 'all' } + + expect_response_contain_exactly(issue2, issue1) + end + + it 'returns issues from archived and non archived projects when non_archived is false' do + get api("/groups/#{group1.id}/issues", user), params: { non_archived: false, scope: 'all' } + + expect_response_contain_exactly(issue1, issue2, issue3) + end + end end context "when returns issue merge_requests_count for different access levels" do @@ -862,4 +888,9 @@ describe API::Issues do include_examples 'time tracking endpoints', 'issue' end + + def expect_response_contain_exactly(*items) + expect(json_response.length).to eq(items.size) + expect(json_response.map { |element| element['id'] }).to contain_exactly(*items.map(&:id)) + end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 33ac175e945..adfe865da90 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -807,6 +807,38 @@ describe API::MergeRequests do end end end + + context 'with archived projects' do + let(:project2) { create(:project, :public, :archived, namespace: group) } + let!(:merge_request_archived) { create(:merge_request, title: 'archived mr', author: user, source_project: project2, target_project: project2) } + + it 'returns an array excluding merge_requests from archived projects' do + get api(endpoint_path, user) + + expect_response_contain_exactly( + merge_request_merged, + merge_request_locked, + merge_request_closed, + merge_request + ) + end + + context 'with non_archived param set as false' do + it 'returns an array including merge_requests from archived projects' do + path = endpoint_path + '?non_archived=false' + + get api(path, user) + + expect_response_contain_exactly( + merge_request_merged, + merge_request_locked, + merge_request_closed, + merge_request, + merge_request_archived + ) + end + end + end end describe "GET /projects/:id/merge_requests/:merge_request_iid" do diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb index d20ec0b818b..dce0ee05b1f 100644 --- a/spec/services/projects/operations/update_service_spec.rb +++ b/spec/services/projects/operations/update_service_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' describe Projects::Operations::UpdateService do let_it_be(:user) { create(:user) } - let_it_be(:project, reload: true) { create(:project) } + let_it_be(:project, refind: true) { create(:project) } let(:result) { subject.execute } @@ -145,6 +145,48 @@ describe Projects::Operations::UpdateService do end end + context 'partial_update' do + let(:params) do + { + error_tracking_setting_attributes: { + enabled: true + } + } + end + + context 'with setting' do + before do + create(:project_error_tracking_setting, :disabled, project: project) + end + + it 'service succeeds' do + expect(result[:status]).to eq(:success) + end + + it 'updates attributes' do + expect { result } + .to change { project.reload.error_tracking_setting.enabled } + .from(false) + .to(true) + end + + it 'only updates enabled attribute' do + result + + expect(project.error_tracking_setting.previous_changes.keys) + .to contain_exactly('enabled') + end + end + + context 'without setting' do + it 'does not create a setting' do + expect(result[:status]).to eq(:error) + + expect(project.reload.error_tracking_setting).to be_nil + end + end + end + context 'with masked param token' do let(:params) do { diff --git a/spec/support/helpers/controller_helpers.rb b/spec/support/helpers/controller_helpers.rb new file mode 100644 index 00000000000..8f5ef8c9696 --- /dev/null +++ b/spec/support/helpers/controller_helpers.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ControllerHelpers + # It seems Devise::Test::ControllerHelpers#sign_in doesn't clear out the @current_user + # variable of the controller, hence it's not overwritten on retries. + # This should be fixed in Devise: + # - https://github.com/heartcombo/devise/issues/5190 + # - https://github.com/heartcombo/devise/pull/5191 + def sign_in(resource, deprecated = nil, scope: nil) + super + + scope ||= Devise::Mapping.find_scope!(resource) + + @controller.instance_variable_set(:"@current_#{scope}", nil) + end +end + +Devise::Test::ControllerHelpers.prepend(ControllerHelpers) |