summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml8
-rw-r--r--.rubocop.yml2
-rw-r--r--app/assets/stylesheets/framework/secondary_navigation_elements.scss4
-rw-r--r--app/models/concerns/milestoneish.rb16
-rw-r--r--app/models/global_milestone.rb2
-rw-r--r--app/services/ci/daily_report_result_service.rb15
-rw-r--r--app/views/ci/variables/_index.html.haml2
-rw-r--r--app/views/projects/issues/_nav_btns.html.haml2
-rw-r--r--app/views/projects/issues/import_csv/_button.html.haml27
-rw-r--r--app/views/shared/milestones/_sidebar.html.haml8
-rw-r--r--changelogs/unreleased/Resolve-Migrate--fa-spinner-app-views-ci-variables.yml5
-rw-r--r--changelogs/unreleased/admin-routing-spec.yml5
-rw-r--r--changelogs/unreleased/eb-fix-daily-report-results-upsert.yml6
-rwxr-xr-xchangelogs/unreleased/include-mr-times.yml5
-rw-r--r--changelogs/unreleased/validate-dynamic-pipeline-dependencies.yml5
-rw-r--r--doc/administration/instance_limits.md10
-rw-r--r--doc/ci/parent_child_pipelines.md7
-rw-r--r--doc/user/project/milestones/index.md2
-rw-r--r--lib/gitlab/ci/config/entry/include.rb5
-rw-r--r--lib/gitlab/ci/yaml_processor.rb50
-rw-r--r--lib/gitlab/database/batch_count.rb4
-rw-r--r--lib/gitlab/jira_import/issue_serializer.rb47
-rw-r--r--locale/gitlab.pot27
-rw-r--r--spec/controllers/projects/settings/ci_cd_controller_spec.rb8
-rw-r--r--spec/frontend/ide/components/commit_sidebar/actions_spec.js141
-rw-r--r--spec/frontend/ide/components/commit_sidebar/empty_state_spec.js (renamed from spec/javascripts/ide/components/commit_sidebar/empty_state_spec.js)0
-rw-r--r--spec/frontend/ide/components/commit_sidebar/form_spec.js (renamed from spec/javascripts/ide/components/commit_sidebar/form_spec.js)16
-rw-r--r--spec/frontend/ide/components/commit_sidebar/list_collapsed_spec.js (renamed from spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js)2
-rw-r--r--spec/frontend/ide/components/commit_sidebar/list_item_spec.js (renamed from spec/javascripts/ide/components/commit_sidebar/list_item_spec.js)18
-rw-r--r--spec/frontend/ide/components/commit_sidebar/list_spec.js (renamed from spec/javascripts/ide/components/commit_sidebar/list_spec.js)2
-rw-r--r--spec/frontend/ide/components/commit_sidebar/new_merge_request_option_spec.js (renamed from spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js)10
-rw-r--r--spec/frontend/ide/components/commit_sidebar/success_message_spec.js (renamed from spec/javascripts/ide/components/commit_sidebar/success_message_spec.js)0
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/actions_spec.js235
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb70
-rw-r--r--spec/lib/gitlab/jira_import/issue_serializer_spec.rb105
-rw-r--r--spec/models/concerns/milestoneish_spec.rb45
-rw-r--r--spec/routing/admin_routing_spec.rb4
-rw-r--r--spec/services/ci/daily_report_result_service_spec.rb21
39 files changed, 593 insertions, 350 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3340ca9d64c..700e2232da1 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-9.6-graphicsmagick-1.3.34"
+image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.14-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-9.6-graphicsmagick-1.3.34"
stages:
- sync
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 7deb4f00a84..e6b81c9f366 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -38,7 +38,7 @@
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg10:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-10-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.14-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-10-graphicsmagick-1.3.34"
services:
- name: postgres:10.12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@@ -47,7 +47,7 @@
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg11:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.14-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
services:
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@@ -65,7 +65,7 @@
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg10-ee:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-10-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.14-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-10-graphicsmagick-1.3.34"
services:
- name: postgres:10.12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@@ -75,7 +75,7 @@
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg11-ee:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.14-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
services:
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
diff --git a/.rubocop.yml b/.rubocop.yml
index ee840476c8a..4262fd9ca74 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -400,8 +400,6 @@ RSpec/RepeatedExample:
- 'spec/lib/gitlab/closing_issue_extractor_spec.rb'
- 'spec/lib/gitlab/danger/changelog_spec.rb'
- 'spec/lib/gitlab/import_export/project/relation_factory_spec.rb'
- - 'spec/routing/admin_routing_spec.rb'
- 'spec/rubocop/cop/migration/update_large_table_spec.rb'
- 'spec/services/notification_service_spec.rb'
- 'spec/services/web_hook_service_spec.rb'
- - 'ee/spec/services/geo/repository_verification_primary_service_spec.rb'
diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
index d4ee59790e9..79f203091f2 100644
--- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss
+++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
@@ -121,6 +121,7 @@
}
> .btn,
+ > .btn-group,
> .btn-container,
> .dropdown,
> input,
@@ -161,7 +162,8 @@
.dropdown,
.dropdown-toggle,
.dropdown-menu-toggle,
- .form-control {
+ .form-control,
+ > .btn-group {
margin: 0 0 $gl-padding-8;
display: block;
width: 100%;
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index fac058e5a46..fa5a79cc12b 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -117,20 +117,20 @@ module Milestoneish
false
end
- def total_issue_time_spent
- @total_issue_time_spent ||= issues.joins(:timelogs).sum(:time_spent)
+ def total_time_spent
+ @total_time_spent ||= issues.joins(:timelogs).sum(:time_spent) + merge_requests.joins(:timelogs).sum(:time_spent)
end
- def human_total_issue_time_spent
- Gitlab::TimeTrackingFormatter.output(total_issue_time_spent)
+ def human_total_time_spent
+ Gitlab::TimeTrackingFormatter.output(total_time_spent)
end
- def total_issue_time_estimate
- @total_issue_time_estimate ||= issues.sum(:time_estimate)
+ def total_time_estimate
+ @total_time_estimate ||= issues.sum(:time_estimate) + merge_requests.sum(:time_estimate)
end
- def human_total_issue_time_estimate
- Gitlab::TimeTrackingFormatter.output(total_issue_time_estimate)
+ def human_total_time_estimate
+ Gitlab::TimeTrackingFormatter.output(total_time_estimate)
end
private
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 65fd5c1b35a..d0cec0e9fc6 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -94,7 +94,7 @@ class GlobalMilestone
end
def merge_requests
- @merge_requests ||= MergeRequest.of_milestones(milestone).includes(:target_project, :assignee, :labels)
+ @merge_requests ||= MergeRequest.of_milestones(milestone).includes(:target_project, :assignees, :labels)
end
def labels
diff --git a/app/services/ci/daily_report_result_service.rb b/app/services/ci/daily_report_result_service.rb
index 79b5015c076..b774a806203 100644
--- a/app/services/ci/daily_report_result_service.rb
+++ b/app/services/ci/daily_report_result_service.rb
@@ -19,12 +19,21 @@ module Ci
last_pipeline_id: pipeline.id
}
- pipeline.builds.with_coverage.map do |build|
+ aggregate(pipeline.builds.with_coverage).map do |group_name, group|
base_attrs.merge(
- title: build.group_name,
- value: build.coverage
+ title: group_name,
+ value: average_coverage(group)
)
end
end
+
+ def aggregate(builds)
+ builds.group_by(&:group_name)
+ end
+
+ def average_coverage(group)
+ total_coverage = group.reduce(0.0) { |sum, build| sum + build.coverage }
+ (total_coverage / group.size).round(2)
+ end
end
end
diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml
index f11c730eba6..3fa957f38a0 100644
--- a/app/views/ci/variables/_index.html.haml
+++ b/app/views/ci/variables/_index.html.haml
@@ -23,7 +23,7 @@
.prepend-top-20
%button.btn.btn-success.js-ci-variables-save-button{ type: 'button' }
%span.hide.js-ci-variables-save-loading-icon
- = icon('spinner spin')
+ .spinner.spinner-light.mr-1
= _('Save variables')
%button.btn.btn-info.btn-inverted.prepend-left-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: "#{@variables.size == 0}" } }
- if @variables.size == 0
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
index d81089bee68..c347b8d2c9c 100644
--- a/app/views/projects/issues/_nav_btns.html.haml
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -6,7 +6,7 @@
- if show_feed_buttons
= render 'shared/issuable/feed_buttons'
- .btn-group.append-right-10<
+ .btn-group
- if show_export_button
= render_if_exists 'projects/issues/export_csv/button'
diff --git a/app/views/projects/issues/import_csv/_button.html.haml b/app/views/projects/issues/import_csv/_button.html.haml
index 78c561e81ef..0a352d26b0b 100644
--- a/app/views/projects/issues/import_csv/_button.html.haml
+++ b/app/views/projects/issues/import_csv/_button.html.haml
@@ -1,11 +1,22 @@
- type = local_assigns.fetch(:type, :icon)
-%button.csv-import-button.btn{ title: _('Import CSV'), class: ('has-tooltip' if type == :icon),
- data: { toggle: 'modal', target: '.issues-import-modal' } }
- - if type == :icon
- = sprite_icon('import')
- - else
- = _('Import CSV')
-
- if Feature.enabled?(:jira_issue_import, @project)
- = link_to _("Import Jira issues"), project_import_jira_path(@project), class: "btn btn-default"
+ .dropdown.btn-group
+ %button.btn.rounded-right.text-center{ class: ('has-tooltip' if type == :icon), title: (_('Import issues') if type == :icon),
+ data: { toggle: 'dropdown' }, 'aria-label' => _('Import issues'), 'aria-haspopup' => 'true', 'aria-expanded' => 'false' }
+ - if type == :icon
+ = sprite_icon('import')
+ - else
+ = _('Import issues')
+ %ul.dropdown-menu
+ %li
+ %button.btn{ data: { toggle: 'modal', target: '.issues-import-modal' } }
+ = _('Import CSV')
+ %li= link_to _('Import from Jira'), project_import_jira_path(@project)
+- else
+ %button.csv-import-button.btn{ title: _('Import CSV'), class: ('has-tooltip' if type == :icon),
+ data: { toggle: 'modal', target: '.issues-import-modal' } }
+ - if type == :icon
+ = sprite_icon('import')
+ - else
+ = _('Import CSV')
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index aa9c4be1cc1..ba1629bd99a 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -93,10 +93,10 @@
= milestone.issues_visible_to_user(current_user).closed.count
.block
- #issuable-time-tracker{ data: { time_estimate: @milestone.total_issue_time_estimate,
- time_spent: @milestone.total_issue_time_spent,
- human_time_estimate: @milestone.human_total_issue_time_estimate,
- human_time_spent: @milestone.human_total_issue_time_spent,
+ #issuable-time-tracker{ data: { time_estimate: @milestone.total_time_estimate,
+ time_spent: @milestone.total_time_spent,
+ human_time_estimate: @milestone.human_total_time_estimate,
+ human_time_spent: @milestone.human_total_time_spent,
limit_to_hours: Gitlab::CurrentSettings.time_tracking_limit_to_hours.to_s } }
= render_if_exists 'shared/milestones/weight', milestone: milestone
diff --git a/changelogs/unreleased/Resolve-Migrate--fa-spinner-app-views-ci-variables.yml b/changelogs/unreleased/Resolve-Migrate--fa-spinner-app-views-ci-variables.yml
new file mode 100644
index 00000000000..a6e90272a8f
--- /dev/null
+++ b/changelogs/unreleased/Resolve-Migrate--fa-spinner-app-views-ci-variables.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate .fa-spinner to .spinner for app/views/ci/variables
+merge_request: 25030
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/admin-routing-spec.yml b/changelogs/unreleased/admin-routing-spec.yml
new file mode 100644
index 00000000000..693aa36bf21
--- /dev/null
+++ b/changelogs/unreleased/admin-routing-spec.yml
@@ -0,0 +1,5 @@
+---
+title: Remove duplicate show spec in admin routing
+merge_request: 28790
+author: Rajendra Kadam
+type: changed
diff --git a/changelogs/unreleased/eb-fix-daily-report-results-upsert.yml b/changelogs/unreleased/eb-fix-daily-report-results-upsert.yml
new file mode 100644
index 00000000000..f1578d8ba27
--- /dev/null
+++ b/changelogs/unreleased/eb-fix-daily-report-results-upsert.yml
@@ -0,0 +1,6 @@
+---
+title: Fix daily report result to use average of coverage values if there are multiple builds for a given group
+ name
+merge_request: 28556
+author:
+type: fixed
diff --git a/changelogs/unreleased/include-mr-times.yml b/changelogs/unreleased/include-mr-times.yml
new file mode 100755
index 00000000000..0e2e3a64dd5
--- /dev/null
+++ b/changelogs/unreleased/include-mr-times.yml
@@ -0,0 +1,5 @@
+---
+title: Include MR times in Milestone time overview
+merge_request: 28519
+author: Bob van de Vijver
+type: fixed
diff --git a/changelogs/unreleased/validate-dynamic-pipeline-dependencies.yml b/changelogs/unreleased/validate-dynamic-pipeline-dependencies.yml
new file mode 100644
index 00000000000..76a75022eab
--- /dev/null
+++ b/changelogs/unreleased/validate-dynamic-pipeline-dependencies.yml
@@ -0,0 +1,5 @@
+---
+title: Validate dependency on job generating a CI config when using dynamic child pipelines
+merge_request: 27916
+author:
+type: added
diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md
index 99abf5d3864..c53c46bf0cb 100644
--- a/doc/administration/instance_limits.md
+++ b/doc/administration/instance_limits.md
@@ -31,6 +31,16 @@ It's possible that this limit will be changed to a lower number in the future.
- **Max size:** ~1 million characters / ~1 MB
+## Number of issues in the milestone overview
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/39453) in GitLab 12.10.
+
+The maximum number of issues loaded on the milestone overview page is 3000.
+When the number exceeds the limit the page displays an alert and links to a paginated
+[issue list](../user/project/issues/index.md#issues-list) of all issues in the milestone.
+
+- **Limit:** 3000 issues
+
## Number of pipelines per Git push
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/issues/51401) in GitLab 11.10.
diff --git a/doc/ci/parent_child_pipelines.md b/doc/ci/parent_child_pipelines.md
index b39e0b6e540..2bc897901fa 100644
--- a/doc/ci/parent_child_pipelines.md
+++ b/doc/ci/parent_child_pipelines.md
@@ -136,12 +136,11 @@ your own script to generate a YAML file, which is then [used to trigger a child
This technique can be very powerful in generating pipelines targeting content that changed or to
build a matrix of targets and architectures.
+In GitLab 12.9, the child pipeline could fail to be created in certain cases, causing the parent pipeline to fail.
+This is [resolved in GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/issues/209070).
+
## Limitations
A parent pipeline can trigger many child pipelines, but a child pipeline cannot trigger
further child pipelines. See the [related issue](https://gitlab.com/gitlab-org/gitlab/issues/29651)
for discussion on possible future improvements.
-
-When triggering dynamic child pipelines, if the job containing the CI config artifact is not a predecessor of the
-trigger job, the child pipeline will fail to be created, causing also the parent pipeline to fail.
-In the future we want to validate the trigger job's dependencies [at the time the parent pipeline is created](https://gitlab.com/gitlab-org/gitlab/-/issues/209070) rather than when the child pipeline is created.
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index 89c5522f4c4..085c1bd143e 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -133,7 +133,7 @@ The milestone sidebar on the milestone view shows the following:
- Percentage complete, which is calculated as number of closed issues divided by total number of issues.
- The start date and due date.
-- The total time spent on all issues assigned to the milestone.
+- The total time spent on all issues and merge requests assigned to the milestone.
- The total issue weight of all issues assigned to the milestone.
![Project milestone page](img/milestones_project_milestone_page.png)
diff --git a/lib/gitlab/ci/config/entry/include.rb b/lib/gitlab/ci/config/entry/include.rb
index cd09d83b728..b2586714636 100644
--- a/lib/gitlab/ci/config/entry/include.rb
+++ b/lib/gitlab/ci/config/entry/include.rb
@@ -15,6 +15,11 @@ module Gitlab
validations do
validates :config, hash_or_string: true
validates :config, allowed_keys: ALLOWED_KEYS
+ validate do
+ if config[:artifact] && config[:job].blank?
+ errors.add(:config, "must specify the job where to fetch the artifact from")
+ end
+ end
end
end
end
diff --git a/lib/gitlab/ci/yaml_processor.rb b/lib/gitlab/ci/yaml_processor.rb
index 764047dae6d..4b0062549f0 100644
--- a/lib/gitlab/ci/yaml_processor.rb
+++ b/lib/gitlab/ci/yaml_processor.rb
@@ -142,6 +142,7 @@ module Gitlab
validate_job_stage!(name, job)
validate_job_dependencies!(name, job)
validate_job_needs!(name, job)
+ validate_dynamic_child_pipeline_dependencies!(name, job)
validate_job_environment!(name, job)
end
end
@@ -163,37 +164,52 @@ module Gitlab
def validate_job_dependencies!(name, job)
return unless job[:dependencies]
- stage_index = @stages.index(job[:stage])
-
job[:dependencies].each do |dependency|
- raise ValidationError, "#{name} job: undefined dependency: #{dependency}" unless @jobs[dependency.to_sym]
+ validate_job_dependency!(name, dependency)
+ end
+ end
- dependency_stage_index = @stages.index(@jobs[dependency.to_sym][:stage])
+ def validate_dynamic_child_pipeline_dependencies!(name, job)
+ return unless includes = job.dig(:trigger, :include)
- unless dependency_stage_index.present? && dependency_stage_index < stage_index
- raise ValidationError, "#{name} job: dependency #{dependency} is not defined in prior stages"
- end
+ includes.each do |included|
+ next unless dependency = included[:job]
+
+ validate_job_dependency!(name, dependency)
end
end
def validate_job_needs!(name, job)
- return unless job.dig(:needs, :job)
-
- stage_index = @stages.index(job[:stage])
+ return unless needs = job.dig(:needs, :job)
- job.dig(:needs, :job).each do |need|
- need_job_name = need[:name]
+ needs.each do |need|
+ dependency = need[:name]
+ validate_job_dependency!(name, dependency, 'need')
+ end
+ end
- raise ValidationError, "#{name} job: undefined need: #{need_job_name}" unless @jobs[need_job_name.to_sym]
+ def validate_job_dependency!(name, dependency, dependency_type = 'dependency')
+ unless @jobs[dependency.to_sym]
+ raise ValidationError, "#{name} job: undefined #{dependency_type}: #{dependency}"
+ end
- needs_stage_index = @stages.index(@jobs[need_job_name.to_sym][:stage])
+ job_stage_index = stage_index(name)
+ dependency_stage_index = stage_index(dependency)
- unless needs_stage_index.present? && needs_stage_index < stage_index
- raise ValidationError, "#{name} job: need #{need_job_name} is not defined in prior stages"
- end
+ # A dependency might be defined later in the configuration
+ # with a stage that does not exist
+ unless dependency_stage_index.present? && dependency_stage_index < job_stage_index
+ raise ValidationError, "#{name} job: #{dependency_type} #{dependency} is not defined in prior stages"
end
end
+ def stage_index(name)
+ job = @jobs[name.to_sym]
+ return unless job
+
+ @stages.index(job[:stage])
+ end
+
def validate_job_environment!(name, job)
return unless job[:environment]
return unless job[:environment].is_a?(Hash)
diff --git a/lib/gitlab/database/batch_count.rb b/lib/gitlab/database/batch_count.rb
index e0f6b0f9eee..5987dc34801 100644
--- a/lib/gitlab/database/batch_count.rb
+++ b/lib/gitlab/database/batch_count.rb
@@ -39,8 +39,8 @@ module Gitlab
SLEEP_TIME_IN_SECONDS = 0.01 # 10 msec sleep
# Each query should take < 500ms https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22705
- DEFAULT_DISTINCT_BATCH_SIZE = 100_000
- DEFAULT_BATCH_SIZE = 10_000
+ DEFAULT_DISTINCT_BATCH_SIZE = 10_000
+ DEFAULT_BATCH_SIZE = 100_000
def initialize(relation, column: nil)
@relation = relation
diff --git a/lib/gitlab/jira_import/issue_serializer.rb b/lib/gitlab/jira_import/issue_serializer.rb
index 0be5d22065a..cdcb62ac6e9 100644
--- a/lib/gitlab/jira_import/issue_serializer.rb
+++ b/lib/gitlab/jira_import/issue_serializer.rb
@@ -4,12 +4,14 @@ module Gitlab
module JiraImport
class IssueSerializer
attr_reader :jira_issue, :project, :params, :formatter
+ attr_accessor :metadata
def initialize(project, jira_issue, params = {})
@jira_issue = jira_issue
@project = project
@params = params
@formatter = Gitlab::ImportFormatter.new
+ @metadata = []
end
def execute
@@ -36,6 +38,7 @@ module Gitlab
body << formatter.author_line(jira_issue.reporter.displayName)
body << formatter.assignee_line(jira_issue.assignee.displayName) if jira_issue.assignee
body << jira_issue.description
+ body << add_metadata
body.join
end
@@ -48,6 +51,50 @@ module Gitlab
Issuable::STATE_ID_MAP[:opened]
end
end
+
+ def add_metadata
+ add_field(%w(issuetype name), 'Issue type')
+ add_field(%w(priority name), 'Priority')
+ add_labels
+ add_field('environment', 'Environment')
+ add_field('duedate', 'Due date')
+ add_parent
+ add_versions
+
+ return if metadata.empty?
+
+ metadata.join("\n").prepend("\n\n---\n\n**Issue metadata**\n\n")
+ end
+
+ def add_field(keys, field_label)
+ value = fields.dig(*keys)
+ return if value.blank?
+
+ metadata << "- #{field_label}: #{value}"
+ end
+
+ def add_labels
+ return if fields['labels'].blank?
+
+ metadata << "- Labels: #{fields['labels'].join(', ')}"
+ end
+
+ def add_parent
+ parent_issue_key = fields.dig('parent', 'key')
+ return if parent_issue_key.blank?
+
+ metadata << "- Parent issue: [#{parent_issue_key}] #{fields['parent']['fields']['summary']}"
+ end
+
+ def add_versions
+ return if fields['fixVersions'].blank?
+
+ metadata << "- Fix versions: #{fields['fixVersions'].map { |version| version['name'] }.join(', ')}"
+ end
+
+ def fields
+ jira_issue.fields
+ end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 278aca1bf86..b9f0e1f7b8b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -10815,9 +10815,6 @@ msgstr ""
msgid "Import CSV"
msgstr ""
-msgid "Import Jira issues"
-msgstr ""
-
msgid "Import Projects from Gitea"
msgstr ""
@@ -10833,6 +10830,9 @@ msgstr ""
msgid "Import an exported GitLab project"
msgstr ""
+msgid "Import from Jira"
+msgstr ""
+
msgid "Import in progress"
msgstr ""
@@ -17829,6 +17829,9 @@ msgstr ""
msgid "SecurityDashboard|Severity"
msgstr ""
+msgid "SecurityDashboard|Status"
+msgstr ""
+
msgid "SecurityDashboard|The security dashboard displays the latest security findings for projects you wish to monitor. Select \"Edit dashboard\" to add and remove projects."
msgstr ""
@@ -22684,6 +22687,21 @@ msgstr ""
msgid "VulnerabilityManagement|Will not fix or a false-positive"
msgstr ""
+msgid "VulnerabilityStatusTypes|All"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Confirmed"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Detected"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Dismissed"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Resolved"
+msgstr ""
+
msgid "Vulnerability|Class"
msgstr ""
@@ -24787,9 +24805,6 @@ msgstr ""
msgid "severity|None"
msgstr ""
-msgid "severity|Undefined"
-msgstr ""
-
msgid "severity|Unknown"
msgstr ""
diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
index 8f516de3322..3684a1bb8d8 100644
--- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
@@ -157,6 +157,14 @@ describe Projects::Settings::CiCdController do
subject
end
+
+ it 'creates a pipeline', :sidekiq_inline do
+ project.repository.create_file(user, 'Gemfile', 'Gemfile contents',
+ message: 'Add Gemfile',
+ branch_name: 'master')
+
+ expect { subject }.to change { Ci::Pipeline.count }.by(1)
+ end
end
end
diff --git a/spec/frontend/ide/components/commit_sidebar/actions_spec.js b/spec/frontend/ide/components/commit_sidebar/actions_spec.js
new file mode 100644
index 00000000000..b3b98a64891
--- /dev/null
+++ b/spec/frontend/ide/components/commit_sidebar/actions_spec.js
@@ -0,0 +1,141 @@
+import Vue from 'vue';
+import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
+import { projectData, branches } from 'jest/ide/mock_data';
+import { createStore } from '~/ide/stores';
+import commitActions from '~/ide/components/commit_sidebar/actions.vue';
+import consts from '~/ide/stores/modules/commit/constants';
+
+const ACTION_UPDATE_COMMIT_ACTION = 'commit/updateCommitAction';
+
+const BRANCH_DEFAULT = 'master';
+const BRANCH_PROTECTED = 'protected/access';
+const BRANCH_PROTECTED_NO_ACCESS = 'protected/no-access';
+const BRANCH_REGULAR = 'regular';
+const BRANCH_REGULAR_NO_ACCESS = 'regular/no-access';
+
+describe('IDE commit sidebar actions', () => {
+ let store;
+ let vm;
+
+ const createComponent = ({ hasMR = false, currentBranchId = 'master' } = {}) => {
+ const Component = Vue.extend(commitActions);
+
+ vm = createComponentWithStore(Component, store);
+
+ vm.$store.state.currentBranchId = currentBranchId;
+ vm.$store.state.currentProjectId = 'abcproject';
+
+ const proj = { ...projectData };
+ proj.branches[currentBranchId] = branches.find(branch => branch.name === currentBranchId);
+
+ Vue.set(vm.$store.state.projects, 'abcproject', proj);
+
+ if (hasMR) {
+ vm.$store.state.currentMergeRequestId = '1';
+ vm.$store.state.projects[store.state.currentProjectId].mergeRequests[
+ store.state.currentMergeRequestId
+ ] = { foo: 'bar' };
+ }
+
+ vm.$mount();
+
+ return vm;
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ jest.spyOn(store, 'dispatch').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ vm = null;
+ });
+
+ it('renders 2 groups', () => {
+ createComponent();
+
+ expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(2);
+ });
+
+ it('renders current branch text', () => {
+ createComponent();
+
+ expect(vm.$el.textContent).toContain('Commit to master branch');
+ });
+
+ it('hides merge request option when project merge requests are disabled', done => {
+ createComponent({ mergeRequestsEnabled: false });
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(2);
+ expect(vm.$el.textContent).not.toContain('Create a new branch and merge request');
+
+ done();
+ });
+ });
+
+ describe('commitToCurrentBranchText', () => {
+ it('escapes current branch', () => {
+ const injectedSrc = '<img src="x" />';
+ createComponent({ currentBranchId: injectedSrc });
+
+ expect(vm.commitToCurrentBranchText).not.toContain(injectedSrc);
+ });
+ });
+
+ describe('updateSelectedCommitAction', () => {
+ it('does not return anything if currentBranch does not exist', () => {
+ createComponent({ currentBranchId: null });
+
+ expect(vm.$store.dispatch).not.toHaveBeenCalled();
+ });
+
+ it('is not called on mount if there is already a selected commitAction', () => {
+ store.state.commitAction = '1';
+ createComponent({ currentBranchId: null });
+
+ expect(vm.$store.dispatch).not.toHaveBeenCalled();
+ });
+
+ it('calls again after staged changes', done => {
+ createComponent({ currentBranchId: null });
+
+ vm.$store.state.currentBranchId = 'master';
+ vm.$store.state.changedFiles.push({});
+ vm.$store.state.stagedFiles.push({});
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ expect.anything(),
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it.each`
+ input | expectedOption
+ ${{ currentBranchId: BRANCH_DEFAULT }} | ${consts.COMMIT_TO_NEW_BRANCH}
+ ${{ currentBranchId: BRANCH_PROTECTED, hasMR: true }} | ${consts.COMMIT_TO_CURRENT_BRANCH}
+ ${{ currentBranchId: BRANCH_PROTECTED, hasMR: false }} | ${consts.COMMIT_TO_CURRENT_BRANCH}
+ ${{ currentBranchId: BRANCH_PROTECTED_NO_ACCESS, hasMR: true }} | ${consts.COMMIT_TO_NEW_BRANCH}
+ ${{ currentBranchId: BRANCH_PROTECTED_NO_ACCESS, hasMR: false }} | ${consts.COMMIT_TO_NEW_BRANCH}
+ ${{ currentBranchId: BRANCH_REGULAR, hasMR: true }} | ${consts.COMMIT_TO_CURRENT_BRANCH}
+ ${{ currentBranchId: BRANCH_REGULAR, hasMR: false }} | ${consts.COMMIT_TO_CURRENT_BRANCH}
+ ${{ currentBranchId: BRANCH_REGULAR_NO_ACCESS, hasMR: true }} | ${consts.COMMIT_TO_NEW_BRANCH}
+ ${{ currentBranchId: BRANCH_REGULAR_NO_ACCESS, hasMR: false }} | ${consts.COMMIT_TO_NEW_BRANCH}
+ `(
+ 'with $input, it dispatches update commit action with $expectedOption',
+ ({ input, expectedOption }) => {
+ createComponent(input);
+
+ expect(vm.$store.dispatch.mock.calls).toEqual([
+ [ACTION_UPDATE_COMMIT_ACTION, expectedOption],
+ ]);
+ },
+ );
+ });
+});
diff --git a/spec/javascripts/ide/components/commit_sidebar/empty_state_spec.js b/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js
index 16d0b354a30..16d0b354a30 100644
--- a/spec/javascripts/ide/components/commit_sidebar/empty_state_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js
diff --git a/spec/javascripts/ide/components/commit_sidebar/form_spec.js b/spec/frontend/ide/components/commit_sidebar/form_spec.js
index f5d1a9de59c..dfde69ab2df 100644
--- a/spec/javascripts/ide/components/commit_sidebar/form_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/form_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
-import { projectData } from 'spec/ide/mock_data';
+import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { projectData } from 'jest/ide/mock_data';
import store from '~/ide/stores';
import CommitForm from '~/ide/components/commit_sidebar/form.vue';
import { leftSidebarViews } from '~/ide/constants';
@@ -12,8 +12,6 @@ describe('IDE commit form', () => {
let vm;
beforeEach(() => {
- spyOnProperty(window, 'innerHeight').and.returnValue(800);
-
store.state.changedFiles.push('test');
store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master';
@@ -111,7 +109,7 @@ describe('IDE commit form', () => {
textarea.dispatchEvent(new Event('input'));
- getSetTimeoutPromise()
+ waitForPromises()
.then(() => {
expect(vm.$store.state.commit.commitMessage).toBe('testing commit message');
})
@@ -148,7 +146,7 @@ describe('IDE commit form', () => {
it('resets commitMessage when clicking discard button', done => {
vm.$store.state.commit.commitMessage = 'testing commit message';
- getSetTimeoutPromise()
+ waitForPromises()
.then(() => {
vm.$el.querySelector('.btn-default').click();
})
@@ -163,14 +161,14 @@ describe('IDE commit form', () => {
describe('when submitting', () => {
beforeEach(() => {
- spyOn(vm, 'commitChanges');
+ jest.spyOn(vm, 'commitChanges').mockImplementation(() => {});
vm.$store.state.stagedFiles.push('test');
});
it('calls commitChanges', done => {
vm.$store.state.commit.commitMessage = 'testing commit message';
- getSetTimeoutPromise()
+ waitForPromises()
.then(() => {
vm.$el.querySelector('.btn-success').click();
})
diff --git a/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js b/spec/frontend/ide/components/commit_sidebar/list_collapsed_spec.js
index 6eb912127d5..45372d18965 100644
--- a/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/list_collapsed_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import store from '~/ide/stores';
import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue';
import { file } from '../../helpers';
diff --git a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js b/spec/frontend/ide/components/commit_sidebar/list_item_spec.js
index 63ba6b95619..ebb41448905 100644
--- a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/list_item_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import { trimText } from 'spec/helpers/text_helper';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { trimText } from 'helpers/text_helper';
+import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import store from '~/ide/stores';
import listItem from '~/ide/components/commit_sidebar/list_item.vue';
import router from '~/ide/ide_router';
@@ -61,12 +61,12 @@ describe('Multi-file editor commit sidebar list item', () => {
});
it('opens a closed file in the editor when clicking the file path', done => {
- spyOn(vm, 'openPendingTab').and.callThrough();
- spyOn(router, 'push');
+ jest.spyOn(vm, 'openPendingTab');
+ jest.spyOn(router, 'push').mockImplementation(() => {});
findPathEl.click();
- setTimeout(() => {
+ setImmediate(() => {
expect(vm.openPendingTab).toHaveBeenCalled();
expect(router.push).toHaveBeenCalled();
@@ -75,13 +75,13 @@ describe('Multi-file editor commit sidebar list item', () => {
});
it('calls updateViewer with diff when clicking file', done => {
- spyOn(vm, 'openFileInEditor').and.callThrough();
- spyOn(vm, 'updateViewer').and.callThrough();
- spyOn(router, 'push');
+ jest.spyOn(vm, 'openFileInEditor');
+ jest.spyOn(vm, 'updateViewer');
+ jest.spyOn(router, 'push').mockImplementation(() => {});
findPathEl.click();
- setTimeout(() => {
+ setImmediate(() => {
expect(vm.updateViewer).toHaveBeenCalledWith('diff');
done();
diff --git a/spec/javascripts/ide/components/commit_sidebar/list_spec.js b/spec/frontend/ide/components/commit_sidebar/list_spec.js
index 5a1682523d8..ee209487665 100644
--- a/spec/javascripts/ide/components/commit_sidebar/list_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/list_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import store from '~/ide/stores';
import commitSidebarList from '~/ide/components/commit_sidebar/list.vue';
import { file, resetStore } from '../../helpers';
diff --git a/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js b/spec/frontend/ide/components/commit_sidebar/new_merge_request_option_spec.js
index 7c0b4000229..7cbf5ebc61a 100644
--- a/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/new_merge_request_option_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { projectData, branches } from 'spec/ide/mock_data';
+import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
+import { projectData, branches } from 'jest/ide/mock_data';
import NewMergeRequestOption from '~/ide/components/commit_sidebar/new_merge_request_option.vue';
import { createStore } from '~/ide/stores';
import { PERMISSION_CREATE_MR } from '~/ide/constants';
@@ -200,11 +200,11 @@ describe('create new MR checkbox', () => {
currentBranchId: 'regular',
});
const el = vm.$el.querySelector('input[type="checkbox"]');
- spyOn(vm.$store, 'dispatch');
+ jest.spyOn(vm.$store, 'dispatch').mockImplementation(() => {});
el.dispatchEvent(new Event('change'));
- expect(vm.$store.dispatch.calls.allArgs()).toEqual(
- jasmine.arrayContaining([['commit/toggleShouldCreateMR', jasmine.any(Object)]]),
+ expect(vm.$store.dispatch.mock.calls).toEqual(
+ expect.arrayContaining([['commit/toggleShouldCreateMR', expect.any(Object)]]),
);
});
});
diff --git a/spec/javascripts/ide/components/commit_sidebar/success_message_spec.js b/spec/frontend/ide/components/commit_sidebar/success_message_spec.js
index e1a432b81be..e1a432b81be 100644
--- a/spec/javascripts/ide/components/commit_sidebar/success_message_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/success_message_spec.js
diff --git a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
deleted file mode 100644
index a8e6195a67c..00000000000
--- a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
+++ /dev/null
@@ -1,235 +0,0 @@
-import Vue from 'vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { projectData, branches } from 'spec/ide/mock_data';
-import { createStore } from '~/ide/stores';
-import commitActions from '~/ide/components/commit_sidebar/actions.vue';
-import consts from '~/ide/stores/modules/commit/constants';
-
-const ACTION_UPDATE_COMMIT_ACTION = 'commit/updateCommitAction';
-
-describe('IDE commit sidebar actions', () => {
- let store;
- let vm;
-
- const createComponent = ({ hasMR = false, currentBranchId = 'master' } = {}) => {
- const Component = Vue.extend(commitActions);
-
- vm = createComponentWithStore(Component, store);
-
- vm.$store.state.currentBranchId = currentBranchId;
- vm.$store.state.currentProjectId = 'abcproject';
-
- const proj = { ...projectData };
- proj.branches[currentBranchId] = branches.find(branch => branch.name === currentBranchId);
-
- Vue.set(vm.$store.state.projects, 'abcproject', proj);
-
- if (hasMR) {
- vm.$store.state.currentMergeRequestId = '1';
- vm.$store.state.projects[store.state.currentProjectId].mergeRequests[
- store.state.currentMergeRequestId
- ] = { foo: 'bar' };
- }
-
- vm.$mount();
-
- return vm;
- };
-
- beforeEach(() => {
- store = createStore();
- spyOn(store, 'dispatch');
- });
-
- afterEach(() => {
- vm.$destroy();
- vm = null;
- });
-
- it('renders 2 groups', () => {
- createComponent();
-
- expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(2);
- });
-
- it('renders current branch text', () => {
- createComponent();
-
- expect(vm.$el.textContent).toContain('Commit to master branch');
- });
-
- it('hides merge request option when project merge requests are disabled', done => {
- createComponent({ mergeRequestsEnabled: false });
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(2);
- expect(vm.$el.textContent).not.toContain('Create a new branch and merge request');
-
- done();
- });
- });
-
- describe('commitToCurrentBranchText', () => {
- it('escapes current branch', () => {
- const injectedSrc = '<img src="x" />';
- createComponent({ currentBranchId: injectedSrc });
-
- expect(vm.commitToCurrentBranchText).not.toContain(injectedSrc);
- });
- });
-
- describe('updateSelectedCommitAction', () => {
- it('does not return anything if currentBranch does not exist', () => {
- createComponent({ currentBranchId: null });
-
- expect(vm.$store.dispatch).not.toHaveBeenCalled();
- });
-
- it('is not called on mount if there is already a selected commitAction', () => {
- store.state.commitAction = '1';
- createComponent({ currentBranchId: null });
-
- expect(vm.$store.dispatch).not.toHaveBeenCalled();
- });
-
- it('calls again after staged changes', done => {
- createComponent({ currentBranchId: null });
-
- vm.$store.state.currentBranchId = 'master';
- vm.$store.state.changedFiles.push({});
- vm.$store.state.stagedFiles.push({});
-
- vm.$nextTick()
- .then(() => {
- expect(vm.$store.dispatch).toHaveBeenCalledWith(
- ACTION_UPDATE_COMMIT_ACTION,
- jasmine.anything(),
- );
- })
- .then(done)
- .catch(done.fail);
- });
-
- describe('default branch', () => {
- it('dispatches correct action for default branch', () => {
- createComponent({
- currentBranchId: 'master',
- });
-
- expect(vm.$store.dispatch).toHaveBeenCalledTimes(1);
- expect(vm.$store.dispatch).toHaveBeenCalledWith(
- ACTION_UPDATE_COMMIT_ACTION,
- consts.COMMIT_TO_NEW_BRANCH,
- );
- });
- });
-
- describe('protected branch', () => {
- describe('with write access', () => {
- it('dispatches correct action when MR exists', () => {
- createComponent({
- hasMR: true,
- currentBranchId: 'protected/access',
- });
-
- expect(vm.$store.dispatch).toHaveBeenCalledWith(
- ACTION_UPDATE_COMMIT_ACTION,
- consts.COMMIT_TO_CURRENT_BRANCH,
- );
- });
-
- it('dispatches correct action when MR does not exists', () => {
- createComponent({
- hasMR: false,
- currentBranchId: 'protected/access',
- });
-
- expect(vm.$store.dispatch).toHaveBeenCalledWith(
- ACTION_UPDATE_COMMIT_ACTION,
- consts.COMMIT_TO_CURRENT_BRANCH,
- );
- });
- });
-
- describe('without write access', () => {
- it('dispatches correct action when MR exists', () => {
- createComponent({
- hasMR: true,
- currentBranchId: 'protected/no-access',
- });
-
- expect(vm.$store.dispatch).toHaveBeenCalledWith(
- ACTION_UPDATE_COMMIT_ACTION,
- consts.COMMIT_TO_NEW_BRANCH,
- );
- });
-
- it('dispatches correct action when MR does not exists', () => {
- createComponent({
- hasMR: false,
- currentBranchId: 'protected/no-access',
- });
-
- expect(vm.$store.dispatch).toHaveBeenCalledWith(
- ACTION_UPDATE_COMMIT_ACTION,
- consts.COMMIT_TO_NEW_BRANCH,
- );
- });
- });
- });
-
- describe('regular branch', () => {
- describe('with write access', () => {
- it('dispatches correct action when MR exists', () => {
- createComponent({
- hasMR: true,
- currentBranchId: 'regular',
- });
-
- expect(vm.$store.dispatch).toHaveBeenCalledWith(
- ACTION_UPDATE_COMMIT_ACTION,
- consts.COMMIT_TO_CURRENT_BRANCH,
- );
- });
-
- it('dispatches correct action when MR does not exists', () => {
- createComponent({
- hasMR: false,
- currentBranchId: 'regular',
- });
-
- expect(vm.$store.dispatch).toHaveBeenCalledWith(
- ACTION_UPDATE_COMMIT_ACTION,
- consts.COMMIT_TO_CURRENT_BRANCH,
- );
- });
- });
-
- describe('without write access', () => {
- it('dispatches correct action when MR exists', () => {
- createComponent({
- hasMR: true,
- currentBranchId: 'regular/no-access',
- });
-
- expect(vm.$store.dispatch).toHaveBeenCalledWith(
- ACTION_UPDATE_COMMIT_ACTION,
- consts.COMMIT_TO_NEW_BRANCH,
- );
- });
-
- it('dispatches correct action when MR does not exists', () => {
- createComponent({
- hasMR: false,
- currentBranchId: 'regular/no-access',
- });
-
- expect(vm.$store.dispatch).toHaveBeenCalledWith(
- ACTION_UPDATE_COMMIT_ACTION,
- consts.COMMIT_TO_NEW_BRANCH,
- );
- });
- });
- });
- });
-});
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 62adba4319e..0b34e887716 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -1647,6 +1647,48 @@ module Gitlab
it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, /is not defined in prior stages/) }
end
+
+ context 'when trigger job includes artifact generated by a dependency' do
+ context 'when dependency is defined in previous stages' do
+ let(:config) do
+ {
+ build1: { stage: 'build', script: 'test' },
+ test1: { stage: 'test', trigger: {
+ include: [{ job: 'build1', artifact: 'generated.yml' }]
+ } }
+ }
+ end
+
+ it { expect { subject }.not_to raise_error }
+ end
+
+ context 'when dependency is defined in later stages' do
+ let(:config) do
+ {
+ build1: { stage: 'build', script: 'test' },
+ test1: { stage: 'test', trigger: {
+ include: [{ job: 'deploy1', artifact: 'generated.yml' }]
+ } },
+ deploy1: { stage: 'deploy', script: 'test' }
+ }
+ end
+
+ it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, /is not defined in prior stages/) }
+ end
+
+ context 'when dependency is not defined' do
+ let(:config) do
+ {
+ build1: { stage: 'build', script: 'test' },
+ test1: { stage: 'test', trigger: {
+ include: [{ job: 'non-existent', artifact: 'generated.yml' }]
+ } }
+ }
+ end
+
+ it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, /undefined dependency: non-existent/) }
+ end
+ end
end
describe "Job Needs" do
@@ -2052,6 +2094,34 @@ module Gitlab
end
end
+ describe 'with trigger:include' do
+ context 'when artifact and job are specified' do
+ let(:config) do
+ YAML.dump({
+ build1: { stage: 'build', script: 'test' },
+ test1: { stage: 'test', trigger: {
+ include: [{ artifact: 'generated.yml', job: 'build1' }]
+ } }
+ })
+ end
+
+ it { expect { subject }.not_to raise_error }
+ end
+
+ context 'when artifact is specified without job' do
+ let(:config) do
+ YAML.dump({
+ build1: { stage: 'build', script: 'test' },
+ test1: { stage: 'test', trigger: {
+ include: [{ artifact: 'generated.yml' }]
+ } }
+ })
+ end
+
+ it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, /must specify the job where to fetch the artifact from/) }
+ end
+ end
+
describe "Error handling" do
it "fails to parse YAML" do
expect do
diff --git a/spec/lib/gitlab/jira_import/issue_serializer_spec.rb b/spec/lib/gitlab/jira_import/issue_serializer_spec.rb
index 03631a3e941..808ed6ee2fa 100644
--- a/spec/lib/gitlab/jira_import/issue_serializer_spec.rb
+++ b/spec/lib/gitlab/jira_import/issue_serializer_spec.rb
@@ -14,6 +14,29 @@ describe Gitlab::JiraImport::IssueSerializer do
let(:updated_at) { '2020-01-10 20:00:00' }
let(:assignee) { double(displayName: 'Solver') }
let(:jira_status) { 'new' }
+
+ let(:parent_field) do
+ { 'key' => 'FOO-2', 'id' => '1050', 'fields' => { 'summary' => 'parent issue FOO' } }
+ end
+ let(:issue_type_field) { { 'name' => 'Task' } }
+ let(:fix_versions_field) { [{ 'name' => '1.0' }, { 'name' => '1.1' }] }
+ let(:priority_field) { { 'name' => 'Medium' } }
+ let(:labels_field) { %w(bug backend) }
+ let(:environment_field) { 'staging' }
+ let(:duedate_field) { '2020-03-01' }
+
+ let(:fields) do
+ {
+ 'parent' => parent_field,
+ 'issuetype' => issue_type_field,
+ 'fixVersions' => fix_versions_field,
+ 'priority' => priority_field,
+ 'labels' => labels_field,
+ 'environment' => environment_field,
+ 'duedate' => duedate_field
+ }
+ end
+
let(:jira_issue) do
double(
id: '1234',
@@ -24,11 +47,15 @@ describe Gitlab::JiraImport::IssueSerializer do
updated: updated_at,
assignee: assignee,
reporter: double(displayName: 'Reporter'),
- status: double(statusCategory: { 'key' => jira_status })
+ status: double(statusCategory: { 'key' => jira_status }),
+ fields: fields
)
end
+
let(:params) { { iid: iid } }
+ subject { described_class.new(project, jira_issue, params).execute }
+
let(:expected_description) do
<<~MD
*Created by: Reporter*
@@ -36,11 +63,21 @@ describe Gitlab::JiraImport::IssueSerializer do
*Assigned to: Solver*
basic description
+
+ ---
+
+ **Issue metadata**
+
+ - Issue type: Task
+ - Priority: Medium
+ - Labels: bug, backend
+ - Environment: staging
+ - Due date: 2020-03-01
+ - Parent issue: [FOO-2] parent issue FOO
+ - Fix versions: 1.0, 1.1
MD
end
- subject { described_class.new(project, jira_issue, params).execute }
-
context 'attributes setting' do
it 'sets the basic attributes' do
expect(subject).to eq(
@@ -54,6 +91,54 @@ describe Gitlab::JiraImport::IssueSerializer do
author_id: project.creator_id
)
end
+
+ context 'when some metadata fields are missing' do
+ let(:assignee) { nil }
+ let(:parent_field) { nil }
+ let(:fix_versions_field) { [] }
+ let(:labels_field) { [] }
+ let(:environment_field) { nil }
+ let(:duedate_field) { '2020-03-01' }
+
+ it 'skips the missing fields' do
+ expected_description = <<~MD
+ *Created by: Reporter*
+
+ basic description
+
+ ---
+
+ **Issue metadata**
+
+ - Issue type: Task
+ - Priority: Medium
+ - Due date: 2020-03-01
+ MD
+
+ expect(subject[:description]).to eq(expected_description.strip)
+ end
+ end
+
+ context 'when all metadata fields are missing' do
+ let(:assignee) { nil }
+ let(:parent_field) { nil }
+ let(:issue_type_field) { nil }
+ let(:fix_versions_field) { [] }
+ let(:priority_field) { nil }
+ let(:labels_field) { [] }
+ let(:environment_field) { nil }
+ let(:duedate_field) { nil }
+
+ it 'skips the whole metadata secction' do
+ expected_description = <<~MD
+ *Created by: Reporter*
+
+ basic description
+ MD
+
+ expect(subject[:description]).to eq(expected_description.strip)
+ end
+ end
end
context 'with done status' do
@@ -64,20 +149,6 @@ describe Gitlab::JiraImport::IssueSerializer do
end
end
- context 'without the assignee' do
- let(:assignee) { nil }
-
- it 'does not include assignee in the description' do
- expected_description = <<~MD
- *Created by: Reporter*
-
- basic description
- MD
-
- expect(subject[:description]).to eq(expected_description.strip)
- end
- end
-
context 'without the iid' do
let(:params) { {} }
diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb
index 5808d6e37e5..81f173cd23a 100644
--- a/spec/models/concerns/milestoneish_spec.rb
+++ b/spec/models/concerns/milestoneish_spec.rb
@@ -302,20 +302,55 @@ describe Milestone, 'Milestoneish' do
end
end
- describe '#total_issue_time_spent' do
- it 'calculates total issue time spent' do
+ describe '#total_time_spent' do
+ it 'calculates total time spent' do
closed_issue_1.spend_time(duration: 300, user_id: author.id)
closed_issue_1.save!
closed_issue_2.spend_time(duration: 600, user_id: assignee.id)
closed_issue_2.save!
- expect(milestone.total_issue_time_spent).to eq(900)
+ expect(milestone.total_time_spent).to eq(900)
+ end
+
+ it 'includes merge request time spent' do
+ closed_issue_1.spend_time(duration: 300, user_id: author.id)
+ closed_issue_1.save!
+ merge_request.spend_time(duration: 900, user_id: author.id)
+ merge_request.save!
+
+ expect(milestone.total_time_spent).to eq(1200)
+ end
+ end
+
+ describe '#human_total_time_spent' do
+ it 'returns nil if no time has been spent' do
+ expect(milestone.human_total_time_spent).to be_nil
+ end
+ end
+
+ describe '#total_time_estimate' do
+ it 'calculates total estimate' do
+ closed_issue_1.time_estimate = 300
+ closed_issue_1.save!
+ closed_issue_2.time_estimate = 600
+ closed_issue_2.save!
+
+ expect(milestone.total_time_estimate).to eq(900)
+ end
+
+ it 'includes merge request time estimate' do
+ closed_issue_1.time_estimate = 300
+ closed_issue_1.save!
+ merge_request.time_estimate = 900
+ merge_request.save!
+
+ expect(milestone.total_time_estimate).to eq(1200)
end
end
- describe '#human_total_issue_time_spent' do
+ describe '#human_total_time_estimate' do
it 'returns nil if no time has been spent' do
- expect(milestone.human_total_issue_time_spent).to be_nil
+ expect(milestone.human_total_time_estimate).to be_nil
end
end
end
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index 93b2c19c74a..10cf76b607f 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -40,10 +40,6 @@ describe Admin::UsersController, "routing" do
expect(get("/admin/users/1/edit")).to route_to('admin/users#edit', id: '1')
end
- it "to #show" do
- expect(get("/admin/users/1")).to route_to('admin/users#show', id: '1')
- end
-
it "to #update" do
expect(put("/admin/users/1")).to route_to('admin/users#update', id: '1')
end
diff --git a/spec/services/ci/daily_report_result_service_spec.rb b/spec/services/ci/daily_report_result_service_spec.rb
index 793fc956acb..240709bab0b 100644
--- a/spec/services/ci/daily_report_result_service_spec.rb
+++ b/spec/services/ci/daily_report_result_service_spec.rb
@@ -38,6 +38,27 @@ describe Ci::DailyReportResultService, '#execute' do
expect(Ci::DailyReportResult.find_by(title: 'extra')).to be_nil
end
+ context 'when there are multiple builds with the same group name that report coverage' do
+ let!(:test_job_1) { create(:ci_build, pipeline: pipeline, name: '1/2 test', coverage: 70) }
+ let!(:test_job_2) { create(:ci_build, pipeline: pipeline, name: '2/2 test', coverage: 80) }
+
+ it 'creates daily code coverage record with the average as the value' do
+ described_class.new.execute(pipeline)
+
+ Ci::DailyReportResult.find_by(title: 'test').tap do |coverage|
+ expect(coverage).to have_attributes(
+ project_id: pipeline.project.id,
+ last_pipeline_id: pipeline.id,
+ ref_path: pipeline.source_ref_path,
+ param_type: 'coverage',
+ title: test_job_2.group_name,
+ value: 75,
+ date: pipeline.created_at.to_date
+ )
+ end
+ end
+ end
+
context 'when there is an existing daily code coverage for the matching date, project, ref_path, and group name' do
let!(:new_pipeline) do
create(