summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-10-16 09:07:51 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-10-16 09:07:51 +0000
commit914ea32e0efca21436220df2c10e1bfbe4ed3da9 (patch)
treee8eb3b97aea2006bd863c586b7ec41d51f654b3b
parent3546e1bb0971347e9e9984de0799e3fb53743b33 (diff)
downloadgitlab-ce-914ea32e0efca21436220df2c10e1bfbe4ed3da9.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--Dangerfile1
-rw-r--r--app/assets/javascripts/issuable_sidebar/components/sidebar_app.vue23
-rw-r--r--app/assets/javascripts/issuable_sidebar/sidebar_bundle.js27
-rw-r--r--app/assets/javascripts/jobs/components/log/duration_badge.vue2
-rw-r--r--app/assets/javascripts/jobs/components/log/line.vue10
-rw-r--r--app/assets/javascripts/jobs/components/log/line_header.vue12
-rw-r--r--app/assets/javascripts/jobs/components/log/line_number.vue2
-rw-r--r--app/assets/javascripts/jobs/store/mutations.js2
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue2
-rw-r--r--app/assets/javascripts/pages/projects/issues/show.js7
-rw-r--r--app/assets/javascripts/pages/projects/issues/show/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js7
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/show/index.js4
-rw-r--r--app/assets/stylesheets/framework/common.scss1
-rw-r--r--app/assets/stylesheets/framework/job_log.scss1
-rw-r--r--app/controllers/groups_controller.rb3
-rw-r--r--app/controllers/projects/issues_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests_controller.rb4
-rw-r--r--app/controllers/projects/pipelines_controller.rb2
-rw-r--r--app/models/ci/build.rb1
-rw-r--r--app/models/ci/pipeline.rb6
-rw-r--r--app/models/ci/pipeline_enums.rb1
-rw-r--r--app/models/ci/sources/pipeline.rb25
-rw-r--r--app/models/group.rb4
-rw-r--r--app/models/project.rb3
-rw-r--r--app/serializers/pipeline_details_entity.rb5
-rw-r--r--app/serializers/pipeline_serializer.rb6
-rw-r--r--app/serializers/triggered_pipeline_entity.rb65
-rw-r--r--app/services/ci/pipeline_trigger_service.rb29
-rw-r--r--app/services/groups/transfer_service.rb8
-rw-r--r--app/services/groups/update_service.rb16
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml330
-rw-r--r--changelogs/unreleased/28243-check-for-docker-images-before-renaming-group.yml6
-rw-r--r--changelogs/unreleased/31573-cross-project-pipeline-triggering-does-not-work-in-core.yml5
-rw-r--r--lib/gitlab/daemon.rb12
-rw-r--r--lib/gitlab/danger/request_helper.rb23
-rw-r--r--lib/gitlab/danger/roulette.rb33
-rw-r--r--lib/gitlab/danger/teammate.rb14
-rw-r--r--lib/gitlab/gitaly_client/storage_service.rb0
-rw-r--r--lib/gitlab/metrics/exporter/base_exporter.rb9
-rw-r--r--lib/gitlab/metrics/samplers/base_sampler.rb5
-rw-r--r--lib/gitlab/sidekiq_daemon/memory_killer.rb2
-rw-r--r--lib/gitlab/sidekiq_daemon/monitor.rb2
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/controllers/groups_controller_spec.rb45
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb187
-rw-r--r--spec/dependencies/omniauth_saml_spec.rb2
-rw-r--r--spec/factories/ci/sources/pipelines.rb15
-rw-r--r--spec/features/projects/jobs_spec.rb6
-rw-r--r--spec/finders/access_requests_finder_spec.rb2
-rw-r--r--spec/finders/admin/projects_finder_spec.rb2
-rw-r--r--spec/finders/autocomplete/move_to_project_finder_spec.rb2
-rw-r--r--spec/finders/autocomplete/users_finder_spec.rb2
-rw-r--r--spec/finders/branches_finder_spec.rb2
-rw-r--r--spec/finders/clusters_finder_spec.rb2
-rw-r--r--spec/finders/concerns/finder_methods_spec.rb2
-rw-r--r--spec/finders/concerns/finder_with_cross_project_access_spec.rb2
-rw-r--r--spec/finders/contributed_projects_finder_spec.rb2
-rw-r--r--spec/finders/environments_finder_spec.rb2
-rw-r--r--spec/finders/events_finder_spec.rb2
-rw-r--r--spec/finders/fork_projects_finder_spec.rb2
-rw-r--r--spec/finders/group_descendants_finder_spec.rb2
-rw-r--r--spec/finders/group_members_finder_spec.rb2
-rw-r--r--spec/finders/group_projects_finder_spec.rb2
-rw-r--r--spec/finders/groups_finder_spec.rb2
-rw-r--r--spec/finders/issues_finder_spec.rb2
-rw-r--r--spec/finders/joined_groups_finder_spec.rb2
-rw-r--r--spec/finders/labels_finder_spec.rb2
-rw-r--r--spec/finders/license_template_finder_spec.rb2
-rw-r--r--spec/finders/members_finder_spec.rb2
-rw-r--r--spec/finders/merge_request_target_project_finder_spec.rb2
-rw-r--r--spec/finders/merge_requests_finder_spec.rb2
-rw-r--r--spec/finders/milestones_finder_spec.rb2
-rw-r--r--spec/finders/notes_finder_spec.rb2
-rw-r--r--spec/finders/personal_access_tokens_finder_spec.rb2
-rw-r--r--spec/finders/personal_projects_finder_spec.rb2
-rw-r--r--spec/finders/pipeline_schedules_finder_spec.rb2
-rw-r--r--spec/finders/pipelines_finder_spec.rb2
-rw-r--r--spec/finders/projects_finder_spec.rb2
-rw-r--r--spec/finders/runner_jobs_finder_spec.rb2
-rw-r--r--spec/finders/snippets_finder_spec.rb2
-rw-r--r--spec/finders/tags_finder_spec.rb2
-rw-r--r--spec/finders/template_finder_spec.rb2
-rw-r--r--spec/finders/todos_finder_spec.rb2
-rw-r--r--spec/finders/user_recent_events_finder_spec.rb2
-rw-r--r--spec/finders/users_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/daemon_spec.rb26
-rw-r--r--spec/lib/gitlab/danger/teammate_spec.rb66
-rw-r--r--spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb8
-rw-r--r--spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb8
-rw-r--r--spec/models/ci/build_spec.rb9
-rw-r--r--spec/models/ci/pipeline_spec.rb6
-rw-r--r--spec/models/ci/sources/pipeline_spec.rb19
-rw-r--r--spec/models/project_spec.rb2
-rw-r--r--spec/serializers/pipeline_details_entity_spec.rb35
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb5
-rw-r--r--spec/services/ci/pipeline_trigger_service_spec.rb162
-rw-r--r--spec/services/groups/transfer_service_spec.rb17
-rw-r--r--spec/services/groups/update_service_spec.rb24
-rw-r--r--spec/spec_helper.rb11
100 files changed, 1177 insertions, 284 deletions
diff --git a/Dangerfile b/Dangerfile
index 228190cd530..b65a9074078 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative 'lib/gitlab_danger'
+require_relative 'lib/gitlab/danger/request_helper'
danger.import_plugin('danger/plugins/helper.rb')
danger.import_plugin('danger/plugins/roulette.rb')
diff --git a/app/assets/javascripts/issuable_sidebar/components/sidebar_app.vue b/app/assets/javascripts/issuable_sidebar/components/sidebar_app.vue
new file mode 100644
index 00000000000..06c50f62aab
--- /dev/null
+++ b/app/assets/javascripts/issuable_sidebar/components/sidebar_app.vue
@@ -0,0 +1,23 @@
+<script>
+export default {
+ props: {
+ signedIn: {
+ type: Boolean,
+ required: true,
+ },
+ sidebarStatusClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+};
+</script>
+
+<template>
+ <aside
+ :class="sidebarStatusClass"
+ class="right-sidebar js-right-sidebar js-issuable-sidebar"
+ aria-live="polite"
+ ></aside>
+</template>
diff --git a/app/assets/javascripts/issuable_sidebar/sidebar_bundle.js b/app/assets/javascripts/issuable_sidebar/sidebar_bundle.js
new file mode 100644
index 00000000000..c8acafa8cd8
--- /dev/null
+++ b/app/assets/javascripts/issuable_sidebar/sidebar_bundle.js
@@ -0,0 +1,27 @@
+import Vue from 'vue';
+
+import SidebarApp from './components/sidebar_app.vue';
+
+export default () => {
+ const el = document.getElementById('js-vue-issuable-sidebar');
+
+ if (!el) {
+ return false;
+ }
+
+ const { sidebarStatusClass } = el.dataset;
+ // An empty string is present when user is signed in.
+ const signedIn = el.dataset.signedIn === '';
+
+ return new Vue({
+ el,
+ components: { SidebarApp },
+ render: createElement =>
+ createElement('sidebar-app', {
+ props: {
+ signedIn,
+ sidebarStatusClass,
+ },
+ }),
+ });
+};
diff --git a/app/assets/javascripts/jobs/components/log/duration_badge.vue b/app/assets/javascripts/jobs/components/log/duration_badge.vue
index 31a101d2c95..8e5dcdcc902 100644
--- a/app/assets/javascripts/jobs/components/log/duration_badge.vue
+++ b/app/assets/javascripts/jobs/components/log/duration_badge.vue
@@ -9,7 +9,7 @@ export default {
};
</script>
<template>
- <div class="log-duration-badge rounded align-self-start px-2 ml-2 flex-shrink-0">
+ <div class="log-duration-badge rounded align-self-start px-2 ml-2 flex-shrink-0 ws-normal">
{{ duration }}
</div>
</template>
diff --git a/app/assets/javascripts/jobs/components/log/line.vue b/app/assets/javascripts/jobs/components/log/line.vue
index 9fae541125e..33ee84bd4ee 100644
--- a/app/assets/javascripts/jobs/components/log/line.vue
+++ b/app/assets/javascripts/jobs/components/log/line.vue
@@ -21,8 +21,12 @@ export default {
<template>
<div class="js-line log-line">
<line-number :line-number="line.lineNumber" :path="path" />
- <span v-for="(content, i) in line.content" :key="i" :class="content.style">{{
- content.text
- }}</span>
+ <span
+ v-for="(content, i) in line.content"
+ :key="i"
+ :class="content.style"
+ class="ws-pre-wrap"
+ >{{ content.text }}</span
+ >
</div>
</template>
diff --git a/app/assets/javascripts/jobs/components/log/line_header.vue b/app/assets/javascripts/jobs/components/log/line_header.vue
index 92cf3b3cf5f..85ccd5996b5 100644
--- a/app/assets/javascripts/jobs/components/log/line_header.vue
+++ b/app/assets/javascripts/jobs/components/log/line_header.vue
@@ -43,15 +43,19 @@ export default {
<template>
<div
- class="log-line collapsible-line d-flex justify-content-between"
+ class="log-line collapsible-line d-flex justify-content-between ws-normal"
role="button"
@click="handleOnClick"
>
<icon :name="iconName" class="arrow position-absolute" />
<line-number :line-number="line.lineNumber" :path="path" />
- <span v-for="(content, i) in line.content" :key="i" class="line-text" :class="content.style">{{
- content.text
- }}</span>
+ <span
+ v-for="(content, i) in line.content"
+ :key="i"
+ class="line-text w-100 ws-pre-wrap"
+ :class="content.style"
+ >{{ content.text }}</span
+ >
<duration-badge v-if="duration" :duration="duration" />
</div>
</template>
diff --git a/app/assets/javascripts/jobs/components/log/line_number.vue b/app/assets/javascripts/jobs/components/log/line_number.vue
index 08c4a7ed330..ae96c32874b 100644
--- a/app/assets/javascripts/jobs/components/log/line_number.vue
+++ b/app/assets/javascripts/jobs/components/log/line_number.vue
@@ -48,7 +48,7 @@ export default {
<template>
<gl-link
:id="lineNumberId"
- class="d-inline-block text-right line-number"
+ class="d-inline-block text-right line-number flex-shrink-0"
:href="buildLineNumber"
>{{ parsedLineNumber }}</gl-link
>
diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js
index 412ae146ca0..702f00888d0 100644
--- a/app/assets/javascripts/jobs/store/mutations.js
+++ b/app/assets/javascripts/jobs/store/mutations.js
@@ -19,7 +19,7 @@ export default {
state.isSidebarOpen = true;
},
- [types.RECEIVE_TRACE_SUCCESS](state, log) {
+ [types.RECEIVE_TRACE_SUCCESS](state, log = {}) {
if (log.state) {
state.traceState = log.state;
}
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 9ecb9324f8c..09afa16e283 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -416,7 +416,6 @@ export default {
<gl-button
v-if="showRearrangePanelsBtn"
:pressed="isRearrangingPanels"
- new-style
variant="default"
class="mr-2 mt-1 js-rearrange-button"
@click="toggleRearrangingPanels"
@@ -426,7 +425,6 @@ export default {
<gl-button
v-if="addingMetricsAvailable"
v-gl-modal="$options.addMetric.modalId"
- new-style
variant="outline-success"
class="mr-2 mt-1 js-add-metric-button"
>
diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js
index 0447d1f79fb..28a136a5fa5 100644
--- a/app/assets/javascripts/pages/projects/issues/show.js
+++ b/app/assets/javascripts/pages/projects/issues/show.js
@@ -5,6 +5,7 @@ import ZenMode from '~/zen_mode';
import '~/notes/index';
import initIssueableApp from '~/issue_show';
import initRelatedMergeRequestsApp from '~/related_merge_requests';
+import initVueIssuableSidebarApp from '~/issuable_sidebar/sidebar_bundle';
export default function() {
initIssueableApp();
@@ -12,5 +13,9 @@ export default function() {
new Issue(); // eslint-disable-line no-new
new ShortcutsIssuable(); // eslint-disable-line no-new
new ZenMode(); // eslint-disable-line no-new
- initIssuableSidebar();
+ if (gon.features && gon.features.vueIssuableSidebar) {
+ initVueIssuableSidebarApp();
+ } else {
+ initIssuableSidebar();
+ }
}
diff --git a/app/assets/javascripts/pages/projects/issues/show/index.js b/app/assets/javascripts/pages/projects/issues/show/index.js
index 7968dfd7a12..ce74a6de11f 100644
--- a/app/assets/javascripts/pages/projects/issues/show/index.js
+++ b/app/assets/javascripts/pages/projects/issues/show/index.js
@@ -3,5 +3,7 @@ import initShow from '../show';
document.addEventListener('DOMContentLoaded', () => {
initShow();
- initSidebarBundle();
+ if (gon.features && !gon.features.vueIssuableSidebar) {
+ initSidebarBundle();
+ }
});
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
index 7bfb83a2204..fa1de1f13cb 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
@@ -4,11 +4,16 @@ import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import { handleLocationHash } from '~/lib/utils/common_utils';
import howToMerge from '~/how_to_merge';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
+import initVueIssuableSidebarApp from '~/issuable_sidebar/sidebar_bundle';
import initWidget from '../../../vue_merge_request_widget';
export default function() {
new ZenMode(); // eslint-disable-line no-new
- initIssuableSidebar();
+ if (gon.features && gon.features.vueIssuableSidebar) {
+ initVueIssuableSidebarApp();
+ } else {
+ initIssuableSidebar();
+ }
initPipelines();
new ShortcutsIssuable(true); // eslint-disable-line no-new
handleLocationHash();
diff --git a/app/assets/javascripts/pages/projects/merge_requests/show/index.js b/app/assets/javascripts/pages/projects/merge_requests/show/index.js
index f61f4db78d5..ddc648702f1 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/show/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/show/index.js
@@ -4,6 +4,8 @@ import initShow from '../init_merge_request_show';
document.addEventListener('DOMContentLoaded', () => {
initShow();
- initSidebarBundle();
+ if (gon.features && !gon.features.vueIssuableSidebar) {
+ initSidebarBundle();
+ }
initMrNotes();
});
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 16cb63fc0df..370fc84e492 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -440,6 +440,7 @@ img.emoji {
.flex-no-shrink { flex-shrink: 0; }
.ws-initial { white-space: initial; }
.ws-normal { white-space: normal; }
+.ws-pre-wrap { white-space: pre-wrap; }
.overflow-auto { overflow: auto; }
.d-flex-center {
diff --git a/app/assets/stylesheets/framework/job_log.scss b/app/assets/stylesheets/framework/job_log.scss
index f26c475c3c1..4a57a458c50 100644
--- a/app/assets/stylesheets/framework/job_log.scss
+++ b/app/assets/stylesheets/framework/job_log.scss
@@ -9,7 +9,6 @@
border-radius: $border-radius-small;
min-height: 42px;
background-color: $builds-trace-bg;
- white-space: pre-wrap;
}
.log-line {
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 3204e1e388b..35e364abba3 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -104,7 +104,6 @@ class GroupsController < Groups::ApplicationController
redirect_to edit_group_path(@group, anchor: params[:update_section]), notice: "Group '#{@group.name}' was successfully updated."
else
@group.path = @group.path_before_last_save || @group.path_was
-
render action: "edit"
end
end
@@ -124,7 +123,7 @@ class GroupsController < Groups::ApplicationController
flash[:notice] = "Group '#{@group.name}' was successfully transferred."
redirect_to group_path(@group)
else
- flash[:alert] = service.error
+ flash[:alert] = service.error.html_safe
redirect_to edit_group_path(@group)
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 7a192a9ec2d..96cb400950b 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -42,6 +42,10 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_import_issues!, only: [:import_csv]
before_action :authorize_download_code!, only: [:related_branches]
+ before_action do
+ push_frontend_feature_flag(:vue_issuable_sidebar, project.group)
+ end
+
respond_to :html
alias_method :designs, :show
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index bd22226da5c..ff199e05e99 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -21,6 +21,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:diffs_batch_load, @project)
end
+ before_action do
+ push_frontend_feature_flag(:vue_issuable_sidebar, @project.group)
+ end
+
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions]
def index
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 1bacdc0b1b2..106ef1b72c1 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -184,7 +184,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def show_represent_params
- { grouped: true }
+ { grouped: true, expanded: params[:expanded].to_a.map(&:to_i) }
end
def create_params
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 3dacd6a6224..b016aa8e477 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -42,6 +42,7 @@ module Ci
has_many :job_artifacts, class_name: 'Ci::JobArtifact', foreign_key: :job_id, dependent: :destroy, inverse_of: :job # rubocop:disable Cop/ActiveRecordDependent
has_many :job_variables, class_name: 'Ci::JobVariable', foreign_key: :job_id
+ has_many :sourced_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :source_job_id
Ci::JobArtifact.file_types.each do |key, value|
has_one :"job_artifacts_#{key}", -> { where(file_type: value) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index b34fd3f1ec9..5e52062ef40 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -52,9 +52,15 @@ module Ci
has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id'
has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id'
+ has_many :sourced_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :source_pipeline_id
+ has_one :source_pipeline, class_name: 'Ci::Sources::Pipeline', inverse_of: :pipeline
has_one :chat_data, class_name: 'Ci::PipelineChatData'
+ has_many :triggered_pipelines, through: :sourced_pipelines, source: :pipeline
+ has_one :triggered_by_pipeline, through: :source_pipeline, source: :source_pipeline
+ has_one :source_job, through: :source_pipeline, source: :source_job
+
accepts_nested_attributes_for :variables, reject_if: :persisted?
delegate :id, to: :project, prefix: true
diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb
index cb92aef4bda..859abc4a0d5 100644
--- a/app/models/ci/pipeline_enums.rb
+++ b/app/models/ci/pipeline_enums.rb
@@ -22,6 +22,7 @@ module Ci
schedule: 4,
api: 5,
external: 6,
+ pipeline: 7,
chat: 8,
merge_request_event: 10,
external_pull_request_event: 11
diff --git a/app/models/ci/sources/pipeline.rb b/app/models/ci/sources/pipeline.rb
new file mode 100644
index 00000000000..feaec27281c
--- /dev/null
+++ b/app/models/ci/sources/pipeline.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Ci
+ module Sources
+ class Pipeline < ApplicationRecord
+ self.table_name = "ci_sources_pipelines"
+
+ belongs_to :project, class_name: "Project"
+ belongs_to :pipeline, class_name: "Ci::Pipeline", inverse_of: :source_pipeline
+
+ belongs_to :source_project, class_name: "Project", foreign_key: :source_project_id
+ belongs_to :source_job, class_name: "CommitStatus", foreign_key: :source_job_id
+ belongs_to :source_pipeline, class_name: "Ci::Pipeline", foreign_key: :source_pipeline_id
+
+ validates :project, presence: true
+ validates :pipeline, presence: true
+
+ validates :source_project, presence: true
+ validates :source_job, presence: true
+ validates :source_pipeline, presence: true
+ end
+ end
+end
+
+::Ci::Sources::Pipeline.prepend_if_ee('::EE::Ci::Sources::Pipeline')
diff --git a/app/models/group.rb b/app/models/group.rb
index 0501fe94440..8b21206fccf 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -259,6 +259,10 @@ class Group < Namespace
members_with_parents.maintainers.exists?(user_id: user)
end
+ def has_container_repositories?
+ container_repositories.exists?
+ end
+
# @deprecated
alias_method :has_master?, :has_maintainer?
diff --git a/app/models/project.rb b/app/models/project.rb
index d7e3dc676ca..4d518862146 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -297,6 +297,9 @@ class Project < ApplicationRecord
has_many :external_pull_requests, inverse_of: :project
+ has_many :sourced_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :source_project_id
+ has_many :source_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :project_id
+
has_one :pages_metadatum, class_name: 'ProjectPagesMetadatum', inverse_of: :project
accepts_nested_attributes_for :variables, allow_destroy: true
diff --git a/app/serializers/pipeline_details_entity.rb b/app/serializers/pipeline_details_entity.rb
index 808e87c3fcf..71589ac8315 100644
--- a/app/serializers/pipeline_details_entity.rb
+++ b/app/serializers/pipeline_details_entity.rb
@@ -10,6 +10,7 @@ class PipelineDetailsEntity < PipelineEntity
expose :manual_actions, using: BuildActionEntity
expose :scheduled_actions, using: BuildActionEntity
end
-end
-PipelineDetailsEntity.prepend_if_ee('EE::PipelineDetailsEntity')
+ expose :triggered_by_pipeline, as: :triggered_by, with: TriggeredPipelineEntity
+ expose :triggered_pipelines, as: :triggered, using: TriggeredPipelineEntity
+end
diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb
index eaaeaf040a2..fc3160e3c69 100644
--- a/app/serializers/pipeline_serializer.rb
+++ b/app/serializers/pipeline_serializer.rb
@@ -54,9 +54,9 @@ class PipelineSerializer < BaseSerializer
artifacts: {
project: [:route, { namespace: :route }]
}
- }
+ },
+ { triggered_by_pipeline: [:project, :user] },
+ { triggered_pipelines: [:project, :user] }
]
end
end
-
-PipelineSerializer.prepend_if_ee('EE::PipelineSerializer')
diff --git a/app/serializers/triggered_pipeline_entity.rb b/app/serializers/triggered_pipeline_entity.rb
new file mode 100644
index 00000000000..fd7e4454abf
--- /dev/null
+++ b/app/serializers/triggered_pipeline_entity.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+class TriggeredPipelineEntity < Grape::Entity
+ include RequestAwareEntity
+
+ MAX_EXPAND_DEPTH = 3
+
+ expose :id
+ expose :user, using: UserEntity
+ expose :active?, as: :active
+ expose :coverage
+ expose :source
+
+ expose :path do |pipeline|
+ project_pipeline_path(pipeline.project, pipeline)
+ end
+
+ expose :details do
+ expose :detailed_status, as: :status, with: DetailedStatusEntity
+
+ expose :ordered_stages,
+ as: :stages, using: StageEntity,
+ if: -> (_, opts) { can_read_details? && expand?(opts) }
+ end
+
+ expose :triggered_by_pipeline,
+ as: :triggered_by, with: TriggeredPipelineEntity,
+ if: -> (_, opts) { can_read_details? && expand_for_path?(opts) }
+
+ expose :triggered_pipelines,
+ as: :triggered, using: TriggeredPipelineEntity,
+ if: -> (_, opts) { can_read_details? && expand_for_path?(opts) }
+
+ expose :project, using: ProjectEntity
+
+ private
+
+ alias_method :pipeline, :object
+
+ def can_read_details?
+ can?(request.current_user, :read_pipeline, pipeline)
+ end
+
+ def detailed_status
+ pipeline.detailed_status(request.current_user)
+ end
+
+ def expand?(opts)
+ opts[:expanded].to_a.include?(pipeline.id)
+ end
+
+ def expand_for_path?(opts)
+ # The `opts[:attr_path]` holds a list of all `exposes` in path
+ # The check ensures that we always expand only `triggered_by`, `triggered_by`, ...
+ # but not the `triggered_by`, `triggered` which would result in dead loop
+ attr_path = opts[:attr_path]
+ current_expose = attr_path.last
+
+ # We expand at most to depth of MAX_DEPTH
+ # We ensure that we expand in one direction: triggered_by,... or triggered, ...
+ attr_path.length < MAX_EXPAND_DEPTH &&
+ attr_path.all?(current_expose) &&
+ expand?(opts)
+ end
+end
diff --git a/app/services/ci/pipeline_trigger_service.rb b/app/services/ci/pipeline_trigger_service.rb
index 0e99f142492..37b9b4c362c 100644
--- a/app/services/ci/pipeline_trigger_service.rb
+++ b/app/services/ci/pipeline_trigger_service.rb
@@ -38,11 +38,34 @@ module Ci
end
def create_pipeline_from_job(job)
- # overridden in EE
+ # this check is to not leak the presence of the project if user cannot read it
+ return unless can?(job.user, :read_project, project)
+
+ return error("400 Job has to be running", 400) unless job.running?
+
+ pipeline = Ci::CreatePipelineService.new(project, job.user, ref: params[:ref])
+ .execute(:pipeline, ignore_skip_ci: true) do |pipeline|
+ source = job.sourced_pipelines.build(
+ source_pipeline: job.pipeline,
+ source_project: job.project,
+ pipeline: pipeline,
+ project: project)
+
+ pipeline.source_pipeline = source
+ pipeline.variables.build(variables)
+ end
+
+ if pipeline.persisted?
+ success(pipeline: pipeline)
+ else
+ error(pipeline.errors.messages, 400)
+ end
end
def job_from_token
- # overridden in EE
+ strong_memoize(:job) do
+ Ci::Build.find_by_token(params[:token].to_s)
+ end
end
def variables
@@ -52,5 +75,3 @@ module Ci
end
end
end
-
-Ci::PipelineTriggerService.prepend_if_ee('EE::Ci::PipelineTriggerService')
diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb
index fe7e07ef9f0..6902b7bd529 100644
--- a/app/services/groups/transfer_service.rb
+++ b/app/services/groups/transfer_service.rb
@@ -7,7 +7,8 @@ module Groups
namespace_with_same_path: s_('TransferGroup|The parent group already has a subgroup with the same path.'),
group_is_already_root: s_('TransferGroup|Group is already a root group.'),
same_parent_as_current: s_('TransferGroup|Group is already associated to the parent group.'),
- invalid_policies: s_("TransferGroup|You don't have enough permissions.")
+ invalid_policies: s_("TransferGroup|You don't have enough permissions."),
+ group_contains_images: s_('TransferGroup|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again.')
}.freeze
TransferError = Class.new(StandardError)
@@ -46,6 +47,7 @@ module Groups
raise_transfer_error(:same_parent_as_current) if same_parent?
raise_transfer_error(:invalid_policies) unless valid_policies?
raise_transfer_error(:namespace_with_same_path) if namespace_with_same_path?
+ raise_transfer_error(:group_contains_images) if group_projects_contain_registry_images?
end
def group_is_already_root?
@@ -72,6 +74,10 @@ module Groups
end
# rubocop: enable CodeReuse/ActiveRecord
+ def group_projects_contain_registry_images?
+ @group.has_container_repositories?
+ end
+
def update_group_attributes
if @new_parent_group && @new_parent_group.visibility_level < @group.visibility_level
update_children_and_projects_visibility
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index 534de601e20..be7502a193e 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -8,6 +8,11 @@ module Groups
reject_parent_id!
remove_unallowed_params
+ if renaming_group_with_container_registry_images?
+ group.errors.add(:base, container_images_error)
+ return false
+ end
+
return false unless valid_visibility_level_change?(group, params[:visibility_level])
return false unless valid_share_with_group_lock_change?
@@ -35,6 +40,17 @@ module Groups
# overridden in EE
end
+ def renaming_group_with_container_registry_images?
+ new_path = params[:path]
+
+ new_path && new_path != group.path &&
+ group.has_container_repositories?
+ end
+
+ def container_images_error
+ s_("GroupSettings|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again.")
+ end
+
def after_update
if group.previous_changes.include?(:visibility_level) && group.private?
# don't enqueue immediately to prevent todos removal in case of a mistake
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index a55b7fc530a..c8b2adcf084 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -5,174 +5,178 @@
- signed_in = !!issuable_sidebar.dig(:current_user, :id)
- can_edit_issuable = issuable_sidebar.dig(:current_user, :can_edit)
-%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: signed_in } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
- .issuable-sidebar
- .block.issuable-sidebar-header
- - if signed_in
- %span.issuable-header-text.hide-collapsed.float-left
- = _('To Do')
- %a.gutter-toggle.float-right.js-sidebar-toggle.has-tooltip{ role: "button", href: "#", "aria-label" => "Toggle sidebar", title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } }
- = sidebar_gutter_toggle_icon
- - if signed_in
- = render "shared/issuable/sidebar_todo", issuable_sidebar: issuable_sidebar
-
- = form_for issuable_type, url: issuable_sidebar[:issuable_json_path], remote: true, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f|
- - if signed_in
- .block.todo.hide-expanded
- = render "shared/issuable/sidebar_todo", issuable_sidebar: issuable_sidebar, is_collapsed: true
- .block.assignee.qa-assignee-block
- = render "shared/issuable/sidebar_assignees", issuable_sidebar: issuable_sidebar, assignees: assignees
-
- = render_if_exists 'shared/issuable/sidebar_item_epic', issuable_sidebar: issuable_sidebar
-
- - milestone = issuable_sidebar[:milestone] || {}
- .block.milestone
- .sidebar-collapsed-icon.has-tooltip{ title: sidebar_milestone_tooltip_label(milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } }
- = icon('clock-o', 'aria-hidden': 'true')
- %span.milestone-title.collapse-truncated-title
- - if milestone.present?
- = milestone[:title]
- - else
- = _('None')
- .title.hide-collapsed
- = _('Milestone')
- = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- - if can_edit_issuable
- = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { track_label: "right_sidebar", track_property: "milestone", track_event: "click_edit_button", track_value: "" }
- .value.hide-collapsed
- - if milestone.present?
- = link_to milestone[:title], milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport', qa_selector: 'milestone_link' }
- - else
- %span.no-value
- = _('None')
-
- .selectbox.hide-collapsed
- = f.hidden_field 'milestone_id', value: milestone[:id], id: nil
- = dropdown_tag('Milestone', options: { title: _('Assign milestone'), toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: _('Search milestones'), data: { show_no: true, field_name: "#{issuable_type}[milestone_id]", project_id: issuable_sidebar[:project_id], issuable_id: issuable_sidebar[:id], milestones: issuable_sidebar[:project_milestones_path], ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], use_id: true, default_no: true, selected: milestone[:title], null_default: true, display: 'static' }})
-
- #issuable-time-tracker.block
- // Fallback while content is loading
- .title.hide-collapsed
- = _('Time tracking')
- = icon('spinner spin', 'aria-hidden': 'true')
-
- - if issuable_sidebar.has_key?(:due_date)
- .block.due_date
- .sidebar-collapsed-icon.has-tooltip{ data: { placement: 'left', container: 'body', html: 'true', boundary: 'viewport' }, title: sidebar_due_date_tooltip_label(issuable_sidebar[:due_date]) }
- = icon('calendar', 'aria-hidden': 'true')
- %span.js-due-date-sidebar-value
- = issuable_sidebar[:due_date].try(:to_s, :medium) || 'None'
+- if Feature.enabled?(:vue_issuable_sidebar, @project.group)
+ %aside#js-vue-issuable-sidebar{ data: { signed_in: signed_in,
+ sidebar_status_class: sidebar_gutter_collapsed_class } }
+- else
+ %aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: signed_in } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
+ .issuable-sidebar
+ .block.issuable-sidebar-header
+ - if signed_in
+ %span.issuable-header-text.hide-collapsed.float-left
+ = _('To Do')
+ %a.gutter-toggle.float-right.js-sidebar-toggle.has-tooltip{ role: "button", href: "#", "aria-label" => "Toggle sidebar", title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } }
+ = sidebar_gutter_toggle_icon
+ - if signed_in
+ = render "shared/issuable/sidebar_todo", issuable_sidebar: issuable_sidebar
+
+ = form_for issuable_type, url: issuable_sidebar[:issuable_json_path], remote: true, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f|
+ - if signed_in
+ .block.todo.hide-expanded
+ = render "shared/issuable/sidebar_todo", issuable_sidebar: issuable_sidebar, is_collapsed: true
+ .block.assignee.qa-assignee-block
+ = render "shared/issuable/sidebar_assignees", issuable_sidebar: issuable_sidebar, assignees: assignees
+
+ = render_if_exists 'shared/issuable/sidebar_item_epic', issuable_sidebar: issuable_sidebar
+
+ - milestone = issuable_sidebar[:milestone] || {}
+ .block.milestone
+ .sidebar-collapsed-icon.has-tooltip{ title: sidebar_milestone_tooltip_label(milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } }
+ = icon('clock-o', 'aria-hidden': 'true')
+ %span.milestone-title.collapse-truncated-title
+ - if milestone.present?
+ = milestone[:title]
+ - else
+ = _('None')
.title.hide-collapsed
- = _('Due date')
+ = _('Milestone')
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
- = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { track_label: "right_sidebar", track_property: "due_date", track_event: "click_edit_button", track_value: "" }
+ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { track_label: "right_sidebar", track_property: "milestone", track_event: "click_edit_button", track_value: "" }
.value.hide-collapsed
- %span.value-content
- - if issuable_sidebar[:due_date]
- %span.bold= issuable_sidebar[:due_date].to_s(:medium)
- - else
- %span.no-value
- = _('None')
+ - if milestone.present?
+ = link_to milestone[:title], milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport', qa_selector: 'milestone_link' }
+ - else
+ %span.no-value
+ = _('None')
+
+ .selectbox.hide-collapsed
+ = f.hidden_field 'milestone_id', value: milestone[:id], id: nil
+ = dropdown_tag('Milestone', options: { title: _('Assign milestone'), toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: _('Search milestones'), data: { show_no: true, field_name: "#{issuable_type}[milestone_id]", project_id: issuable_sidebar[:project_id], issuable_id: issuable_sidebar[:id], milestones: issuable_sidebar[:project_milestones_path], ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], use_id: true, default_no: true, selected: milestone[:title], null_default: true, display: 'static' }})
+
+ #issuable-time-tracker.block
+ // Fallback while content is loading
+ .title.hide-collapsed
+ = _('Time tracking')
+ = icon('spinner spin', 'aria-hidden': 'true')
+
+ - if issuable_sidebar.has_key?(:due_date)
+ .block.due_date
+ .sidebar-collapsed-icon.has-tooltip{ data: { placement: 'left', container: 'body', html: 'true', boundary: 'viewport' }, title: sidebar_due_date_tooltip_label(issuable_sidebar[:due_date]) }
+ = icon('calendar', 'aria-hidden': 'true')
+ %span.js-due-date-sidebar-value
+ = issuable_sidebar[:due_date].try(:to_s, :medium) || 'None'
+ .title.hide-collapsed
+ = _('Due date')
+ = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
+ - if can_edit_issuable
+ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { track_label: "right_sidebar", track_property: "due_date", track_event: "click_edit_button", track_value: "" }
+ .value.hide-collapsed
+ %span.value-content
+ - if issuable_sidebar[:due_date]
+ %span.bold= issuable_sidebar[:due_date].to_s(:medium)
+ - else
+ %span.no-value
+ = _('None')
+ - if can_edit_issuable
+ %span.no-value.js-remove-due-date-holder{ class: ("hidden" if issuable_sidebar[:due_date].nil?) }
+ \-
+ %a.js-remove-due-date{ href: "#", role: "button" }
+ = _('remove due date')
- if can_edit_issuable
- %span.no-value.js-remove-due-date-holder{ class: ("hidden" if issuable_sidebar[:due_date].nil?) }
- \-
- %a.js-remove-due-date{ href: "#", role: "button" }
- = _('remove due date')
- - if can_edit_issuable
- .selectbox.hide-collapsed
- = f.hidden_field :due_date, value: issuable_sidebar[:due_date].try(:strftime, 'yy-mm-dd')
- .dropdown
- %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable_type}[due_date]", ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], display: 'static' } }
- %span.dropdown-toggle-text
- = _('Due date')
- = icon('chevron-down', 'aria-hidden': 'true')
- .dropdown-menu.dropdown-menu-due-date
- = dropdown_title(_('Due date'))
- = dropdown_content do
- .js-due-date-calendar
-
- - selected_labels = issuable_sidebar[:labels]
- .block.labels
- .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(selected_labels), data: { placement: "left", container: "body", boundary: 'viewport' } }
- = icon('tags', 'aria-hidden': 'true')
- %span
- = selected_labels.size
- .title.hide-collapsed
- = _('Labels')
- = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- - if can_edit_issuable
- = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link qa-edit-link-labels float-right', data: { track_label: "right_sidebar", track_property: "labels", track_event: "click_edit_button", track_value: "" }
- .value.issuable-show-labels.dont-hide.hide-collapsed.qa-labels-block{ class: ("has-labels" if selected_labels.any?) }
- - if selected_labels.any?
- - selected_labels.each do |label_hash|
- = render_label(label_from_hash(label_hash).present(issuable_subject: nil), link: sidebar_label_filter_path(issuable_sidebar[:project_issuables_path], label_hash[:title]))
- - else
- %span.no-value
- = _('None')
- .selectbox.hide-collapsed
- - selected_labels.each do |label|
- = hidden_field_tag "#{issuable_type}[label_names][]", label[:id], id: nil
- .dropdown
- %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: sidebar_label_dropdown_data(issuable_type, issuable_sidebar) }
- %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
- = multi_label_name(selected_labels, "Labels")
- = icon('chevron-down', 'aria-hidden': 'true')
- .dropdown-menu.dropdown-select.dropdown-menu-paging.qa-dropdown-menu-labels.dropdown-menu-labels.dropdown-menu-selectable.dropdown-extended-height
- = render partial: "shared/issuable/label_page_default"
- - if issuable_sidebar.dig(:current_user, :can_admin_label)
- = render partial: "shared/issuable/label_page_create"
-
- = render_if_exists 'shared/issuable/sidebar_weight', issuable_sidebar: issuable_sidebar
-
- - if issuable_sidebar.has_key?(:confidential)
+ .selectbox.hide-collapsed
+ = f.hidden_field :due_date, value: issuable_sidebar[:due_date].try(:strftime, 'yy-mm-dd')
+ .dropdown
+ %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable_type}[due_date]", ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], display: 'static' } }
+ %span.dropdown-toggle-text
+ = _('Due date')
+ = icon('chevron-down', 'aria-hidden': 'true')
+ .dropdown-menu.dropdown-menu-due-date
+ = dropdown_title(_('Due date'))
+ = dropdown_content do
+ .js-due-date-calendar
+
+ - selected_labels = issuable_sidebar[:labels]
+ .block.labels
+ .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(selected_labels), data: { placement: "left", container: "body", boundary: 'viewport' } }
+ = icon('tags', 'aria-hidden': 'true')
+ %span
+ = selected_labels.size
+ .title.hide-collapsed
+ = _('Labels')
+ = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
+ - if can_edit_issuable
+ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link qa-edit-link-labels float-right', data: { track_label: "right_sidebar", track_property: "labels", track_event: "click_edit_button", track_value: "" }
+ .value.issuable-show-labels.dont-hide.hide-collapsed.qa-labels-block{ class: ("has-labels" if selected_labels.any?) }
+ - if selected_labels.any?
+ - selected_labels.each do |label_hash|
+ = render_label(label_from_hash(label_hash).present(issuable_subject: nil), link: sidebar_label_filter_path(issuable_sidebar[:project_issuables_path], label_hash[:title]))
+ - else
+ %span.no-value
+ = _('None')
+ .selectbox.hide-collapsed
+ - selected_labels.each do |label|
+ = hidden_field_tag "#{issuable_type}[label_names][]", label[:id], id: nil
+ .dropdown
+ %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: sidebar_label_dropdown_data(issuable_type, issuable_sidebar) }
+ %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
+ = multi_label_name(selected_labels, "Labels")
+ = icon('chevron-down', 'aria-hidden': 'true')
+ .dropdown-menu.dropdown-select.dropdown-menu-paging.qa-dropdown-menu-labels.dropdown-menu-labels.dropdown-menu-selectable.dropdown-extended-height
+ = render partial: "shared/issuable/label_page_default"
+ - if issuable_sidebar.dig(:current_user, :can_admin_label)
+ = render partial: "shared/issuable/label_page_create"
+
+ = render_if_exists 'shared/issuable/sidebar_weight', issuable_sidebar: issuable_sidebar
+
+ - if issuable_sidebar.has_key?(:confidential)
+ -# haml-lint:disable InlineJavaScript
+ %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: issuable_sidebar[:confidential], is_editable: can_edit_issuable }.to_json.html_safe
+ #js-confidential-entry-point
+
-# haml-lint:disable InlineJavaScript
- %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: issuable_sidebar[:confidential], is_editable: can_edit_issuable }.to_json.html_safe
- #js-confidential-entry-point
+ %script#js-lock-issue-data{ type: "application/json" }= { is_locked: !!issuable_sidebar[:discussion_locked], is_editable: can_edit_issuable }.to_json.html_safe
+ #js-lock-entry-point
+
+ .js-sidebar-participants-entry-point
+
+ - if signed_in
+ - if issuable_sidebar[:project_emails_disabled]
+ .block.js-emails-disabled
+ .sidebar-collapsed-icon.has-tooltip{ title: notification_description(:owner_disabled), data: { placement: "left", container: "body", boundary: 'viewport' } }
+ = notification_setting_icon
+ .hide-collapsed= notification_description(:owner_disabled)
+ - else
+ .js-sidebar-subscriptions-entry-point
+
+ - project_ref = issuable_sidebar[:reference]
+ .block.project-reference
+ .sidebar-collapsed-icon.dont-change-state
+ = clipboard_button(text: project_ref, title: _('Copy reference'), placement: "left", boundary: 'viewport')
+ .cross-project-reference.hide-collapsed
+ %span
+ = _('Reference:')
+ %cite{ title: project_ref }
+ = project_ref
+ = clipboard_button(text: project_ref, title: _('Copy reference'), placement: "left", boundary: 'viewport')
+
+ - if issuable_sidebar.dig(:current_user, :can_move)
+ .block.js-sidebar-move-issue-block
+ .sidebar-collapsed-icon{ data: { toggle: 'tooltip', placement: 'left', container: 'body', boundary: 'viewport' }, title: _('Move issue') }
+ = custom_icon('icon_arrow_right')
+ .dropdown.sidebar-move-issue-dropdown.hide-collapsed
+ %button.btn.btn-default.btn-block.js-sidebar-dropdown-toggle.js-move-issue{ type: 'button',
+ data: { toggle: 'dropdown', display: 'static', track_label: "right_sidebar", track_property: "move_issue", track_event: "click_button", track_value: "" } }
+ = _('Move issue')
+ .dropdown-menu.dropdown-menu-selectable.dropdown-extended-height
+ = dropdown_title(_('Move issue'))
+ = dropdown_filter(_('Search project'), search_id: 'sidebar-move-issue-dropdown-search')
+ = dropdown_content
+ = dropdown_loading
+ = dropdown_footer add_content_class: true do
+ %button.btn.btn-success.sidebar-move-issue-confirmation-button.js-move-issue-confirmation-button{ type: 'button', disabled: true }
+ = _('Move')
+ = icon('spinner spin', class: 'sidebar-move-issue-confirmation-loading-icon')
-# haml-lint:disable InlineJavaScript
- %script#js-lock-issue-data{ type: "application/json" }= { is_locked: !!issuable_sidebar[:discussion_locked], is_editable: can_edit_issuable }.to_json.html_safe
- #js-lock-entry-point
-
- .js-sidebar-participants-entry-point
-
- - if signed_in
- - if issuable_sidebar[:project_emails_disabled]
- .block.js-emails-disabled
- .sidebar-collapsed-icon.has-tooltip{ title: notification_description(:owner_disabled), data: { placement: "left", container: "body", boundary: 'viewport' } }
- = notification_setting_icon
- .hide-collapsed= notification_description(:owner_disabled)
- - else
- .js-sidebar-subscriptions-entry-point
-
- - project_ref = issuable_sidebar[:reference]
- .block.project-reference
- .sidebar-collapsed-icon.dont-change-state
- = clipboard_button(text: project_ref, title: _('Copy reference'), placement: "left", boundary: 'viewport')
- .cross-project-reference.hide-collapsed
- %span
- = _('Reference:')
- %cite{ title: project_ref }
- = project_ref
- = clipboard_button(text: project_ref, title: _('Copy reference'), placement: "left", boundary: 'viewport')
-
- - if issuable_sidebar.dig(:current_user, :can_move)
- .block.js-sidebar-move-issue-block
- .sidebar-collapsed-icon{ data: { toggle: 'tooltip', placement: 'left', container: 'body', boundary: 'viewport' }, title: _('Move issue') }
- = custom_icon('icon_arrow_right')
- .dropdown.sidebar-move-issue-dropdown.hide-collapsed
- %button.btn.btn-default.btn-block.js-sidebar-dropdown-toggle.js-move-issue{ type: 'button',
- data: { toggle: 'dropdown', display: 'static', track_label: "right_sidebar", track_property: "move_issue", track_event: "click_button", track_value: "" } }
- = _('Move issue')
- .dropdown-menu.dropdown-menu-selectable.dropdown-extended-height
- = dropdown_title(_('Move issue'))
- = dropdown_filter(_('Search project'), search_id: 'sidebar-move-issue-dropdown-search')
- = dropdown_content
- = dropdown_loading
- = dropdown_footer add_content_class: true do
- %button.btn.btn-success.sidebar-move-issue-confirmation-button.js-move-issue-confirmation-button{ type: 'button', disabled: true }
- = _('Move')
- = icon('spinner spin', class: 'sidebar-move-issue-confirmation-loading-icon')
-
- -# haml-lint:disable InlineJavaScript
- %script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable_sidebar).to_json.html_safe
+ %script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable_sidebar).to_json.html_safe
diff --git a/changelogs/unreleased/28243-check-for-docker-images-before-renaming-group.yml b/changelogs/unreleased/28243-check-for-docker-images-before-renaming-group.yml
new file mode 100644
index 00000000000..8717d59b1bc
--- /dev/null
+++ b/changelogs/unreleased/28243-check-for-docker-images-before-renaming-group.yml
@@ -0,0 +1,6 @@
+---
+title: Prevents a group path change when a project inside the group has container
+ registry images
+merge_request: 17583
+author:
+type: fixed
diff --git a/changelogs/unreleased/31573-cross-project-pipeline-triggering-does-not-work-in-core.yml b/changelogs/unreleased/31573-cross-project-pipeline-triggering-does-not-work-in-core.yml
new file mode 100644
index 00000000000..1638746ea72
--- /dev/null
+++ b/changelogs/unreleased/31573-cross-project-pipeline-triggering-does-not-work-in-core.yml
@@ -0,0 +1,5 @@
+---
+title: Allow cross-project pipeline triggering with CI_JOB_TOKEN in core
+merge_request: 17251
+author:
+type: added
diff --git a/lib/gitlab/daemon.rb b/lib/gitlab/daemon.rb
index 43c159fee27..8a253893892 100644
--- a/lib/gitlab/daemon.rb
+++ b/lib/gitlab/daemon.rb
@@ -34,7 +34,9 @@ module Gitlab
@mutex.synchronize do
break thread if thread?
- @thread = Thread.new { start_working }
+ if start_working
+ @thread = Thread.new { run_thread }
+ end
end
end
@@ -57,10 +59,18 @@ module Gitlab
private
+ # Executed in lock context before starting thread
+ # Needs to return success
def start_working
+ true
+ end
+
+ # Executed in separate thread
+ def run_thread
raise NotImplementedError
end
+ # Executed in lock context
def stop_working
# no-ops
end
diff --git a/lib/gitlab/danger/request_helper.rb b/lib/gitlab/danger/request_helper.rb
new file mode 100644
index 00000000000..06da4ed9ad3
--- /dev/null
+++ b/lib/gitlab/danger/request_helper.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'net/http'
+require 'json'
+
+module Gitlab
+ module Danger
+ module RequestHelper
+ HTTPError = Class.new(RuntimeError)
+
+ # @param [String] url
+ def self.http_get_json(url)
+ rsp = Net::HTTP.get_response(URI.parse(url))
+
+ unless rsp.is_a?(Net::HTTPOK)
+ raise HTTPError, "Failed to read #{url}: #{rsp.code} #{rsp.message}"
+ end
+
+ JSON.parse(rsp.body)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/danger/roulette.rb b/lib/gitlab/danger/roulette.rb
index 25de0a87c9d..0700a72c918 100644
--- a/lib/gitlab/danger/roulette.rb
+++ b/lib/gitlab/danger/roulette.rb
@@ -1,16 +1,11 @@
# frozen_string_literal: true
-require 'net/http'
-require 'json'
-require 'cgi'
-
require_relative 'teammate'
module Gitlab
module Danger
module Roulette
ROULETTE_DATA_URL = 'https://about.gitlab.com/roulette.json'
- HTTPError = Class.new(RuntimeError)
# Looks up the current list of GitLab team members and parses it into a
# useful form
@@ -19,7 +14,7 @@ module Gitlab
def team
@team ||=
begin
- data = http_get_json(ROULETTE_DATA_URL)
+ data = Gitlab::Danger::RequestHelper.http_get_json(ROULETTE_DATA_URL)
data.map { |hash| ::Gitlab::Danger::Teammate.new(hash) }
rescue JSON::ParserError
raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
@@ -44,6 +39,7 @@ module Gitlab
# Known issue: If someone is rejected due to OOO, and then becomes not OOO, the
# selection will change on next spin
+ # @param [Array<Teammate>] people
def spin_for_person(people, random:)
people.shuffle(random: random)
.find(&method(:valid_person?))
@@ -51,32 +47,17 @@ module Gitlab
private
+ # @param [Teammate] person
+ # @return [Boolean]
def valid_person?(person)
- !mr_author?(person) && !out_of_office?(person)
+ !mr_author?(person) && !person.out_of_office?
end
+ # @param [Teammate] person
+ # @return [Boolean]
def mr_author?(person)
person.username == gitlab.mr_author
end
-
- def out_of_office?(person)
- username = CGI.escape(person.username)
- api_endpoint = "https://gitlab.com/api/v4/users/#{username}/status"
- response = http_get_json(api_endpoint)
- response["message"]&.match?(/OOO/i)
- rescue HTTPError, JSON::ParserError
- false # this is no worse than not checking for OOO
- end
-
- def http_get_json(url)
- rsp = Net::HTTP.get_response(URI.parse(url))
-
- unless rsp.is_a?(Net::HTTPSuccess)
- raise HTTPError, "Failed to read #{url}: #{rsp.code} #{rsp.message}"
- end
-
- JSON.parse(rsp.body)
- end
end
end
end
diff --git a/lib/gitlab/danger/teammate.rb b/lib/gitlab/danger/teammate.rb
index 4ad66f61c2b..c0a2d909f69 100644
--- a/lib/gitlab/danger/teammate.rb
+++ b/lib/gitlab/danger/teammate.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'cgi'
+
module Gitlab
module Danger
class Teammate
@@ -34,6 +36,18 @@ module Gitlab
has_capability?(project, category, :maintainer, labels)
end
+ def status
+ api_endpoint = "https://gitlab.com/api/v4/users/#{CGI.escape(username)}/status"
+ @status ||= Gitlab::Danger::RequestHelper.http_get_json(api_endpoint)
+ rescue Gitlab::Danger::RequestHelper::HTTPError, JSON::ParserError
+ nil # better no status than a crashing Danger
+ end
+
+ # @return [Boolean]
+ def out_of_office?
+ status&.dig("message")&.match?(/OOO/i) || false
+ end
+
private
def has_capability?(project, category, kind, labels)
diff --git a/lib/gitlab/gitaly_client/storage_service.rb b/lib/gitlab/gitaly_client/storage_service.rb
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/lib/gitlab/gitaly_client/storage_service.rb
+++ /dev/null
diff --git a/lib/gitlab/metrics/exporter/base_exporter.rb b/lib/gitlab/metrics/exporter/base_exporter.rb
index b56770e224b..3aac18f59c6 100644
--- a/lib/gitlab/metrics/exporter/base_exporter.rb
+++ b/lib/gitlab/metrics/exporter/base_exporter.rb
@@ -40,7 +40,14 @@ module Gitlab
::Gitlab::HealthChecks::Probes::Liveness.new, req, res)
end
server.mount '/', Rack::Handler::WEBrick, rack_app
- server.start
+
+ true
+ end
+
+ def run_thread
+ server&.start
+ rescue IOError
+ # ignore forcibily closed servers
end
def stop_working
diff --git a/lib/gitlab/metrics/samplers/base_sampler.rb b/lib/gitlab/metrics/samplers/base_sampler.rb
index d7d848d2833..90051f85f31 100644
--- a/lib/gitlab/metrics/samplers/base_sampler.rb
+++ b/lib/gitlab/metrics/samplers/base_sampler.rb
@@ -50,6 +50,11 @@ module Gitlab
def start_working
@running = true
+
+ true
+ end
+
+ def run_thread
sleep(sleep_interval)
while running
safe_sample
diff --git a/lib/gitlab/sidekiq_daemon/memory_killer.rb b/lib/gitlab/sidekiq_daemon/memory_killer.rb
index eb58435e3f1..804170169e8 100644
--- a/lib/gitlab/sidekiq_daemon/memory_killer.rb
+++ b/lib/gitlab/sidekiq_daemon/memory_killer.rb
@@ -29,7 +29,7 @@ module Gitlab
private
- def start_working
+ def run_thread
Sidekiq.logger.info(
class: self.class.to_s,
action: 'start',
diff --git a/lib/gitlab/sidekiq_daemon/monitor.rb b/lib/gitlab/sidekiq_daemon/monitor.rb
index 09f30837cd2..a3d61c69ae1 100644
--- a/lib/gitlab/sidekiq_daemon/monitor.rb
+++ b/lib/gitlab/sidekiq_daemon/monitor.rb
@@ -61,7 +61,7 @@ module Gitlab
private
- def start_working
+ def run_thread
return unless notification_channel_enabled?
begin
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 237b2b7cb0d..0741e0445a6 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8282,6 +8282,9 @@ msgstr ""
msgid "GroupSettings|Be careful. Changing a group's parent can have unintended %{side_effects_link_start}side effects%{side_effects_link_end}."
msgstr ""
+msgid "GroupSettings|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again."
+msgstr ""
+
msgid "GroupSettings|Change group path"
msgstr ""
@@ -17290,6 +17293,9 @@ msgstr ""
msgid "Transfer project"
msgstr ""
+msgid "TransferGroup|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again."
+msgstr ""
+
msgid "TransferGroup|Database is not supported."
msgstr ""
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index a35ef99ef12..3c39a6468e5 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -385,6 +385,29 @@ describe GroupsController do
expect(response).to have_gitlab_http_status(302)
expect(group.reload.project_creation_level).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
end
+
+ context 'when a project inside the group has container repositories' do
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_container_registry_tags(repository: /image/, tags: %w[rc1])
+ create(:container_repository, project: project, name: :image)
+ end
+
+ it 'does allow the group to be renamed' do
+ post :update, params: { id: group.to_param, group: { name: 'new_name' } }
+
+ expect(controller).to set_flash[:notice]
+ expect(response).to have_gitlab_http_status(302)
+ expect(group.reload.name).to eq('new_name')
+ end
+
+ it 'does not allow to path of the group to be changed' do
+ post :update, params: { id: group.to_param, group: { path: 'new_path' } }
+
+ expect(assigns(:group).errors[:base].first).to match(/Docker images in their Container Registry/)
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
end
describe '#ensure_canonical_path' do
@@ -673,6 +696,28 @@ describe GroupsController do
expect(response).to have_gitlab_http_status(404)
end
end
+
+ context 'transferring when a project has container images' do
+ let(:group) { create(:group, :public, :nested) }
+ let!(:group_member) { create(:group_member, :owner, group: group, user: user) }
+
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_container_registry_tags(repository: /image/, tags: %w[rc1])
+ create(:container_repository, project: project, name: :image)
+
+ put :transfer,
+ params: {
+ id: group.to_param,
+ new_parent_group_id: ''
+ }
+ end
+
+ it 'does not allow the group to be transferred' do
+ expect(controller).to set_flash[:alert].to match(/Docker images in their Container Registry/)
+ expect(response).to redirect_to(edit_group_path(group))
+ end
+ end
end
context 'token authentication' do
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index d964b0672a9..e3ad36f8d24 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -217,6 +217,193 @@ describe Projects::PipelinesController do
end
end
+ context 'with triggered pipelines' do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:source_project) { create(:project, :repository) }
+ let_it_be(:target_project) { create(:project, :repository) }
+ let_it_be(:root_pipeline) { create_pipeline(project) }
+ let_it_be(:source_pipeline) { create_pipeline(source_project) }
+ let_it_be(:source_of_source_pipeline) { create_pipeline(source_project) }
+ let_it_be(:target_pipeline) { create_pipeline(target_project) }
+ let_it_be(:target_of_target_pipeline) { create_pipeline(target_project) }
+
+ before do
+ create_link(source_of_source_pipeline, source_pipeline)
+ create_link(source_pipeline, root_pipeline)
+ create_link(root_pipeline, target_pipeline)
+ create_link(target_pipeline, target_of_target_pipeline)
+ end
+
+ shared_examples 'not expanded' do
+ let(:expected_stages) { be_nil }
+
+ it 'does return base details' do
+ get_pipeline_json(root_pipeline)
+
+ expect(json_response['triggered_by']).to include('id' => source_pipeline.id)
+ expect(json_response['triggered']).to contain_exactly(
+ include('id' => target_pipeline.id))
+ end
+
+ it 'does not expand triggered_by pipeline' do
+ get_pipeline_json(root_pipeline)
+
+ triggered_by = json_response['triggered_by']
+ expect(triggered_by['triggered_by']).to be_nil
+ expect(triggered_by['triggered']).to be_nil
+ expect(triggered_by['details']['stages']).to expected_stages
+ end
+
+ it 'does not expand triggered pipelines' do
+ get_pipeline_json(root_pipeline)
+
+ first_triggered = json_response['triggered'].first
+ expect(first_triggered['triggered_by']).to be_nil
+ expect(first_triggered['triggered']).to be_nil
+ expect(first_triggered['details']['stages']).to expected_stages
+ end
+ end
+
+ shared_examples 'expanded' do
+ it 'does return base details' do
+ get_pipeline_json(root_pipeline)
+
+ expect(json_response['triggered_by']).to include('id' => source_pipeline.id)
+ expect(json_response['triggered']).to contain_exactly(
+ include('id' => target_pipeline.id))
+ end
+
+ it 'does expand triggered_by pipeline' do
+ get_pipeline_json(root_pipeline)
+
+ triggered_by = json_response['triggered_by']
+ expect(triggered_by['triggered_by']).to include(
+ 'id' => source_of_source_pipeline.id)
+ expect(triggered_by['details']['stages']).not_to be_nil
+ end
+
+ it 'does not recursively expand triggered_by' do
+ get_pipeline_json(root_pipeline)
+
+ triggered_by = json_response['triggered_by']
+ expect(triggered_by['triggered']).to be_nil
+ end
+
+ it 'does expand triggered pipelines' do
+ get_pipeline_json(root_pipeline)
+
+ first_triggered = json_response['triggered'].first
+ expect(first_triggered['triggered']).to contain_exactly(
+ include('id' => target_of_target_pipeline.id))
+ expect(first_triggered['details']['stages']).not_to be_nil
+ end
+
+ it 'does not recursively expand triggered' do
+ get_pipeline_json(root_pipeline)
+
+ first_triggered = json_response['triggered'].first
+ expect(first_triggered['triggered_by']).to be_nil
+ end
+ end
+
+ context 'when it does have permission to read other projects' do
+ before do
+ source_project.add_developer(user)
+ target_project.add_developer(user)
+ end
+
+ context 'when not-expanding any pipelines' do
+ let(:expanded) { nil }
+
+ it_behaves_like 'not expanded'
+ end
+
+ context 'when expanding non-existing pipeline' do
+ let(:expanded) { [-1] }
+
+ it_behaves_like 'not expanded'
+ end
+
+ context 'when expanding pipeline that is not directly expandable' do
+ let(:expanded) { [source_of_source_pipeline.id, target_of_target_pipeline.id] }
+
+ it_behaves_like 'not expanded'
+ end
+
+ context 'when expanding self' do
+ let(:expanded) { [root_pipeline.id] }
+
+ context 'it does not recursively expand pipelines' do
+ it_behaves_like 'not expanded'
+ end
+ end
+
+ context 'when expanding source and target pipeline' do
+ let(:expanded) { [source_pipeline.id, target_pipeline.id] }
+
+ it_behaves_like 'expanded'
+
+ context 'when expand depth is limited to 1' do
+ before do
+ stub_const('TriggeredPipelineEntity::MAX_EXPAND_DEPTH', 1)
+ end
+
+ it_behaves_like 'not expanded' do
+ # We expect that triggered/triggered_by is not expanded,
+ # but we still return details.stages for that pipeline
+ let(:expected_stages) { be_a(Array) }
+ end
+ end
+ end
+
+ context 'when expanding all' do
+ let(:expanded) do
+ [
+ source_of_source_pipeline.id,
+ source_pipeline.id,
+ root_pipeline.id,
+ target_pipeline.id,
+ target_of_target_pipeline.id
+ ]
+ end
+
+ it_behaves_like 'expanded'
+ end
+ end
+
+ context 'when does not have permission to read other projects' do
+ let(:expanded) { [source_pipeline.id, target_pipeline.id] }
+
+ it_behaves_like 'not expanded'
+ end
+
+ def create_pipeline(project)
+ create(:ci_empty_pipeline, project: project).tap do |pipeline|
+ create(:ci_build, pipeline: pipeline, stage: 'test', name: 'rspec')
+ end
+ end
+
+ def create_link(source_pipeline, pipeline)
+ source_pipeline.sourced_pipelines.create!(
+ source_job: source_pipeline.builds.all.sample,
+ source_project: source_pipeline.project,
+ project: pipeline.project,
+ pipeline: pipeline
+ )
+ end
+
+ def get_pipeline_json(pipeline)
+ params = {
+ namespace_id: pipeline.project.namespace,
+ project_id: pipeline.project,
+ id: pipeline,
+ expanded: expanded
+ }
+
+ get :show, params: params.compact, format: :json
+ end
+ end
+
def get_pipeline_json
get :show, params: { namespace_id: project.namespace, project_id: project, id: pipeline }, format: :json
end
diff --git a/spec/dependencies/omniauth_saml_spec.rb b/spec/dependencies/omniauth_saml_spec.rb
index ccc604dc230..8a685648c71 100644
--- a/spec/dependencies/omniauth_saml_spec.rb
+++ b/spec/dependencies/omniauth_saml_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'omniauth/strategies/saml'
diff --git a/spec/factories/ci/sources/pipelines.rb b/spec/factories/ci/sources/pipelines.rb
new file mode 100644
index 00000000000..57495502944
--- /dev/null
+++ b/spec/factories/ci/sources/pipelines.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :ci_sources_pipeline, class: Ci::Sources::Pipeline do
+ after(:build) do |source|
+ source.project ||= source.pipeline.project
+ source.source_pipeline ||= source.source_job.pipeline
+ source.source_project ||= source.source_pipeline.project
+ end
+
+ source_job factory: :ci_build
+
+ pipeline factory: :ci_empty_pipeline
+ end
+end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 458c67760d5..f5d5bc7f5b9 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'tempfile'
@@ -424,8 +426,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'loads job trace' do
expect(page).to have_content 'BUILD TRACE'
- job.trace.write('a+b') do |stream|
- stream.append(' and more trace', 11)
+ job.trace.write(+'a+b') do |stream|
+ stream.append(+' and more trace', 11)
end
expect(page).to have_content 'BUILD TRACE and more trace'
diff --git a/spec/finders/access_requests_finder_spec.rb b/spec/finders/access_requests_finder_spec.rb
index 605777462bb..4c67087b50f 100644
--- a/spec/finders/access_requests_finder_spec.rb
+++ b/spec/finders/access_requests_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AccessRequestsFinder do
diff --git a/spec/finders/admin/projects_finder_spec.rb b/spec/finders/admin/projects_finder_spec.rb
index 44cc8debd04..eb5d0bba183 100644
--- a/spec/finders/admin/projects_finder_spec.rb
+++ b/spec/finders/admin/projects_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::ProjectsFinder do
diff --git a/spec/finders/autocomplete/move_to_project_finder_spec.rb b/spec/finders/autocomplete/move_to_project_finder_spec.rb
index 4a87b47bd08..f997dd32c40 100644
--- a/spec/finders/autocomplete/move_to_project_finder_spec.rb
+++ b/spec/finders/autocomplete/move_to_project_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Autocomplete::MoveToProjectFinder do
diff --git a/spec/finders/autocomplete/users_finder_spec.rb b/spec/finders/autocomplete/users_finder_spec.rb
index f3b54ca0461..5d340c46114 100644
--- a/spec/finders/autocomplete/users_finder_spec.rb
+++ b/spec/finders/autocomplete/users_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Autocomplete::UsersFinder do
diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb
index 3fc86f3e408..1a33bdf11d7 100644
--- a/spec/finders/branches_finder_spec.rb
+++ b/spec/finders/branches_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BranchesFinder do
diff --git a/spec/finders/clusters_finder_spec.rb b/spec/finders/clusters_finder_spec.rb
index da529e0670f..f6ea8347f67 100644
--- a/spec/finders/clusters_finder_spec.rb
+++ b/spec/finders/clusters_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ClustersFinder do
diff --git a/spec/finders/concerns/finder_methods_spec.rb b/spec/finders/concerns/finder_methods_spec.rb
index e074e53c2c5..2e44df8b044 100644
--- a/spec/finders/concerns/finder_methods_spec.rb
+++ b/spec/finders/concerns/finder_methods_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe FinderMethods do
diff --git a/spec/finders/concerns/finder_with_cross_project_access_spec.rb b/spec/finders/concerns/finder_with_cross_project_access_spec.rb
index f29acb521a8..7f6190f96e0 100644
--- a/spec/finders/concerns/finder_with_cross_project_access_spec.rb
+++ b/spec/finders/concerns/finder_with_cross_project_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe FinderWithCrossProjectAccess do
diff --git a/spec/finders/contributed_projects_finder_spec.rb b/spec/finders/contributed_projects_finder_spec.rb
index ee84fd067d4..1d907261fe9 100644
--- a/spec/finders/contributed_projects_finder_spec.rb
+++ b/spec/finders/contributed_projects_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ContributedProjectsFinder do
diff --git a/spec/finders/environments_finder_spec.rb b/spec/finders/environments_finder_spec.rb
index 25835bb4d94..69687eaa99f 100644
--- a/spec/finders/environments_finder_spec.rb
+++ b/spec/finders/environments_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe EnvironmentsFinder do
diff --git a/spec/finders/events_finder_spec.rb b/spec/finders/events_finder_spec.rb
index 3bce46cc4d1..848030262cd 100644
--- a/spec/finders/events_finder_spec.rb
+++ b/spec/finders/events_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe EventsFinder do
diff --git a/spec/finders/fork_projects_finder_spec.rb b/spec/finders/fork_projects_finder_spec.rb
index 98cff37205e..2fba53a74a0 100644
--- a/spec/finders/fork_projects_finder_spec.rb
+++ b/spec/finders/fork_projects_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ForkProjectsFinder do
diff --git a/spec/finders/group_descendants_finder_spec.rb b/spec/finders/group_descendants_finder_spec.rb
index 5fb6739d6e2..17875a9b9ab 100644
--- a/spec/finders/group_descendants_finder_spec.rb
+++ b/spec/finders/group_descendants_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupDescendantsFinder do
diff --git a/spec/finders/group_members_finder_spec.rb b/spec/finders/group_members_finder_spec.rb
index 49b0e14241e..12f92f6b5b0 100644
--- a/spec/finders/group_members_finder_spec.rb
+++ b/spec/finders/group_members_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupMembersFinder, '#execute' do
diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb
index f4bd8a3f6ba..b291b5d4b90 100644
--- a/spec/finders/group_projects_finder_spec.rb
+++ b/spec/finders/group_projects_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupProjectsFinder do
diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb
index c8875d1f92d..741a89a270b 100644
--- a/spec/finders/groups_finder_spec.rb
+++ b/spec/finders/groups_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupsFinder do
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index a17ff1ad50d..c27ce263bf0 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe IssuesFinder do
diff --git a/spec/finders/joined_groups_finder_spec.rb b/spec/finders/joined_groups_finder_spec.rb
index ae3e55f90f1..b01bd44470a 100644
--- a/spec/finders/joined_groups_finder_spec.rb
+++ b/spec/finders/joined_groups_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe JoinedGroupsFinder do
diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb
index ba41ded112a..2681f098fec 100644
--- a/spec/finders/labels_finder_spec.rb
+++ b/spec/finders/labels_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LabelsFinder do
diff --git a/spec/finders/license_template_finder_spec.rb b/spec/finders/license_template_finder_spec.rb
index f6f40bf33cc..183ee67d801 100644
--- a/spec/finders/license_template_finder_spec.rb
+++ b/spec/finders/license_template_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LicenseTemplateFinder do
diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb
index 5134db96133..9849808c255 100644
--- a/spec/finders/members_finder_spec.rb
+++ b/spec/finders/members_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MembersFinder, '#execute' do
diff --git a/spec/finders/merge_request_target_project_finder_spec.rb b/spec/finders/merge_request_target_project_finder_spec.rb
index d26a75179de..1d78b7ba4e3 100644
--- a/spec/finders/merge_request_target_project_finder_spec.rb
+++ b/spec/finders/merge_request_target_project_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequestTargetProjectFinder do
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 6c0bbeff4f4..a396284f1e9 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequestsFinder do
diff --git a/spec/finders/milestones_finder_spec.rb b/spec/finders/milestones_finder_spec.rb
index 34c7b508c56..3545ff35ed8 100644
--- a/spec/finders/milestones_finder_spec.rb
+++ b/spec/finders/milestones_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MilestonesFinder do
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index 88906adfeeb..44636a22ef9 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe NotesFinder do
diff --git a/spec/finders/personal_access_tokens_finder_spec.rb b/spec/finders/personal_access_tokens_finder_spec.rb
index 3e849c9a644..a44daf585ba 100644
--- a/spec/finders/personal_access_tokens_finder_spec.rb
+++ b/spec/finders/personal_access_tokens_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PersonalAccessTokensFinder do
diff --git a/spec/finders/personal_projects_finder_spec.rb b/spec/finders/personal_projects_finder_spec.rb
index ef7dd0cd4a8..7686dd3dc9d 100644
--- a/spec/finders/personal_projects_finder_spec.rb
+++ b/spec/finders/personal_projects_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PersonalProjectsFinder do
diff --git a/spec/finders/pipeline_schedules_finder_spec.rb b/spec/finders/pipeline_schedules_finder_spec.rb
index 2fefa0280d1..8d0bde15e03 100644
--- a/spec/finders/pipeline_schedules_finder_spec.rb
+++ b/spec/finders/pipeline_schedules_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineSchedulesFinder do
diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb
index b23fd8ccdc6..05d13a76e0e 100644
--- a/spec/finders/pipelines_finder_spec.rb
+++ b/spec/finders/pipelines_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelinesFinder do
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index ac866e49fcd..4ec12b5a7f7 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectsFinder do
diff --git a/spec/finders/runner_jobs_finder_spec.rb b/spec/finders/runner_jobs_finder_spec.rb
index 01f45a37ba8..c11f9182036 100644
--- a/spec/finders/runner_jobs_finder_spec.rb
+++ b/spec/finders/runner_jobs_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RunnerJobsFinder do
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 72de05b5131..e7372189d17 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SnippetsFinder do
diff --git a/spec/finders/tags_finder_spec.rb b/spec/finders/tags_finder_spec.rb
index 460e278e2d3..85f970b71c4 100644
--- a/spec/finders/tags_finder_spec.rb
+++ b/spec/finders/tags_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TagsFinder do
diff --git a/spec/finders/template_finder_spec.rb b/spec/finders/template_finder_spec.rb
index 114af9461e0..ed47752cf60 100644
--- a/spec/finders/template_finder_spec.rb
+++ b/spec/finders/template_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TemplateFinder do
diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb
index 9a3ffffb3f2..5d284f4cf17 100644
--- a/spec/finders/todos_finder_spec.rb
+++ b/spec/finders/todos_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TodosFinder do
diff --git a/spec/finders/user_recent_events_finder_spec.rb b/spec/finders/user_recent_events_finder_spec.rb
index 5ebceeb7586..eef6448a4a2 100644
--- a/spec/finders/user_recent_events_finder_spec.rb
+++ b/spec/finders/user_recent_events_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UserRecentEventsFinder do
diff --git a/spec/finders/users_finder_spec.rb b/spec/finders/users_finder_spec.rb
index d71d3c99272..7f1fc1cc1c5 100644
--- a/spec/finders/users_finder_spec.rb
+++ b/spec/finders/users_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UsersFinder do
diff --git a/spec/lib/gitlab/daemon_spec.rb b/spec/lib/gitlab/daemon_spec.rb
index 0372b770844..cf1f089c577 100644
--- a/spec/lib/gitlab/daemon_spec.rb
+++ b/spec/lib/gitlab/daemon_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Daemon do
subject { described_class.new }
before do
- allow(subject).to receive(:start_working)
+ allow(subject).to receive(:run_thread)
allow(subject).to receive(:stop_working)
end
@@ -44,7 +44,7 @@ describe Gitlab::Daemon do
it 'starts the Daemon' do
expect { subject.start.join }.to change { subject.thread? }.from(false).to(true)
- expect(subject).to have_received(:start_working)
+ expect(subject).to have_received(:run_thread)
end
end
@@ -52,7 +52,21 @@ describe Gitlab::Daemon do
it "doesn't shutdown stopped Daemon" do
expect { subject.stop }.not_to change { subject.thread? }
- expect(subject).not_to have_received(:start_working)
+ expect(subject).not_to have_received(:run_thread)
+ end
+ end
+ end
+
+ describe '#start_working' do
+ context 'when start_working fails' do
+ before do
+ expect(subject).to receive(:start_working) { false }
+ end
+
+ it 'does not start thread' do
+ expect(subject).not_to receive(:run_thread)
+
+ expect(subject.start).to eq(nil)
end
end
end
@@ -66,7 +80,7 @@ describe Gitlab::Daemon do
it "doesn't start running Daemon" do
expect { subject.start.join }.not_to change { subject.thread }
- expect(subject).to have_received(:start_working).once
+ expect(subject).to have_received(:run_thread).once
end
end
@@ -79,7 +93,7 @@ describe Gitlab::Daemon do
context 'when stop_working raises exception' do
before do
- allow(subject).to receive(:start_working) do
+ allow(subject).to receive(:run_thread) do
sleep(1000)
end
end
@@ -108,7 +122,7 @@ describe Gitlab::Daemon do
expect(subject.start).to be_nil
expect { subject.start }.not_to change { subject.thread? }
- expect(subject).not_to have_received(:start_working)
+ expect(subject).not_to have_received(:run_thread)
end
end
diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb
index ca036390bde..36486cbbc7d 100644
--- a/spec/lib/gitlab/danger/teammate_spec.rb
+++ b/spec/lib/gitlab/danger/teammate_spec.rb
@@ -2,11 +2,13 @@
require 'fast_spec_helper'
+require 'rspec-parameterized'
+
require 'gitlab/danger/teammate'
describe Gitlab::Danger::Teammate do
- subject { described_class.new(options) }
- let(:options) { { 'projects' => projects, 'role' => role } }
+ subject { described_class.new(options.stringify_keys) }
+ let(:options) { { username: 'luigi', projects: projects, role: role } }
let(:projects) { { project => capabilities } }
let(:role) { 'Engineer, Manage' }
let(:labels) { [] }
@@ -95,4 +97,64 @@ describe Gitlab::Danger::Teammate do
expect(subject.maintainer?(project, :frontend, labels)).to be_falsey
end
end
+
+ describe '#status' do
+ let(:capabilities) { ['dish washing'] }
+
+ context 'with empty cache' do
+ context 'for successful request' do
+ it 'returns the response' do
+ mock_status = double(does_not: 'matter')
+ expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
+ .and_return(mock_status)
+
+ expect(subject.status).to be mock_status
+ end
+ end
+
+ context 'for failing request' do
+ it 'returns nil' do
+ expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
+ .and_raise(Gitlab::Danger::RequestHelper::HTTPError.new)
+
+ expect(subject.status).to be nil
+ end
+ end
+ end
+
+ context 'with filled cache' do
+ it 'returns the cached response' do
+ mock_status = double(does_not: 'matter')
+ expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
+ .and_return(mock_status)
+ subject.status
+
+ expect(Gitlab::Danger::RequestHelper).not_to receive(:http_get_json)
+ expect(subject.status).to be mock_status
+ end
+ end
+ end
+
+ describe '#out_of_office?' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:capabilities) { ['dry head'] }
+
+ where(:status, :result) do
+ nil | false
+ {} | false
+ { message: 'dear reader' } | false
+ { message: 'OOO: massage' } | true
+ { message: 'love it SOOO much' } | true
+ end
+
+ with_them do
+ before do
+ expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
+ .and_return(status&.stringify_keys)
+ end
+
+ it { expect(subject.out_of_office?).to be result }
+ end
+ end
end
diff --git a/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
index 263cc821c1a..adea2ba30b3 100644
--- a/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
+++ b/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
@@ -12,8 +12,8 @@ describe Gitlab::SidekiqDaemon::MemoryKiller do
allow(Sidekiq.logger).to receive(:warn)
end
- describe '#start_working' do
- subject { memory_killer.send(:start_working) }
+ describe '#run_thread' do
+ subject { memory_killer.send(:run_thread) }
before do
# let enabled? return 3 times: true, true, false
@@ -37,7 +37,7 @@ describe Gitlab::SidekiqDaemon::MemoryKiller do
.with(
class: described_class.to_s,
pid: pid,
- message: "Exception from start_working: My Exception")
+ message: "Exception from run_thread: My Exception")
expect(memory_killer).to receive(:rss_within_range?).twice.and_raise(StandardError, 'My Exception')
expect(memory_killer).to receive(:sleep).twice.with(Gitlab::SidekiqDaemon::MemoryKiller::CHECK_INTERVAL_SECONDS)
@@ -50,7 +50,7 @@ describe Gitlab::SidekiqDaemon::MemoryKiller do
.with(
class: described_class.to_s,
pid: pid,
- message: "Exception from start_working: My Exception")
+ message: "Exception from run_thread: My Exception")
expect(memory_killer).to receive(:rss_within_range?).once.and_raise(Exception, 'My Exception')
diff --git a/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb b/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb
index 397098ed5a4..3f49ef0e9a7 100644
--- a/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb
+++ b/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb
@@ -37,8 +37,8 @@ describe Gitlab::SidekiqDaemon::Monitor do
end
end
- describe '#start_working when notification channel not enabled' do
- subject { monitor.send(:start_working) }
+ describe '#run_thread when notification channel not enabled' do
+ subject { monitor.send(:run_thread) }
it 'return directly' do
allow(monitor).to receive(:notification_channel_enabled?).and_return(nil)
@@ -52,8 +52,8 @@ describe Gitlab::SidekiqDaemon::Monitor do
end
end
- describe '#start_working when notification channel enabled' do
- subject { monitor.send(:start_working) }
+ describe '#run_thread when notification channel enabled' do
+ subject { monitor.send(:run_thread) }
before do
# we want to run at most once cycle
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index de90e4c2fba..15281c9e826 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -19,17 +19,24 @@ describe Ci::Build do
it { is_expected.to belong_to(:runner) }
it { is_expected.to belong_to(:trigger_request) }
it { is_expected.to belong_to(:erased_by) }
+
it { is_expected.to have_many(:trace_sections) }
it { is_expected.to have_many(:needs) }
+ it { is_expected.to have_many(:sourced_pipelines) }
+ it { is_expected.to have_many(:job_variables) }
+
it { is_expected.to have_one(:deployment) }
it { is_expected.to have_one(:runner_session) }
- it { is_expected.to have_many(:job_variables) }
+
it { is_expected.to validate_presence_of(:ref) }
+
it { is_expected.to respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) }
+
it { is_expected.to delegate_method(:merge_request_event?).to(:pipeline) }
it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) }
it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) }
+
it { is_expected.to include_module(Ci::PipelineDelegator) }
describe 'associations' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 0e11c595388..de0ce9932e8 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -28,7 +28,13 @@ describe Ci::Pipeline, :mailer do
it { is_expected.to have_many(:builds) }
it { is_expected.to have_many(:auto_canceled_pipelines) }
it { is_expected.to have_many(:auto_canceled_jobs) }
+ it { is_expected.to have_many(:sourced_pipelines) }
+ it { is_expected.to have_many(:triggered_pipelines) }
+
it { is_expected.to have_one(:chat_data) }
+ it { is_expected.to have_one(:source_pipeline) }
+ it { is_expected.to have_one(:triggered_by_pipeline) }
+ it { is_expected.to have_one(:source_job) }
it { is_expected.to validate_presence_of(:sha) }
it { is_expected.to validate_presence_of(:status) }
diff --git a/spec/models/ci/sources/pipeline_spec.rb b/spec/models/ci/sources/pipeline_spec.rb
new file mode 100644
index 00000000000..63bee5bfb55
--- /dev/null
+++ b/spec/models/ci/sources/pipeline_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::Sources::Pipeline do
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:pipeline) }
+
+ it { is_expected.to belong_to(:source_project) }
+ it { is_expected.to belong_to(:source_job) }
+ it { is_expected.to belong_to(:source_pipeline) }
+
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:pipeline) }
+
+ it { is_expected.to validate_presence_of(:source_project) }
+ it { is_expected.to validate_presence_of(:source_job) }
+ it { is_expected.to validate_presence_of(:source_pipeline) }
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 1490955f4a3..68833fdaf73 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -101,6 +101,8 @@ describe Project do
it { is_expected.to have_many(:deploy_tokens).through(:project_deploy_tokens) }
it { is_expected.to have_many(:cycle_analytics_stages) }
it { is_expected.to have_many(:external_pull_requests) }
+ it { is_expected.to have_many(:sourced_pipelines) }
+ it { is_expected.to have_many(:source_pipelines) }
it 'has an inverse relationship with merge requests' do
expect(described_class.reflect_on_association(:merge_requests).has_inverse?).to eq(:target_project)
diff --git a/spec/serializers/pipeline_details_entity_spec.rb b/spec/serializers/pipeline_details_entity_spec.rb
index d7c40b8e7b9..b180ede51eb 100644
--- a/spec/serializers/pipeline_details_entity_spec.rb
+++ b/spec/serializers/pipeline_details_entity_spec.rb
@@ -138,5 +138,40 @@ describe PipelineDetailsEntity do
expect(subject[:flags][:yaml_errors]).to be false
end
end
+
+ context 'when pipeline is triggered by other pipeline' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
+ before do
+ create(:ci_sources_pipeline, pipeline: pipeline)
+ end
+
+ it 'contains an information about depedent pipeline' do
+ expect(subject[:triggered_by]).to be_a(Hash)
+ expect(subject[:triggered_by][:path]).not_to be_nil
+ expect(subject[:triggered_by][:details]).not_to be_nil
+ expect(subject[:triggered_by][:details][:status]).not_to be_nil
+ expect(subject[:triggered_by][:project]).not_to be_nil
+ end
+ end
+
+ context 'when pipeline triggered other pipeline' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+
+ before do
+ create(:ci_sources_pipeline, source_job: build)
+ create(:ci_sources_pipeline, source_job: build)
+ end
+
+ it 'contains an information about depedent pipeline' do
+ expect(subject[:triggered]).to be_a(Array)
+ expect(subject[:triggered].length).to eq(2)
+ expect(subject[:triggered].first[:path]).not_to be_nil
+ expect(subject[:triggered].first[:details]).not_to be_nil
+ expect(subject[:triggered].first[:details][:status]).not_to be_nil
+ expect(subject[:triggered].first[:project]).not_to be_nil
+ end
+ end
end
end
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index a1d275cfa2a..9762c83ed6a 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -158,7 +158,7 @@ describe PipelineSerializer do
it 'verifies number of queries', :request_store do
recorded = ActiveRecord::QueryRecorder.new { subject }
- expected_queries = Gitlab.ee? ? 38 : 31
+ expected_queries = Gitlab.ee? ? 38 : 35
expect(recorded.count).to be_within(2).of(expected_queries)
expect(recorded.cached_count).to eq(0)
@@ -179,7 +179,8 @@ describe PipelineSerializer do
# pipeline. With the same ref this check is cached but if refs are
# different then there is an extra query per ref
# https://gitlab.com/gitlab-org/gitlab-foss/issues/46368
- expected_queries = Gitlab.ee? ? 44 : 38
+ expected_queries = Gitlab.ee? ? 44 : 41
+
expect(recorded.count).to be_within(2).of(expected_queries)
expect(recorded.cached_count).to eq(0)
end
diff --git a/spec/services/ci/pipeline_trigger_service_spec.rb b/spec/services/ci/pipeline_trigger_service_spec.rb
index 76251b5b557..24d42f402f4 100644
--- a/spec/services/ci/pipeline_trigger_service_spec.rb
+++ b/spec/services/ci/pipeline_trigger_service_spec.rb
@@ -11,76 +11,158 @@ describe Ci::PipelineTriggerService do
describe '#execute' do
let(:user) { create(:user) }
- let(:trigger) { create(:ci_trigger, project: project, owner: user) }
let(:result) { described_class.new(project, user, params).execute }
before do
project.add_developer(user)
end
- context 'when trigger belongs to a different project' do
- let(:params) { { token: trigger.token, ref: 'master', variables: nil } }
- let(:trigger) { create(:ci_trigger, project: create(:project), owner: user) }
+ context 'with a trigger token' do
+ let(:trigger) { create(:ci_trigger, project: project, owner: user) }
- it 'does nothing' do
- expect { result }.not_to change { Ci::Pipeline.count }
- end
- end
-
- context 'when params have an existsed trigger token' do
- context 'when params have an existsed ref' do
+ context 'when trigger belongs to a different project' do
let(:params) { { token: trigger.token, ref: 'master', variables: nil } }
+ let(:trigger) { create(:ci_trigger, project: create(:project), owner: user) }
- it 'triggers a pipeline' do
- expect { result }.to change { Ci::Pipeline.count }.by(1)
- expect(result[:pipeline].ref).to eq('master')
- expect(result[:pipeline].project).to eq(project)
- expect(result[:pipeline].user).to eq(trigger.owner)
- expect(result[:pipeline].trigger_requests.to_a)
- .to eq(result[:pipeline].builds.map(&:trigger_request).uniq)
- expect(result[:status]).to eq(:success)
+ it 'does nothing' do
+ expect { result }.not_to change { Ci::Pipeline.count }
end
+ end
- context 'when commit message has [ci skip]' do
- before do
- allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { '[ci skip]' }
- end
+ context 'when params have an existsed trigger token' do
+ context 'when params have an existsed ref' do
+ let(:params) { { token: trigger.token, ref: 'master', variables: nil } }
- it 'ignores [ci skip] and create as general' do
+ it 'triggers a pipeline' do
expect { result }.to change { Ci::Pipeline.count }.by(1)
+ expect(result[:pipeline].ref).to eq('master')
+ expect(result[:pipeline].project).to eq(project)
+ expect(result[:pipeline].user).to eq(trigger.owner)
+ expect(result[:pipeline].trigger_requests.to_a)
+ .to eq(result[:pipeline].builds.map(&:trigger_request).uniq)
expect(result[:status]).to eq(:success)
end
+
+ context 'when commit message has [ci skip]' do
+ before do
+ allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { '[ci skip]' }
+ end
+
+ it 'ignores [ci skip] and create as general' do
+ expect { result }.to change { Ci::Pipeline.count }.by(1)
+ expect(result[:status]).to eq(:success)
+ end
+ end
+
+ context 'when params have a variable' do
+ let(:params) { { token: trigger.token, ref: 'master', variables: variables } }
+ let(:variables) { { 'AAA' => 'AAA123' } }
+
+ it 'has a variable' do
+ expect { result }.to change { Ci::PipelineVariable.count }.by(1)
+ .and change { Ci::TriggerRequest.count }.by(1)
+ expect(result[:pipeline].variables.map { |v| { v.key => v.value } }.first).to eq(variables)
+ expect(result[:pipeline].trigger_requests.last.variables).to be_nil
+ end
+ end
end
- context 'when params have a variable' do
- let(:params) { { token: trigger.token, ref: 'master', variables: variables } }
- let(:variables) { { 'AAA' => 'AAA123' } }
+ context 'when params have a non-existsed ref' do
+ let(:params) { { token: trigger.token, ref: 'invalid-ref', variables: nil } }
- it 'has a variable' do
- expect { result }.to change { Ci::PipelineVariable.count }.by(1)
- .and change { Ci::TriggerRequest.count }.by(1)
- expect(result[:pipeline].variables.map { |v| { v.key => v.value } }.first).to eq(variables)
- expect(result[:pipeline].trigger_requests.last.variables).to be_nil
+ it 'does not trigger a pipeline' do
+ expect { result }.not_to change { Ci::Pipeline.count }
+ expect(result[:http_status]).to eq(400)
end
end
end
- context 'when params have a non-existsed ref' do
- let(:params) { { token: trigger.token, ref: 'invalid-ref', variables: nil } }
+ context 'when params have a non-existsed trigger token' do
+ let(:params) { { token: 'invalid-token', ref: nil, variables: nil } }
it 'does not trigger a pipeline' do
expect { result }.not_to change { Ci::Pipeline.count }
- expect(result[:http_status]).to eq(400)
+ expect(result).to be_nil
end
end
end
- context 'when params have a non-existsed trigger token' do
- let(:params) { { token: 'invalid-token', ref: nil, variables: nil } }
+ context 'with a pipeline job token' do
+ let!(:pipeline) { create(:ci_empty_pipeline, project: project) }
+ let(:job) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+ context 'when job user does not have a permission to read a project' do
+ let(:params) { { token: job.token, ref: 'master', variables: nil } }
+ let(:job) { create(:ci_build, pipeline: pipeline, user: create(:user)) }
+
+ it 'does nothing' do
+ expect { result }.not_to change { Ci::Pipeline.count }
+ end
+ end
+
+ context 'when job is not running' do
+ let(:params) { { token: job.token, ref: 'master', variables: nil } }
+ let(:job) { create(:ci_build, :success, pipeline: pipeline, user: user) }
+
+ it 'does nothing' do
+ expect { result }.not_to change { Ci::Pipeline.count }
+ expect(result[:message]).to eq('400 Job has to be running')
+ end
+ end
- it 'does not trigger a pipeline' do
- expect { result }.not_to change { Ci::Pipeline.count }
- expect(result).to be_nil
+ context 'when params have an existsed job token' do
+ context 'when params have an existsed ref' do
+ let(:params) { { token: job.token, ref: 'master', variables: nil } }
+
+ it 'triggers a pipeline' do
+ expect { result }.to change { Ci::Pipeline.count }.by(1)
+ expect(result[:pipeline].ref).to eq('master')
+ expect(result[:pipeline].project).to eq(project)
+ expect(result[:pipeline].user).to eq(job.user)
+ expect(result[:status]).to eq(:success)
+ end
+
+ context 'when commit message has [ci skip]' do
+ before do
+ allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { '[ci skip]' }
+ end
+
+ it 'ignores [ci skip] and create as general' do
+ expect { result }.to change { Ci::Pipeline.count }.by(1)
+ expect(result[:status]).to eq(:success)
+ end
+ end
+
+ context 'when params have a variable' do
+ let(:params) { { token: job.token, ref: 'master', variables: variables } }
+ let(:variables) { { 'AAA' => 'AAA123' } }
+
+ it 'has a variable' do
+ expect { result }.to change { Ci::PipelineVariable.count }.by(1)
+ .and change { Ci::Sources::Pipeline.count }.by(1)
+ expect(result[:pipeline].variables.map { |v| { v.key => v.value } }.first).to eq(variables)
+ expect(job.sourced_pipelines.last.pipeline_id).to eq(result[:pipeline].id)
+ end
+ end
+ end
+
+ context 'when params have a non-existsed ref' do
+ let(:params) { { token: job.token, ref: 'invalid-ref', variables: nil } }
+
+ it 'does not job a pipeline' do
+ expect { result }.not_to change { Ci::Pipeline.count }
+ expect(result[:http_status]).to eq(400)
+ end
+ end
+ end
+
+ context 'when params have a non-existsed trigger token' do
+ let(:params) { { token: 'invalid-token', ref: nil, variables: nil } }
+
+ it 'does not trigger a pipeline' do
+ expect { result }.not_to change { Ci::Pipeline.count }
+ expect(result).to be_nil
+ end
end
end
end
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index 0cbb3122bb0..5ef1fb1932f 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -426,5 +426,22 @@ describe Groups::TransferService do
end
end
end
+
+ context 'when a project in group has container images' do
+ let(:group) { create(:group, :public, :nested) }
+ let!(:project) { create(:project, :repository, :public, namespace: group) }
+
+ before do
+ stub_container_registry_tags(repository: /image/, tags: %w[rc1])
+ create(:container_repository, project: project, name: :image)
+ create(:group_member, :owner, group: new_parent_group, user: user)
+ end
+
+ it 'does not allow group to be transferred' do
+ transfer_service.execute(new_parent_group)
+
+ expect(transfer_service.error).to match(/Docker images in their Container Registry/)
+ end
+ end
end
end
diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb
index 12e9c2b2f3a..ca8eaf4c970 100644
--- a/spec/services/groups/update_service_spec.rb
+++ b/spec/services/groups/update_service_spec.rb
@@ -148,6 +148,30 @@ describe Groups::UpdateService do
end
end
+ context 'projects in group have container images' do
+ let(:service) { described_class.new(public_group, user, path: SecureRandom.hex) }
+ let(:project) { create(:project, :internal, group: public_group) }
+
+ before do
+ stub_container_registry_tags(repository: /image/, tags: %w[rc1])
+ create(:container_repository, project: project, name: :image)
+ end
+
+ it 'does not allow path to be changed' do
+ result = described_class.new(public_group, user, path: 'new-path').execute
+
+ expect(result).to eq false
+ expect(public_group.errors[:base].first).to match(/Docker images in their Container Registry/)
+ end
+
+ it 'allows other settings to be changed' do
+ result = described_class.new(public_group, user, name: 'new-name').execute
+
+ expect(result).to eq true
+ expect(public_group.reload.name).to eq('new-name')
+ end
+ end
+
context 'for a subgroup' do
let(:subgroup) { create(:group, :private, parent: private_group) }
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 948e5e6250b..61e50fe5723 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -154,6 +154,17 @@ RSpec.configure do |config|
.with(:force_autodevops_on_by_default, anything)
.and_return(false)
+ # The following can be removed once Vue Issuable Sidebar
+ # is feature-complete and can be made default in place
+ # of older sidebar.
+ # See https://gitlab.com/groups/gitlab-org/-/epics/1863
+ allow(Feature).to receive(:enabled?)
+ .with(:vue_issuable_sidebar, anything)
+ .and_return(false)
+ allow(Feature).to receive(:enabled?)
+ .with(:vue_issuable_epic_sidebar, anything)
+ .and_return(false)
+
# Stub these calls due to being expensive operations
# It can be reenabled for specific tests via:
#