Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/CODEOWNERS8
-rw-r--r--Gemfile.lock2
-rw-r--r--app/controllers/application_controller.rb8
-rw-r--r--app/controllers/concerns/cycle_analytics_params.rb4
-rw-r--r--app/services/merge_requests/migrate_external_diffs_service.rb5
-rw-r--r--app/services/projects/operations/update_service.rb21
-rw-r--r--app/workers/schedule_migrate_external_diffs_worker.rb7
-rw-r--r--changelogs/unreleased/28352-group-issues-api-searches-archived-projects.yml5
-rw-r--r--changelogs/unreleased/28352-group-merge-request-api-searches-archived-projects.yml5
-rw-r--r--changelogs/unreleased/enable-disable-error-tracking-api.yml5
-rw-r--r--changelogs/unreleased/refactor-admin-mode-in-sidekiq-jobs.yml5
-rw-r--r--doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md2
-rw-r--r--doc/api/error_tracking.md28
-rw-r--r--doc/api/issues.md1
-rw-r--r--doc/api/merge_requests.md3
-rw-r--r--doc/user/project/import/repo_by_url.md3
-rw-r--r--lib/api/error_tracking.rb28
-rw-r--r--lib/api/issues.rb1
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/gitlab/auth/current_user_mode.rb61
-rw-r--r--lib/gitlab/sidekiq_middleware.rb2
-rw-r--r--lib/gitlab/sidekiq_middleware/admin_mode/client.rb34
-rw-r--r--lib/gitlab/sidekiq_middleware/admin_mode/server.rb24
-rw-r--r--spec/controllers/groups_controller_spec.rb4
-rw-r--r--spec/factories/project_error_tracking_settings.rb4
-rw-r--r--spec/features/admin/admin_mode/workers_spec.rb77
-rw-r--r--spec/features/admin/admin_uses_repository_checks_spec.rb66
-rw-r--r--spec/frontend/helpers/dom_shims/index.js1
-rw-r--r--spec/frontend/helpers/dom_shims/window_scroll_to.js1
-rw-r--r--spec/frontend/jobs/components/job_app_spec.js (renamed from spec/javascripts/jobs/components/job_app_spec.js)405
-rw-r--r--spec/frontend/jobs/mock_data.js1191
-rw-r--r--spec/javascripts/jobs/mock_data.js1193
-rw-r--r--spec/lib/gitlab/auth/current_user_mode_spec.rb172
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb94
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb72
-rw-r--r--spec/lib/gitlab/sidekiq_middleware_spec.rb6
-rw-r--r--spec/models/user_spec.rb6
-rw-r--r--spec/policies/base_policy_spec.rb10
-rw-r--r--spec/requests/api/error_tracking_spec.rb162
-rw-r--r--spec/requests/api/issues/issues_spec.rb31
-rw-r--r--spec/requests/api/merge_requests_spec.rb32
-rw-r--r--spec/services/projects/operations/update_service_spec.rb44
-rw-r--r--spec/support/helpers/controller_helpers.rb18
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)