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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/CODEOWNERS2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js70
-rw-r--r--app/assets/javascripts/diff_notes/diff_notes_bundle.js3
-rw-r--r--app/assets/javascripts/filtered_search/available_dropdown_mappings.js4
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js14
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js23
-rw-r--r--app/assets/javascripts/filtered_search/visual_token_value.js35
-rw-r--r--app/assets/javascripts/self_monitor/components/self_monitor_form.vue2
-rw-r--r--app/assets/javascripts/self_monitor/store/state.js2
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss9
-rw-r--r--app/finders/serverless_domain_finder.rb2
-rw-r--r--app/models/serverless/lookup_path.rb30
-rw-r--r--app/models/serverless/virtual_domain.rb22
-rw-r--r--app/services/ci/register_job_service.rb10
-rw-r--r--app/views/discussions/_resolve_all.html.haml8
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml2
-rw-r--r--changelogs/unreleased/207223-fix-self-monitoring-project.yml5
-rw-r--r--changelogs/unreleased/Resolve-Migrate--fa-spinner-ee-app-views-shared-members.yml5
-rw-r--r--changelogs/unreleased/add-shard-label-to-queue-timing-histogram-metric.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-fix-group-members-owner-access-level.yml5
-rw-r--r--db/migrate/20200221105436_update_application_setting_npm_package_requests_forwarding_default.rb17
-rw-r--r--db/schema.rb4
-rw-r--r--doc/user/project/integrations/gitlab_slack_application.md6
-rw-r--r--doc/user/project/integrations/webhooks.md4
-rw-r--r--doc/user/search/index.md1
-rw-r--r--lib/api/entities/internal/pages/lookup_path.rb (renamed from lib/api/entities/internal.rb)5
-rw-r--r--lib/api/entities/internal/pages/virtual_domain.rb14
-rw-r--r--lib/api/entities/internal/serverless/lookup_path.rb13
-rw-r--r--lib/api/entities/internal/serverless/virtual_domain.rb14
-rw-r--r--lib/api/internal/pages.rb23
-rw-r--r--lib/gitlab/ci/config/entry/bridge.rb90
-rw-r--r--lib/gitlab/ci/config/entry/job.rb102
-rw-r--r--lib/gitlab/ci/config/entry/processable.rb102
-rw-r--r--lib/gitlab/ci/config/entry/root.rb4
-rw-r--r--lib/gitlab/config/entry/configurable.rb12
-rw-r--r--lib/gitlab/danger/helper.rb3
-rw-r--r--lib/gitlab/gon_helper.rb1
-rw-r--r--lib/gitlab/import_export/members_mapper.rb12
-rw-r--r--lib/gitlab/kubernetes/helm.rb2
-rw-r--r--lib/gitlab/reference_counter.rb55
-rw-r--r--locale/gitlab.pot9
-rw-r--r--spec/finders/serverless_domain_finder_spec.rb26
-rw-r--r--spec/fixtures/api/schemas/internal/serverless/lookup_path.json28
-rw-r--r--spec/fixtures/api/schemas/internal/serverless/virtual_domain.json14
-rw-r--r--spec/frontend/create_cluster/gke_cluster/helpers.js64
-rw-r--r--spec/frontend/create_cluster/gke_cluster/stores/actions_spec.js (renamed from spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js)7
-rw-r--r--spec/frontend/self_monitor/components/self_monitor_form_spec.js8
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/helpers.js49
-rw-r--r--spec/lib/gitlab/ci/config/entry/processable_spec.rb265
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb4
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb294
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb2
-rw-r--r--spec/lib/gitlab/reference_counter_spec.rb62
-rw-r--r--spec/migrations/update_application_setting_npm_package_requests_forwarding_default_spec.rb38
-rw-r--r--spec/requests/api/internal/pages_spec.rb82
-rw-r--r--spec/services/ci/register_job_service_spec.rb26
59 files changed, 1211 insertions, 519 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index 0b817fe14b5..a0ee7c456e1 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -21,6 +21,8 @@ lib/gitlab/database/ @gitlab-org/maintainers/database
lib/gitlab/sql/ @gitlab-org/maintainers/database
lib/gitlab/github_import/ @gitlab-org/maintainers/database
/ee/db/ @gitlab-org/maintainers/database
+/app/finders/ @gitlab-org/maintainers/database
+/ee/app/finders/ @gitlab-org/maintainers/database
# Feature specific owners
/ee/lib/gitlab/code_owners/ @reprazent
diff --git a/Gemfile b/Gemfile
index 4f543d5938d..6d4f4d6eefa 100644
--- a/Gemfile
+++ b/Gemfile
@@ -375,7 +375,7 @@ group :development, :test do
gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.34.0', require: false
gem 'simplecov', '~> 0.16.1', require: false
- gem 'bundler-audit', '~> 0.5.0', require: false
+ gem 'bundler-audit', '~> 0.6.1', require: false
gem 'benchmark-ips', '~> 2.3.0', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 724021ced7c..bbf1148281b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -134,8 +134,8 @@ GEM
bullet (6.0.2)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
- bundler-audit (0.5.0)
- bundler (~> 1.2)
+ bundler-audit (0.6.1)
+ bundler (>= 1.2.0, < 3)
thor (~> 0.18)
byebug (9.1.0)
capybara (3.22.0)
@@ -1176,7 +1176,7 @@ DEPENDENCIES
brakeman (~> 4.2)
browser (~> 2.5)
bullet (~> 6.0.2)
- bundler-audit (~> 0.5.0)
+ bundler-audit (~> 0.6.1)
capybara (~> 3.22.0)
capybara-screenshot (~> 1.0.22)
carrierwave (~> 1.3)
diff --git a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js
deleted file mode 100644
index 5f2a17da630..00000000000
--- a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/* eslint-disable no-else-return */
-/* global CommentsStore */
-/* global ResolveService */
-
-import Vue from 'vue';
-import { __ } from '~/locale';
-
-const ResolveDiscussionBtn = Vue.extend({
- props: {
- discussionId: {
- type: String,
- required: true,
- },
- mergeRequestId: {
- type: Number,
- required: true,
- },
- canResolve: {
- type: Boolean,
- required: true,
- },
- },
- data() {
- return {
- discussion: {},
- };
- },
- computed: {
- showButton() {
- if (this.discussion) {
- return this.discussion.isResolvable();
- } else {
- return false;
- }
- },
- isDiscussionResolved() {
- if (this.discussion) {
- return this.discussion.isResolved();
- } else {
- return false;
- }
- },
- buttonText() {
- if (this.isDiscussionResolved) {
- return __('Unresolve discussion');
- } else {
- return __('Resolve discussion');
- }
- },
- loading() {
- if (this.discussion) {
- return this.discussion.loading;
- } else {
- return false;
- }
- },
- },
- created() {
- CommentsStore.createDiscussion(this.discussionId, this.canResolve);
-
- this.discussion = CommentsStore.state[this.discussionId];
- },
- methods: {
- resolve() {
- ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId);
- },
- },
-});
-
-Vue.component('resolve-discussion-btn', ResolveDiscussionBtn);
diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
index 7dcf3594471..92862d4c933 100644
--- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js
+++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
@@ -11,7 +11,6 @@ import './components/comment_resolve_btn';
import './components/jump_to_discussion';
import './components/resolve_btn';
import './components/resolve_count';
-import './components/resolve_discussion_btn';
import './components/diff_note_avatars';
import './components/new_issue_for_discussion';
@@ -20,7 +19,7 @@ export default () => {
document.querySelector('.merge-request') || document.querySelector('.commit-box');
const { projectPath } = projectPathHolder.dataset;
const COMPONENT_SELECTOR =
- 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn, new-issue-for-discussion-btn';
+ 'resolve-btn, jump-to-discussion, comment-and-resolve-btn, new-issue-for-discussion-btn';
window.gl = window.gl || {};
window.gl.diffNoteApps = {};
diff --git a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js
index 5450abf4cbd..692b41da965 100644
--- a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js
+++ b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js
@@ -9,7 +9,7 @@ import DropdownUtils from './dropdown_utils';
import { mergeUrlParams } from '../lib/utils/url_utility';
export default class AvailableDropdownMappings {
- constructor(
+ constructor({
container,
runnerTagsEndpoint,
labelsEndpoint,
@@ -18,7 +18,7 @@ export default class AvailableDropdownMappings {
groupsOnly,
includeAncestorGroups,
includeDescendantGroups,
- ) {
+ }) {
this.container = container;
this.runnerTagsEndpoint = runnerTagsEndpoint;
this.labelsEndpoint = labelsEndpoint;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index 566fb295588..03f65612b60 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -13,6 +13,7 @@ export default class FilteredSearchDropdownManager {
labelsEndpoint = '',
milestonesEndpoint = '',
releasesEndpoint = '',
+ epicsEndpoint = '',
tokenizer,
page,
isGroup,
@@ -27,6 +28,7 @@ export default class FilteredSearchDropdownManager {
this.labelsEndpoint = removeTrailingSlash(labelsEndpoint);
this.milestonesEndpoint = removeTrailingSlash(milestonesEndpoint);
this.releasesEndpoint = removeTrailingSlash(releasesEndpoint);
+ this.epicsEndpoint = removeTrailingSlash(epicsEndpoint);
this.tokenizer = tokenizer;
this.filteredSearchTokenKeys = filteredSearchTokenKeys || FilteredSearchTokenKeys;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
@@ -54,16 +56,8 @@ export default class FilteredSearchDropdownManager {
setupMapping() {
const supportedTokens = this.filteredSearchTokenKeys.getKeys();
- const availableMappings = new AvailableDropdownMappings(
- this.container,
- this.runnerTagsEndpoint,
- this.labelsEndpoint,
- this.milestonesEndpoint,
- this.releasesEndpoint,
- this.groupsOnly,
- this.includeAncestorGroups,
- this.includeDescendantGroups,
- );
+
+ const availableMappings = new AvailableDropdownMappings({ ...this });
this.mapping = availableMappings.getAllowedMappings(supportedTokens);
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 0b4f9457c54..e9a714605c7 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -45,6 +45,11 @@ export default class FilteredSearchManager {
this.filteredSearchTokenKeys.enableMultipleAssignees();
}
+ const { epicsEndpoint } = this.filteredSearchInput.dataset;
+ if (!epicsEndpoint && this.filteredSearchTokenKeys.removeEpicToken) {
+ this.filteredSearchTokenKeys.removeEpicToken();
+ }
+
this.recentSearchesStore = new RecentSearchesStore({
isLocalStorageAvailable: RecentSearchesService.isAvailable(),
allowedKeys: this.filteredSearchTokenKeys.getKeys(),
@@ -88,12 +93,20 @@ export default class FilteredSearchManager {
if (this.filteredSearchInput) {
this.tokenizer = FilteredSearchTokenizer;
+ const {
+ runnerTagsEndpoint = '',
+ labelsEndpoint = '',
+ milestonesEndpoint = '',
+ releasesEndpoint = '',
+ epicsEndpoint = '',
+ } = this.filteredSearchInput.dataset;
+
this.dropdownManager = new FilteredSearchDropdownManager({
- runnerTagsEndpoint:
- this.filteredSearchInput.getAttribute('data-runner-tags-endpoint') || '',
- labelsEndpoint: this.filteredSearchInput.getAttribute('data-labels-endpoint') || '',
- milestonesEndpoint: this.filteredSearchInput.getAttribute('data-milestones-endpoint') || '',
- releasesEndpoint: this.filteredSearchInput.getAttribute('data-releases-endpoint') || '',
+ runnerTagsEndpoint,
+ labelsEndpoint,
+ milestonesEndpoint,
+ releasesEndpoint,
+ epicsEndpoint,
tokenizer: this.tokenizer,
page: this.page,
isGroup: this.isGroup,
diff --git a/app/assets/javascripts/filtered_search/visual_token_value.js b/app/assets/javascripts/filtered_search/visual_token_value.js
index 9f3cf881af4..b7ac655b619 100644
--- a/app/assets/javascripts/filtered_search/visual_token_value.js
+++ b/app/assets/javascripts/filtered_search/visual_token_value.js
@@ -28,6 +28,8 @@ export default class VisualTokenValue {
this.updateUserTokenAppearance(tokenValueContainer, tokenValueElement);
} else if (tokenType === 'my-reaction') {
this.updateEmojiTokenAppearance(tokenValueContainer, tokenValueElement);
+ } else if (tokenType === 'epic') {
+ this.updateEpicLabel(tokenValueContainer, tokenValueElement);
}
}
@@ -83,6 +85,39 @@ export default class VisualTokenValue {
.catch(() => new Flash(__('An error occurred while fetching label colors.')));
}
+ updateEpicLabel(tokenValueContainer) {
+ const tokenValue = this.tokenValue.replace(/^&/, '');
+ const filteredSearchInput = FilteredSearchContainer.container.querySelector('.filtered-search');
+ const { epicsEndpoint } = filteredSearchInput.dataset;
+ const epicsEndpointWithParams = FilteredSearchVisualTokens.getEndpointWithQueryParams(
+ `${epicsEndpoint}.json`,
+ filteredSearchInput.dataset.endpointQueryParams,
+ );
+
+ return AjaxCache.retrieve(epicsEndpointWithParams)
+ .then(epics => {
+ const matchingEpic = (epics || []).find(epic => epic.id === Number(tokenValue));
+
+ if (!matchingEpic) {
+ return;
+ }
+
+ VisualTokenValue.replaceEpicTitle(tokenValueContainer, matchingEpic.title, matchingEpic.id);
+ })
+ .catch(() => new Flash(__('An error occurred while adding formatted title for epic')));
+ }
+
+ static replaceEpicTitle(tokenValueContainer, epicTitle, epicId) {
+ const tokenContainer = tokenValueContainer;
+
+ const valueContainer = tokenContainer.querySelector('.value');
+
+ if (valueContainer) {
+ tokenContainer.dataset.originalValue = valueContainer.innerText;
+ valueContainer.innerText = `"${epicTitle}"::&${epicId}`;
+ }
+ }
+
static setTokenStyle(tokenValueContainer, backgroundColor, textColor) {
const token = tokenValueContainer;
diff --git a/app/assets/javascripts/self_monitor/components/self_monitor_form.vue b/app/assets/javascripts/self_monitor/components/self_monitor_form.vue
index 6b19a72317c..1e203d332db 100644
--- a/app/assets/javascripts/self_monitor/components/self_monitor_form.vue
+++ b/app/assets/javascripts/self_monitor/components/self_monitor_form.vue
@@ -129,7 +129,7 @@ export default {
</div>
<div class="settings-content">
<form name="self-monitoring-form">
- <p v-html="selfMonitoringFormText"></p>
+ <p ref="selfMonitoringFormText" v-html="selfMonitoringFormText"></p>
<gl-form-group :label="$options.formLabels.createProject" label-for="self-monitor-toggle">
<gl-toggle
v-model="selfMonitorEnabled"
diff --git a/app/assets/javascripts/self_monitor/store/state.js b/app/assets/javascripts/self_monitor/store/state.js
index a0ce88ff58c..582ce5576f1 100644
--- a/app/assets/javascripts/self_monitor/store/state.js
+++ b/app/assets/javascripts/self_monitor/store/state.js
@@ -9,7 +9,7 @@ export default (initialState = {}) => ({
deleteProjectStatusEndpoint: initialState.statusDeleteSelfMonitoringProjectPath || '',
selfMonitorProjectPath: initialState.selfMonitoringProjectFullPath || '',
showAlert: false,
- projectPath: '',
+ projectPath: initialState.selfMonitoringProjectFullPath || '',
loading: false,
alertContent: {},
});
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 005e5efbdaf..53a642d68ce 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -410,6 +410,15 @@
}
}
+ > button.dropdown-epic-button {
+ flex-direction: column;
+
+ .reference {
+ color: $gl-gray-400;
+ margin-top: $gl-padding-4;
+ }
+ }
+
&.droplab-item-selected i {
visibility: visible;
}
diff --git a/app/finders/serverless_domain_finder.rb b/app/finders/serverless_domain_finder.rb
index 3a8a55022bb..661cd0ca363 100644
--- a/app/finders/serverless_domain_finder.rb
+++ b/app/finders/serverless_domain_finder.rb
@@ -11,7 +11,7 @@ class ServerlessDomainFinder
return unless serverless?
@serverless_domain_cluster = ::Serverless::DomainCluster.for_uuid(serverless_domain_cluster_uuid)
- return unless serverless_domain_cluster
+ return unless serverless_domain_cluster&.knative&.external_ip
@environment = ::Environment.for_id_and_slug(match[:environment_id].to_i(16), match[:environment_slug])
return unless environment
diff --git a/app/models/serverless/lookup_path.rb b/app/models/serverless/lookup_path.rb
new file mode 100644
index 00000000000..c09b3718651
--- /dev/null
+++ b/app/models/serverless/lookup_path.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Serverless
+ class LookupPath
+ attr_reader :serverless_domain
+
+ delegate :serverless_domain_cluster, to: :serverless_domain
+ delegate :knative, to: :serverless_domain_cluster
+ delegate :certificate, to: :serverless_domain_cluster
+ delegate :key, to: :serverless_domain_cluster
+
+ def initialize(serverless_domain)
+ @serverless_domain = serverless_domain
+ end
+
+ def source
+ {
+ type: 'serverless',
+ service: serverless_domain.knative_uri.host,
+ cluster: {
+ hostname: knative.hostname,
+ address: knative.external_ip,
+ port: 443,
+ cert: certificate,
+ key: key
+ }
+ }
+ end
+ end
+end
diff --git a/app/models/serverless/virtual_domain.rb b/app/models/serverless/virtual_domain.rb
new file mode 100644
index 00000000000..d6a23a4c0ce
--- /dev/null
+++ b/app/models/serverless/virtual_domain.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Serverless
+ class VirtualDomain
+ attr_reader :serverless_domain
+
+ delegate :serverless_domain_cluster, to: :serverless_domain
+ delegate :pages_domain, to: :serverless_domain_cluster
+ delegate :certificate, to: :pages_domain
+ delegate :key, to: :pages_domain
+
+ def initialize(serverless_domain)
+ @serverless_domain = serverless_domain
+ end
+
+ def lookup_paths
+ [
+ ::Serverless::LookupPath.new(serverless_domain)
+ ]
+ end
+ end
+end
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index 57c0cdd0602..fb59797a8df 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -8,6 +8,8 @@ module Ci
JOB_QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30, 60, 300, 900, 1800, 3600].freeze
JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET = 5.freeze
+ METRICS_SHARD_TAG_PREFIX = 'metrics_shard::'.freeze
+ DEFAULT_METRICS_SHARD = 'default'.freeze
Result = Struct.new(:build, :valid?)
@@ -193,7 +195,13 @@ module Ci
def register_success(job)
labels = { shared_runner: runner.instance_type?,
- jobs_running_for_project: jobs_running_for_project(job) }
+ jobs_running_for_project: jobs_running_for_project(job),
+ shard: DEFAULT_METRICS_SHARD }
+
+ if runner.instance_type?
+ shard = runner.tag_list.sort.find { |name| name.starts_with?(METRICS_SHARD_TAG_PREFIX) }
+ labels[:shard] = shard.gsub(METRICS_SHARD_TAG_PREFIX, '') if shard
+ end
job_queue_duration_seconds.observe(labels, Time.now - job.queued_at) unless job.queued_at.nil?
attempt_counter.increment
diff --git a/app/views/discussions/_resolve_all.html.haml b/app/views/discussions/_resolve_all.html.haml
deleted file mode 100644
index 689a22acd27..00000000000
--- a/app/views/discussions/_resolve_all.html.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-%resolve-discussion-btn{ ":discussion-id" => "'#{discussion.id}'",
- ":merge-request-id" => discussion.noteable.iid,
- ":can-resolve" => discussion.can_resolve?(current_user),
- "inline-template" => true }
- .btn-group{ role: "group", "v-if" => "showButton" }
- %button.btn.btn-default{ type: "button", "@click" => "resolve", ":disabled" => "loading", "v-cloak" => "true" }
- = icon("spinner spin", "v-show" => "loading")
- {{ buttonText }}
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index a27ceaff782..d9ca0b8869f 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -159,6 +159,8 @@
= render_if_exists 'shared/issuable/filter_weight', type: type
+ = render_if_exists 'shared/issuable/filter_epic', type: type
+
%button.clear-search.hidden{ type: 'button' }
= icon('times')
.filter-dropdown-container.d-flex.flex-column.flex-md-row
diff --git a/changelogs/unreleased/207223-fix-self-monitoring-project.yml b/changelogs/unreleased/207223-fix-self-monitoring-project.yml
new file mode 100644
index 00000000000..3db9eb9977b
--- /dev/null
+++ b/changelogs/unreleased/207223-fix-self-monitoring-project.yml
@@ -0,0 +1,5 @@
+---
+title: Fix self monitoring project link
+merge_request: 25516
+author:
+type: fixed
diff --git a/changelogs/unreleased/Resolve-Migrate--fa-spinner-ee-app-views-shared-members.yml b/changelogs/unreleased/Resolve-Migrate--fa-spinner-ee-app-views-shared-members.yml
new file mode 100644
index 00000000000..1e592d40992
--- /dev/null
+++ b/changelogs/unreleased/Resolve-Migrate--fa-spinner-ee-app-views-shared-members.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate .fa-spinner to .spinner for ee/app/views/shared/members
+merge_request: 25019
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/add-shard-label-to-queue-timing-histogram-metric.yml b/changelogs/unreleased/add-shard-label-to-queue-timing-histogram-metric.yml
new file mode 100644
index 00000000000..5d2ba73dc5d
--- /dev/null
+++ b/changelogs/unreleased/add-shard-label-to-queue-timing-histogram-metric.yml
@@ -0,0 +1,5 @@
+---
+title: Add 'shard' label for 'job_queue_duration_seconds' metric
+merge_request: 23536
+author:
+type: changed
diff --git a/changelogs/unreleased/georgekoltsov-fix-group-members-owner-access-level.yml b/changelogs/unreleased/georgekoltsov-fix-group-members-owner-access-level.yml
new file mode 100644
index 00000000000..3cdc56e553c
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-fix-group-members-owner-access-level.yml
@@ -0,0 +1,5 @@
+---
+title: Fix an issue with Group Import members with Owner access level being imported with Maintainer access level. Owner access level is now preserved
+merge_request: 25595
+author:
+type: fixed
diff --git a/db/migrate/20200221105436_update_application_setting_npm_package_requests_forwarding_default.rb b/db/migrate/20200221105436_update_application_setting_npm_package_requests_forwarding_default.rb
new file mode 100644
index 00000000000..2479df2737d
--- /dev/null
+++ b/db/migrate/20200221105436_update_application_setting_npm_package_requests_forwarding_default.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class UpdateApplicationSettingNpmPackageRequestsForwardingDefault < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def up
+ change_column_default :application_settings, :npm_package_requests_forwarding, true
+
+ execute('UPDATE application_settings SET npm_package_requests_forwarding = TRUE')
+ end
+
+ def down
+ change_column_default :application_settings, :npm_package_requests_forwarding, false
+
+ execute('UPDATE application_settings SET npm_package_requests_forwarding = FALSE')
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 2fe21469398..792e5e7b8b5 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2020_02_20_180944) do
+ActiveRecord::Schema.define(version: 2020_02_21_105436) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -351,7 +351,7 @@ ActiveRecord::Schema.define(version: 2020_02_20_180944) do
t.boolean "prevent_merge_requests_committers_approval", default: false, null: false
t.boolean "email_restrictions_enabled", default: false, null: false
t.text "email_restrictions"
- t.boolean "npm_package_requests_forwarding", default: false, null: false
+ t.boolean "npm_package_requests_forwarding", default: true, null: false
t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id"
t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id"
t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id"
diff --git a/doc/user/project/integrations/gitlab_slack_application.md b/doc/user/project/integrations/gitlab_slack_application.md
index c1e6f93de30..72b1318a16a 100644
--- a/doc/user/project/integrations/gitlab_slack_application.md
+++ b/doc/user/project/integrations/gitlab_slack_application.md
@@ -3,9 +3,9 @@
NOTE: **Note:**
The GitLab Slack application is only configurable for GitLab.com. It will **not**
work for on-premises installations where you can configure the
-[Slack slash commands](slack_slash_commands.md) service instead. We're working
-with Slack on making this configurable for all GitLab installations, but there's
-no ETA.
+[Slack slash commands](slack_slash_commands.md) service instead. We're planning
+to make this configurable for all GitLab installations, but there's
+no ETA - see [#28164](https://gitlab.com/gitlab-org/gitlab/issues/28164).
It was first introduced in GitLab 9.4 and distributed to Slack App Directory in
GitLab 10.2.
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 5c9a21e2fbb..d93c4b3cdae 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -174,7 +174,8 @@ X-Gitlab-Event: Push Hook
"commits": [
{
"id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
- "message": "Update Catalan translation to e38cb41.",
+ "message": "Update Catalan translation to e38cb41.\n\nSee https://gitlab.com/gitlab-org/gitlab for more information",
+ "title": "Update Catalan translation to e38cb41.",
"timestamp": "2011-12-12T14:27:31+02:00",
"url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"author": {
@@ -188,6 +189,7 @@ X-Gitlab-Event: Push Hook
{
"id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"message": "fixed readme",
+ "title": "fixed readme",
"timestamp": "2012-01-03T23:36:29+02:00",
"url": "http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"author": {
diff --git a/doc/user/search/index.md b/doc/user/search/index.md
index 4e0e0734200..46c9c974c34 100644
--- a/doc/user/search/index.md
+++ b/doc/user/search/index.md
@@ -41,6 +41,7 @@ groups:
- [Label](../project/labels.md)
- My-reaction
- Confidential
+ - Epic ([Introduced](https://gitlab.com/gitlab-org/gitlab/issues/195704) in GitLab 12.8)
- Search for this text
1. Select or type the operator to use for filtering the attribute. The following operators are
available:
diff --git a/lib/api/entities/internal.rb b/lib/api/entities/internal/pages/lookup_path.rb
index 8f79bd14833..1bf94f74fb4 100644
--- a/lib/api/entities/internal.rb
+++ b/lib/api/entities/internal/pages/lookup_path.rb
@@ -8,11 +8,6 @@ module API
expose :project_id, :access_control,
:source, :https_only, :prefix
end
-
- class VirtualDomain < Grape::Entity
- expose :certificate, :key
- expose :lookup_paths, using: LookupPath
- end
end
end
end
diff --git a/lib/api/entities/internal/pages/virtual_domain.rb b/lib/api/entities/internal/pages/virtual_domain.rb
new file mode 100644
index 00000000000..27eb7571368
--- /dev/null
+++ b/lib/api/entities/internal/pages/virtual_domain.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Internal
+ module Pages
+ class VirtualDomain < Grape::Entity
+ expose :certificate, :key
+ expose :lookup_paths, using: LookupPath
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/internal/serverless/lookup_path.rb b/lib/api/entities/internal/serverless/lookup_path.rb
new file mode 100644
index 00000000000..8ca40b4f128
--- /dev/null
+++ b/lib/api/entities/internal/serverless/lookup_path.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Internal
+ module Serverless
+ class LookupPath < Grape::Entity
+ expose :source
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/internal/serverless/virtual_domain.rb b/lib/api/entities/internal/serverless/virtual_domain.rb
new file mode 100644
index 00000000000..8b53aa51bf5
--- /dev/null
+++ b/lib/api/entities/internal/serverless/virtual_domain.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Internal
+ module Serverless
+ class VirtualDomain < Grape::Entity
+ expose :certificate, :key
+ expose :lookup_paths, using: LookupPath
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/internal/pages.rb b/lib/api/internal/pages.rb
index a2fe3e09df8..e2e1351c939 100644
--- a/lib/api/internal/pages.rb
+++ b/lib/api/internal/pages.rb
@@ -24,13 +24,26 @@ module API
requires :host, type: String, desc: 'The host to query for'
end
get "/" do
- host = Namespace.find_by_pages_host(params[:host]) || PagesDomain.find_by_domain(params[:host])
- no_content! unless host
+ serverless_domain_finder = ServerlessDomainFinder.new(params[:host])
+ if serverless_domain_finder.serverless?
+ # Handle Serverless domains
+ serverless_domain = serverless_domain_finder.execute
+ no_content! unless serverless_domain
- virtual_domain = host.pages_virtual_domain
- no_content! unless virtual_domain
+ virtual_domain = Serverless::VirtualDomain.new(serverless_domain)
+ no_content! unless virtual_domain
- present virtual_domain, with: Entities::Internal::Pages::VirtualDomain
+ present virtual_domain, with: Entities::Internal::Serverless::VirtualDomain
+ else
+ # Handle Pages domains
+ host = Namespace.find_by_pages_host(params[:host]) || PagesDomain.find_by_domain(params[:host])
+ no_content! unless host
+
+ virtual_domain = host.pages_virtual_domain
+ no_content! unless virtual_domain
+
+ present virtual_domain, with: Entities::Internal::Pages::VirtualDomain
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/bridge.rb b/lib/gitlab/ci/config/entry/bridge.rb
index c0247dca73d..721c7c8b6d7 100644
--- a/lib/gitlab/ci/config/entry/bridge.rb
+++ b/lib/gitlab/ci/config/entry/bridge.rb
@@ -9,34 +9,21 @@ module Gitlab
# defining a downstream project trigger.
#
class Bridge < ::Gitlab::Config::Entry::Node
- include ::Gitlab::Config::Entry::Configurable
- include ::Gitlab::Config::Entry::Attributable
- include ::Gitlab::Config::Entry::Inheritable
+ include ::Gitlab::Ci::Config::Entry::Processable
- ALLOWED_KEYS = %i[trigger stage allow_failure only except
- when extends variables needs rules].freeze
+ ALLOWED_KEYS = %i[trigger allow_failure when variables needs].freeze
validations do
- validates :config, allowed_keys: ALLOWED_KEYS
- validates :config, presence: true
- validates :name, presence: true
- validates :name, type: Symbol
- validates :config, disallowed_keys: {
- in: %i[only except when start_in],
- message: 'key may not be used with `rules`'
- },
- if: :has_rules?
+ validates :config, allowed_keys: ALLOWED_KEYS + PROCESSABLE_ALLOWED_KEYS
with_options allow_nil: true do
validates :when,
inclusion: { in: %w[on_success on_failure always],
message: 'should be on_success, on_failure or always' }
- validates :extends, type: String
- validates :rules, array_of_hashes: true
end
validate on: :composed do
- unless trigger.present? || bridge_needs.present?
+ unless trigger_defined? || bridge_needs.present?
errors.add(:config, 'should contain either a trigger or a needs:pipeline')
end
end
@@ -58,32 +45,13 @@ module Gitlab
inherit: false,
metadata: { allowed_needs: %i[job bridge] }
- entry :stage, ::Gitlab::Ci::Config::Entry::Stage,
- description: 'Pipeline stage this job will be executed into.',
- inherit: false
-
- entry :only, ::Gitlab::Ci::Config::Entry::Policy,
- description: 'Refs policy this job will be executed for.',
- default: ::Gitlab::Ci::Config::Entry::Policy::DEFAULT_ONLY,
- inherit: false
-
- entry :except, ::Gitlab::Ci::Config::Entry::Policy,
- description: 'Refs policy this job will be executed for.',
- inherit: false
-
- entry :rules, ::Gitlab::Ci::Config::Entry::Rules,
- description: 'List of evaluable Rules to determine job inclusion.',
- inherit: false,
- metadata: {
- allowed_when: %w[on_success on_failure always never manual delayed].freeze
- }
-
entry :variables, ::Gitlab::Ci::Config::Entry::Variables,
description: 'Environment variables available for this job.',
inherit: false
- helpers(*ALLOWED_KEYS)
- attributes(*ALLOWED_KEYS)
+ helpers :trigger, :needs, :variables
+
+ attributes :when, :allow_failure
def self.matching?(name, config)
!name.to_s.start_with?('.') &&
@@ -95,56 +63,20 @@ module Gitlab
true
end
- def compose!(deps = nil)
- super do
- has_workflow_rules = deps&.workflow&.has_rules?
-
- # If workflow:rules: or rules: are used
- # they are considered not compatible
- # with `only/except` defaults
- #
- # Context: https://gitlab.com/gitlab-org/gitlab/merge_requests/21742
- if has_rules? || has_workflow_rules
- # Remove only/except defaults
- # defaults are not considered as defined
- @entries.delete(:only) unless only_defined?
- @entries.delete(:except) unless except_defined?
- end
- end
- end
-
- def has_rules?
- @config&.key?(:rules)
- end
-
- def name
- @metadata[:name]
- end
-
def value
- { name: name,
+ super.merge(
trigger: (trigger_value if trigger_defined?),
needs: (needs_value if needs_defined?),
ignore: !!allow_failure,
- stage: stage_value,
- when: when_value,
- extends: extends_value,
+ when: self.when,
variables: (variables_value if variables_defined?),
- rules: (rules_value if has_rules?),
- only: only_value,
- except: except_value,
- scheduling_type: needs_defined? && !bridge_needs ? :dag : :stage }.compact
+ scheduling_type: needs_defined? && !bridge_needs ? :dag : :stage
+ ).compact
end
def bridge_needs
needs_value[:bridge] if needs_value
end
-
- private
-
- def overwrite_entry(deps, key, current_entry)
- deps.default[key] unless current_entry.specified?
- end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 666c6e23eb4..931f769e920 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -8,33 +8,21 @@ module Gitlab
# Entry that represents a concrete CI/CD job.
#
class Job < ::Gitlab::Config::Entry::Node
- include ::Gitlab::Config::Entry::Configurable
- include ::Gitlab::Config::Entry::Attributable
- include ::Gitlab::Config::Entry::Inheritable
+ include ::Gitlab::Ci::Config::Entry::Processable
ALLOWED_WHEN = %w[on_success on_failure always manual delayed].freeze
- ALLOWED_KEYS = %i[tags script only except rules type image services
- allow_failure type stage when start_in artifacts cache
+ ALLOWED_KEYS = %i[tags script type image services
+ allow_failure type when start_in artifacts cache
dependencies before_script needs after_script variables
- environment coverage retry parallel extends interruptible timeout
+ environment coverage retry parallel interruptible timeout
resource_group release].freeze
REQUIRED_BY_NEEDS = %i[stage].freeze
validations do
- validates :config, type: Hash
- validates :config, allowed_keys: ALLOWED_KEYS
+ validates :config, allowed_keys: ALLOWED_KEYS + PROCESSABLE_ALLOWED_KEYS
validates :config, required_keys: REQUIRED_BY_NEEDS, if: :has_needs?
- validates :config, presence: true
validates :script, presence: true
- validates :name, presence: true
- validates :name, type: Symbol
- validates :config,
- disallowed_keys: {
- in: %i[only except when start_in],
- message: 'key may not be used with `rules`'
- },
- if: :has_rules?
validates :config,
disallowed_keys: {
in: %i[release],
@@ -53,8 +41,6 @@ module Gitlab
}
validates :dependencies, array_of_strings: true
- validates :extends, array_of_strings_or_string: true
- validates :rules, array_of_hashes: true
validates :resource_group, type: String
end
@@ -81,10 +67,6 @@ module Gitlab
description: 'Commands that will be executed in this job.',
inherit: false
- entry :stage, Entry::Stage,
- description: 'Pipeline stage this job will be executed into.',
- inherit: false
-
entry :type, Entry::Stage,
description: 'Deprecated: stage this job will be executed into.',
inherit: false
@@ -125,22 +107,6 @@ module Gitlab
description: 'Artifacts configuration for this job.',
inherit: true
- entry :only, Entry::Policy,
- description: 'Refs policy this job will be executed for.',
- default: ::Gitlab::Ci::Config::Entry::Policy::DEFAULT_ONLY,
- inherit: false
-
- entry :except, Entry::Policy,
- description: 'Refs policy this job will be executed for.',
- inherit: false
-
- entry :rules, Entry::Rules,
- description: 'List of evaluable Rules to determine job inclusion.',
- inherit: false,
- metadata: {
- allowed_when: %w[on_success on_failure always never manual delayed].freeze
- }
-
entry :needs, Entry::Needs,
description: 'Needs configuration for this job.',
metadata: { allowed_needs: %i[job cross_dependency] },
@@ -162,13 +128,13 @@ module Gitlab
description: 'This job will produce a release.',
inherit: false
- helpers :before_script, :script, :stage, :type, :after_script,
- :cache, :image, :services, :only, :except, :variables,
- :artifacts, :environment, :coverage, :retry, :rules,
- :parallel, :needs, :interruptible, :release, :tags
+ helpers :before_script, :script, :type, :after_script,
+ :cache, :image, :services, :variables,
+ :artifacts, :environment, :coverage, :retry,
+ :needs, :interruptible, :release, :tags
attributes :script, :tags, :allow_failure, :when, :dependencies,
- :needs, :retry, :parallel, :extends, :start_in, :rules,
+ :needs, :retry, :parallel, :start_in,
:interruptible, :timeout, :resource_group, :release
def self.matching?(name, config)
@@ -187,31 +153,9 @@ module Gitlab
end
@entries.delete(:type)
-
- has_workflow_rules = deps&.workflow&.has_rules?
-
- # If workflow:rules: or rules: are used
- # they are considered not compatible
- # with `only/except` defaults
- #
- # Context: https://gitlab.com/gitlab-org/gitlab/merge_requests/21742
- if has_rules? || has_workflow_rules
- # Remove only/except defaults
- # defaults are not considered as defined
- @entries.delete(:only) unless only_defined?
- @entries.delete(:except) unless except_defined?
- end
end
end
- def name
- @metadata[:name]
- end
-
- def value
- @config.merge(to_hash.compact)
- end
-
def manual_action?
self.when == 'manual'
end
@@ -220,38 +164,27 @@ module Gitlab
self.when == 'delayed'
end
- def has_rules?
- @config.try(:key?, :rules)
- end
-
def ignored?
allow_failure.nil? ? manual_action? : allow_failure
end
- private
-
- def overwrite_entry(deps, key, current_entry)
- deps.default[key] unless current_entry.specified?
- end
-
- def to_hash
- { name: name,
+ def value
+ super.merge(
before_script: before_script_value,
script: script_value,
image: image_value,
services: services_value,
- stage: stage_value,
cache: cache_value,
tags: tags_value,
- only: only_value,
- except: except_value,
- rules: has_rules? ? rules_value : nil,
+ when: self.when,
+ start_in: self.start_in,
+ dependencies: dependencies,
variables: variables_defined? ? variables_value : {},
environment: environment_defined? ? environment_value : nil,
environment_name: environment_defined? ? environment_value[:name] : nil,
coverage: coverage_defined? ? coverage_value : nil,
retry: retry_defined? ? retry_value : nil,
- parallel: parallel_defined? ? parallel_value.to_i : nil,
+ parallel: has_parallel? ? parallel.to_i : nil,
interruptible: interruptible_defined? ? interruptible_value : nil,
timeout: has_timeout? ? ChronicDuration.parse(timeout.to_s) : nil,
artifacts: artifacts_value,
@@ -260,7 +193,8 @@ module Gitlab
ignore: ignored?,
needs: needs_defined? ? needs_value : nil,
resource_group: resource_group,
- scheduling_type: needs_defined? ? :dag : :stage }
+ scheduling_type: needs_defined? ? :dag : :stage
+ ).compact
end
end
end
diff --git a/lib/gitlab/ci/config/entry/processable.rb b/lib/gitlab/ci/config/entry/processable.rb
new file mode 100644
index 00000000000..19e6601e31f
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/processable.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Entry
+ ##
+ # Entry that represents a CI/CD Processable (a job)
+ #
+ module Processable
+ extend ActiveSupport::Concern
+
+ include ::Gitlab::Config::Entry::Configurable
+ include ::Gitlab::Config::Entry::Attributable
+ include ::Gitlab::Config::Entry::Inheritable
+
+ PROCESSABLE_ALLOWED_KEYS = %i[extends stage only except rules].freeze
+
+ included do
+ validations do
+ validates :config, presence: true
+ validates :name, presence: true
+ validates :name, type: Symbol
+
+ validates :config, disallowed_keys: {
+ in: %i[only except when start_in],
+ message: 'key may not be used with `rules`'
+ },
+ if: :has_rules?
+
+ with_options allow_nil: true do
+ validates :extends, array_of_strings_or_string: true
+ validates :rules, array_of_hashes: true
+ end
+ end
+
+ entry :stage, Entry::Stage,
+ description: 'Pipeline stage this job will be executed into.',
+ inherit: false
+
+ entry :only, ::Gitlab::Ci::Config::Entry::Policy,
+ description: 'Refs policy this job will be executed for.',
+ default: ::Gitlab::Ci::Config::Entry::Policy::DEFAULT_ONLY,
+ inherit: false
+
+ entry :except, ::Gitlab::Ci::Config::Entry::Policy,
+ description: 'Refs policy this job will be executed for.',
+ inherit: false
+
+ entry :rules, ::Gitlab::Ci::Config::Entry::Rules,
+ description: 'List of evaluable Rules to determine job inclusion.',
+ inherit: false,
+ metadata: {
+ allowed_when: %w[on_success on_failure always never manual delayed].freeze
+ }
+
+ helpers :stage, :only, :except, :rules
+
+ attributes :extends, :rules
+ end
+
+ def compose!(deps = nil)
+ super do
+ has_workflow_rules = deps&.workflow&.has_rules?
+
+ # If workflow:rules: or rules: are used
+ # they are considered not compatible
+ # with `only/except` defaults
+ #
+ # Context: https://gitlab.com/gitlab-org/gitlab/merge_requests/21742
+ if has_rules? || has_workflow_rules
+ # Remove only/except defaults
+ # defaults are not considered as defined
+ @entries.delete(:only) unless only_defined? # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ @entries.delete(:except) unless except_defined? # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+
+ yield if block_given?
+ end
+ end
+
+ def name
+ metadata[:name]
+ end
+
+ def overwrite_entry(deps, key, current_entry)
+ deps.default[key] unless current_entry.specified?
+ end
+
+ def value
+ { name: name,
+ stage: stage_value,
+ extends: extends,
+ rules: rules_value,
+ only: only_value,
+ except: except_value }.compact
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/root.rb b/lib/gitlab/ci/config/entry/root.rb
index 12dd942fc1c..620f6a95e9d 100644
--- a/lib/gitlab/ci/config/entry/root.rb
+++ b/lib/gitlab/ci/config/entry/root.rb
@@ -67,7 +67,9 @@ module Gitlab
entry :workflow, Entry::Workflow,
description: 'List of evaluable rules to determine Pipeline status'
- helpers :default, :jobs, :stages, :types, :variables, :workflow
+ helpers :default, :stages, :types, :variables, :workflow
+
+ helpers :jobs, dynamic: true
delegate :before_script_value,
:image_value,
diff --git a/lib/gitlab/config/entry/configurable.rb b/lib/gitlab/config/entry/configurable.rb
index e7d441bb21c..75e15cd8cb1 100644
--- a/lib/gitlab/config/entry/configurable.rb
+++ b/lib/gitlab/config/entry/configurable.rb
@@ -75,6 +75,8 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def entry(key, entry, description: nil, default: nil, inherit: nil, reserved: nil, metadata: {})
+ raise ArgumentError, "Entry #{key} already defined" if @nodes.to_h[key.to_sym]
+
factory = ::Gitlab::Config::Entry::Factory.new(entry)
.with(description: description)
.with(default: default)
@@ -86,8 +88,16 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
- def helpers(*nodes)
+ def helpers(*nodes, dynamic: false)
nodes.each do |symbol|
+ if method_defined?("#{symbol}_defined?") || method_defined?("#{symbol}_value")
+ raise ArgumentError, "Method #{symbol}_defined? or #{symbol}_value already defined"
+ end
+
+ unless @nodes.to_h[symbol]
+ raise ArgumentError, "Entry for #{symbol} is undefined" unless dynamic
+ end
+
define_method("#{symbol}_defined?") do
entries[symbol]&.specified?
end
diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb
index 5363533ace5..3c3f95e5b78 100644
--- a/lib/gitlab/danger/helper.rb
+++ b/lib/gitlab/danger/helper.rb
@@ -128,9 +128,12 @@ module Gitlab
%r{\A(ee/)?db/(?!fixtures)[^/]+} => :database,
%r{\A(ee/)?lib/gitlab/(database|background_migration|sql|github_import)(/|\.rb)} => :database,
%r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => :database,
+ %r{\A(ee/)?app/finders/} => :database,
%r{\Arubocop/cop/migration(/|\.rb)} => :database,
%r{\A(\.gitlab-ci\.yml\z|\.gitlab\/ci)} => :engineering_productivity,
+ %r{\A\.overcommit\.yml\.example\z} => :engineering_productivity,
+ %r{\Atooling/overcommit/} => :engineering_productivity,
%r{Dangerfile\z} => :engineering_productivity,
%r{\A(ee/)?(danger/|lib/gitlab/danger/)} => :engineering_productivity,
%r{\A(ee/)?scripts/} => :engineering_productivity,
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 3db6c3b51c0..e4e69241bd9 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -46,6 +46,7 @@ module Gitlab
push_frontend_feature_flag(:monaco_snippets, default_enabled: false)
push_frontend_feature_flag(:monaco_blobs, default_enabled: false)
push_frontend_feature_flag(:monaco_ci, default_enabled: false)
+ push_frontend_feature_flag(:snippets_edit_vue, default_enabled: false)
end
# Exposes the state of a feature flag to the frontend code.
diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb
index 2a70344374b..e7eae0a8c31 100644
--- a/lib/gitlab/import_export/members_mapper.rb
+++ b/lib/gitlab/import_export/members_mapper.rb
@@ -51,7 +51,7 @@ module Gitlab
@importable.members.destroy_all # rubocop: disable DestroyAll
- relation_class.create!(user: @user, access_level: relation_class::MAINTAINER, source_id: @importable.id, importing: true)
+ relation_class.create!(user: @user, access_level: highest_access_level, source_id: @importable.id, importing: true)
rescue => e
raise e, "Error adding importer user to #{@importable.class} members. #{e.message}"
end
@@ -59,7 +59,7 @@ module Gitlab
def user_already_member?
member = @importable.members&.first
- member&.user == @user && member.access_level >= relation_class::MAINTAINER
+ member&.user == @user && member.access_level >= highest_access_level
end
def add_team_member(member, existing_user = nil)
@@ -72,7 +72,7 @@ module Gitlab
parsed_hash(member).merge(
'source_id' => @importable.id,
'importing' => true,
- 'access_level' => [member['access_level'], relation_class::MAINTAINER].min
+ 'access_level' => [member['access_level'], highest_access_level].min
).except('user_id')
end
@@ -97,6 +97,12 @@ module Gitlab
GroupMember
end
end
+
+ def highest_access_level
+ return relation_class::OWNER if relation_class == GroupMember
+
+ relation_class::MAINTAINER
+ end
end
end
end
diff --git a/lib/gitlab/kubernetes/helm.rb b/lib/gitlab/kubernetes/helm.rb
index 9f66f35b5ab..3e201d68297 100644
--- a/lib/gitlab/kubernetes/helm.rb
+++ b/lib/gitlab/kubernetes/helm.rb
@@ -3,7 +3,7 @@
module Gitlab
module Kubernetes
module Helm
- HELM_VERSION = '2.16.1'
+ HELM_VERSION = '2.16.3'
KUBECTL_VERSION = '1.13.12'
NAMESPACE = 'gitlab-managed-apps'
NAMESPACE_LABELS = { 'app.gitlab.com/managed_by' => :gitlab }.freeze
diff --git a/lib/gitlab/reference_counter.rb b/lib/gitlab/reference_counter.rb
index 1c43de35816..5fdfa5e75ed 100644
--- a/lib/gitlab/reference_counter.rb
+++ b/lib/gitlab/reference_counter.rb
@@ -1,20 +1,42 @@
# frozen_string_literal: true
module Gitlab
+ # Reference Counter
+ #
+ # A reference counter is used as a mechanism to identify when
+ # a repository is being accessed by a writable operation.
+ #
+ # Maintenance operations would use this as a clue to when it should
+ # execute significant changes in order to avoid disrupting running traffic
class ReferenceCounter
REFERENCE_EXPIRE_TIME = 600
attr_reader :gl_repository, :key
+ # Reference Counter instance
+ #
+ # @example
+ # Gitlab::ReferenceCounter.new('project-1')
+ #
+ # @see Gitlab::GlRepository::RepoType.identifier_for_repositorable
+ # @param [String] gl_repository repository identifier
def initialize(gl_repository)
@gl_repository = gl_repository
@key = "git-receive-pack-reference-counter:#{gl_repository}"
end
+ # Return the actual counter value
+ #
+ # @return [Integer] value
def value
- Gitlab::Redis::SharedState.with { |redis| (redis.get(key) || 0).to_i }
+ Gitlab::Redis::SharedState.with do |redis|
+ (redis.get(key) || 0).to_i
+ end
end
+ # Increase the counter
+ #
+ # @return [Boolean] whether operation was a success
def increase
redis_cmd do |redis|
redis.incr(key)
@@ -22,26 +44,51 @@ module Gitlab
end
end
- # rubocop:disable Gitlab/RailsLogger
+ # Decrease the counter
+ #
+ # @return [Boolean] whether operation was a success
def decrease
redis_cmd do |redis|
current_value = redis.decr(key)
if current_value < 0
+ # rubocop:disable Gitlab/RailsLogger
Rails.logger.warn("Reference counter for #{gl_repository} decreased" \
- " when its value was less than 1. Reseting the counter.")
+ " when its value was less than 1. Resetting the counter.")
+ # rubocop:enable Gitlab/RailsLogger
redis.del(key)
end
end
end
- # rubocop:enable Gitlab/RailsLogger
+
+ # Reset the reference counter
+ #
+ # @private Used internally by SRE and debugging purpose
+ # @return [Boolean] whether reset was a success
+ def reset!
+ redis_cmd do |redis|
+ redis.del(key)
+ end
+ end
+
+ # When the reference counter would expire
+ #
+ # @api private Used internally by SRE and debugging purpose
+ # @return [Integer] Number in seconds until expiration or false if never
+ def expires_in
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.ttl(key)
+ end
+ end
private
def redis_cmd
Gitlab::Redis::SharedState.with { |redis| yield(redis) }
+
true
rescue => e
Rails.logger.warn("GitLab: An unexpected error occurred in writing to Redis: #{e}") # rubocop:disable Gitlab/RailsLogger
+
false
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f241503c40d..430c4b932b0 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1728,6 +1728,9 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while adding formatted title for epic"
+msgstr ""
+
msgid "An error occurred while checking group path"
msgstr ""
@@ -16372,9 +16375,6 @@ msgstr ""
msgid "Resolve conflicts on source branch"
msgstr ""
-msgid "Resolve discussion"
-msgstr ""
-
msgid "Resolve thread"
msgstr ""
@@ -20780,9 +20780,6 @@ msgstr ""
msgid "Unresolve"
msgstr ""
-msgid "Unresolve discussion"
-msgstr ""
-
msgid "Unresolve thread"
msgstr ""
diff --git a/spec/finders/serverless_domain_finder_spec.rb b/spec/finders/serverless_domain_finder_spec.rb
index 3fe82264cda..c41f09535d3 100644
--- a/spec/finders/serverless_domain_finder_spec.rb
+++ b/spec/finders/serverless_domain_finder_spec.rb
@@ -5,12 +5,34 @@ require 'spec_helper'
describe ServerlessDomainFinder do
let(:function_name) { 'test-function' }
let(:pages_domain_name) { 'serverless.gitlab.io' }
- let(:pages_domain) { create(:pages_domain, :instance_serverless, domain: pages_domain_name) }
- let!(:serverless_domain_cluster) { create(:serverless_domain_cluster, uuid: 'abcdef12345678', pages_domain: pages_domain) }
let(:valid_cluster_uuid) { 'aba1cdef123456f278' }
let(:invalid_cluster_uuid) { 'aba1cdef123456f178' }
let!(:environment) { create(:environment, name: 'test') }
+ let(:pages_domain) do
+ create(
+ :pages_domain,
+ :instance_serverless,
+ domain: pages_domain_name
+ )
+ end
+
+ let(:knative_with_ingress) do
+ create(
+ :clusters_applications_knative,
+ external_ip: '10.0.0.1'
+ )
+ end
+
+ let!(:serverless_domain_cluster) do
+ create(
+ :serverless_domain_cluster,
+ uuid: 'abcdef12345678',
+ pages_domain: pages_domain,
+ knative: knative_with_ingress
+ )
+ end
+
let(:valid_uri) { "https://#{function_name}-#{valid_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{pages_domain_name}" }
let(:valid_fqdn) { "#{function_name}-#{valid_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{pages_domain_name}" }
let(:invalid_uri) { "https://#{function_name}-#{invalid_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{pages_domain_name}" }
diff --git a/spec/fixtures/api/schemas/internal/serverless/lookup_path.json b/spec/fixtures/api/schemas/internal/serverless/lookup_path.json
new file mode 100644
index 00000000000..c20ea926587
--- /dev/null
+++ b/spec/fixtures/api/schemas/internal/serverless/lookup_path.json
@@ -0,0 +1,28 @@
+{
+ "type": "object",
+ "required": [
+ "source"
+ ],
+ "properties": {
+ "source": { "type": "object",
+ "required": ["type", "service", "cluster"],
+ "properties" : {
+ "type": { "type": "string", "enum": ["serverless"] },
+ "service": { "type": "string" },
+ "cluster": { "type": "object",
+ "required": ["hostname", "address", "port", "cert", "key"],
+ "properties": {
+ "hostname": { "type": "string" },
+ "address": { "type": "string" },
+ "port": { "type": "integer" },
+ "cert": { "type": "string" },
+ "key": { "type": "string" }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/internal/serverless/virtual_domain.json b/spec/fixtures/api/schemas/internal/serverless/virtual_domain.json
new file mode 100644
index 00000000000..50e899ef2f8
--- /dev/null
+++ b/spec/fixtures/api/schemas/internal/serverless/virtual_domain.json
@@ -0,0 +1,14 @@
+{
+ "type": "object",
+ "required": [
+ "lookup_paths",
+ "certificate",
+ "key"
+ ],
+ "properties": {
+ "certificate": { "type": ["string", "null"] },
+ "key": { "type": ["string", "null"] },
+ "lookup_paths": { "type": "array", "items": { "$ref": "lookup_path.json" } }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/frontend/create_cluster/gke_cluster/helpers.js b/spec/frontend/create_cluster/gke_cluster/helpers.js
new file mode 100644
index 00000000000..52b43b82698
--- /dev/null
+++ b/spec/frontend/create_cluster/gke_cluster/helpers.js
@@ -0,0 +1,64 @@
+import {
+ gapiProjectsResponseMock,
+ gapiZonesResponseMock,
+ gapiMachineTypesResponseMock,
+} from './mock_data';
+
+const cloudbilling = {
+ projects: {
+ getBillingInfo: jest.fn(
+ () =>
+ new Promise(resolve => {
+ resolve({
+ result: { billingEnabled: true },
+ });
+ }),
+ ),
+ },
+};
+
+const cloudresourcemanager = {
+ projects: {
+ list: jest.fn(
+ () =>
+ new Promise(resolve => {
+ resolve({
+ result: { ...gapiProjectsResponseMock },
+ });
+ }),
+ ),
+ },
+};
+
+const compute = {
+ zones: {
+ list: jest.fn(
+ () =>
+ new Promise(resolve => {
+ resolve({
+ result: { ...gapiZonesResponseMock },
+ });
+ }),
+ ),
+ },
+ machineTypes: {
+ list: jest.fn(
+ () =>
+ new Promise(resolve => {
+ resolve({
+ result: { ...gapiMachineTypesResponseMock },
+ });
+ }),
+ ),
+ },
+};
+
+const gapi = {
+ client: {
+ cloudbilling,
+ cloudresourcemanager,
+ compute,
+ },
+};
+
+export { gapi as default };
diff --git a/spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js b/spec/frontend/create_cluster/gke_cluster/stores/actions_spec.js
index 7ceaeace82f..8c3525207d6 100644
--- a/spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/stores/actions_spec.js
@@ -1,7 +1,7 @@
-import testAction from 'spec/helpers/vuex_action_helper';
+import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/create_cluster/gke_cluster/store/actions';
import { createStore } from '~/create_cluster/gke_cluster/store';
-import { gapi } from '../helpers';
+import gapi from '../helpers';
import { selectedProjectMock, selectedZoneMock, selectedMachineTypeMock } from '../mock_data';
describe('GCP Cluster Dropdown Store Actions', () => {
@@ -65,9 +65,10 @@ describe('GCP Cluster Dropdown Store Actions', () => {
describe('async fetch methods', () => {
let originalGapi;
+
beforeAll(() => {
originalGapi = window.gapi;
- window.gapi = gapi();
+ window.gapi = gapi;
});
afterAll(() => {
diff --git a/spec/frontend/self_monitor/components/self_monitor_form_spec.js b/spec/frontend/self_monitor/components/self_monitor_form_spec.js
index 50b97ae914d..6532c6ed2c7 100644
--- a/spec/frontend/self_monitor/components/self_monitor_form_spec.js
+++ b/spec/frontend/self_monitor/components/self_monitor_form_spec.js
@@ -72,11 +72,17 @@ describe('self monitor component', () => {
selfMonitoringProjectExists: true,
createSelfMonitoringProjectPath: '/create',
deleteSelfMonitoringProjectPath: '/delete',
+ selfMonitoringProjectFullPath: 'instance-administrators-random/gitlab-self-monitoring',
});
wrapper = shallowMount(SelfMonitor, { store });
- expect(wrapper.vm.selfMonitoringFormText).toContain('<a href="http://localhost/">');
+ expect(
+ wrapper
+ .find({ ref: 'selfMonitoringFormText' })
+ .find('a')
+ .attributes('href'),
+ ).toEqual('http://localhost/instance-administrators-random/gitlab-self-monitoring');
});
});
});
diff --git a/spec/javascripts/create_cluster/gke_cluster/helpers.js b/spec/javascripts/create_cluster/gke_cluster/helpers.js
deleted file mode 100644
index 6df511e9157..00000000000
--- a/spec/javascripts/create_cluster/gke_cluster/helpers.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import {
- gapiProjectsResponseMock,
- gapiZonesResponseMock,
- gapiMachineTypesResponseMock,
-} from './mock_data';
-
-// eslint-disable-next-line import/prefer-default-export
-export const gapi = () => ({
- client: {
- cloudbilling: {
- projects: {
- getBillingInfo: () =>
- new Promise(resolve => {
- resolve({
- result: { billingEnabled: true },
- });
- }),
- },
- },
- cloudresourcemanager: {
- projects: {
- list: () =>
- new Promise(resolve => {
- resolve({
- result: { ...gapiProjectsResponseMock },
- });
- }),
- },
- },
- compute: {
- zones: {
- list: () =>
- new Promise(resolve => {
- resolve({
- result: { ...gapiZonesResponseMock },
- });
- }),
- },
- machineTypes: {
- list: () =>
- new Promise(resolve => {
- resolve({
- result: { ...gapiMachineTypesResponseMock },
- });
- }),
- },
- },
- },
-});
diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
new file mode 100644
index 00000000000..410aef1cd53
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
@@ -0,0 +1,265 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Entry::Processable do
+ let(:node_class) do
+ Class.new(::Gitlab::Config::Entry::Node) do
+ include Gitlab::Ci::Config::Entry::Processable
+
+ def self.name
+ 'job'
+ end
+ end
+ end
+
+ let(:entry) { node_class.new(config, name: :rspec) }
+
+ describe 'validations' do
+ before do
+ entry.compose!
+ end
+
+ context 'when entry config value is correct' do
+ let(:config) { { stage: 'test' } }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ context 'when job name is empty' do
+ let(:entry) { node_class.new(config, name: ''.to_sym) }
+
+ it 'reports error' do
+ expect(entry.errors).to include "job name can't be blank"
+ end
+ end
+ end
+
+ context 'when entry value is not correct' do
+ context 'incorrect config value type' do
+ let(:config) { ['incorrect'] }
+
+ describe '#errors' do
+ it 'reports error about a config type' do
+ expect(entry.errors)
+ .to include 'job config should be a hash'
+ end
+ end
+ end
+
+ context 'when config is empty' do
+ let(:config) { {} }
+
+ describe '#valid' do
+ it 'is invalid' do
+ expect(entry).not_to be_valid
+ end
+ end
+ end
+
+ context 'when extends key is not a string' do
+ let(:config) { { extends: 123 } }
+
+ it 'returns error about wrong value type' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include "job extends should be an array of strings or a string"
+ end
+ end
+
+ context 'when it uses both "when:" and "rules:"' do
+ let(:config) do
+ {
+ script: 'echo',
+ when: 'on_failure',
+ rules: [{ if: '$VARIABLE', when: 'on_success' }]
+ }
+ end
+
+ it 'returns an error about when: being combined with rules' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job config key may not be used with `rules`: when'
+ end
+ end
+
+ context 'when only: is used with rules:' do
+ let(:config) { { only: ['merge_requests'], rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing only: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+
+ context 'and only: is blank' do
+ let(:config) { { only: nil, rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing only: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+
+ context 'and rules: is blank' do
+ let(:config) { { only: ['merge_requests'], rules: nil } }
+
+ it 'returns error about mixing only: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+ end
+
+ context 'when except: is used with rules:' do
+ let(:config) { { except: { refs: %w[master] }, rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+
+ context 'and except: is blank' do
+ let(:config) { { except: nil, rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+
+ context 'and rules: is blank' do
+ let(:config) { { except: { refs: %w[master] }, rules: nil } }
+
+ it 'returns error about mixing except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+ end
+
+ context 'when only: and except: are both used with rules:' do
+ let(:config) do
+ {
+ only: %w[merge_requests],
+ except: { refs: %w[master] },
+ rules: [{ if: '$THIS' }]
+ }
+ end
+
+ it 'returns errors about mixing both only: and except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+
+ context 'when only: and except: as both blank' do
+ let(:config) do
+ { only: nil, except: nil, rules: [{ if: '$THIS' }] }
+ end
+
+ it 'returns errors about mixing both only: and except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+
+ context 'when rules: is blank' do
+ let(:config) do
+ { only: %w[merge_requests], except: { refs: %w[master] }, rules: nil }
+ end
+
+ it 'returns errors about mixing both only: and except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+ end
+ end
+ end
+
+ describe '#relevant?' do
+ it 'is a relevant entry' do
+ entry = node_class.new({ stage: 'test' }, name: :rspec)
+
+ expect(entry).to be_relevant
+ end
+ end
+
+ describe '#compose!' do
+ let(:specified) do
+ double('specified', 'specified?' => true, value: 'specified')
+ end
+
+ let(:unspecified) { double('unspecified', 'specified?' => false) }
+ let(:default) { double('default', '[]' => unspecified) }
+ let(:workflow) { double('workflow', 'has_rules?' => false) }
+ let(:deps) { double('deps', 'default' => default, '[]' => unspecified, 'workflow' => workflow) }
+
+ context 'with workflow rules' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:name, :has_workflow_rules?, :only, :rules, :result) do
+ "uses default only" | false | nil | nil | { refs: %w[branches tags] }
+ "uses user only" | false | %w[branches] | nil | { refs: %w[branches] }
+ "does not define only" | false | nil | [] | nil
+ "does not define only" | true | nil | nil | nil
+ "uses user only" | true | %w[branches] | nil | { refs: %w[branches] }
+ "does not define only" | true | nil | [] | nil
+ end
+
+ with_them do
+ let(:config) { { script: 'ls', rules: rules, only: only }.compact }
+
+ it "#{name}" do
+ expect(workflow).to receive(:has_rules?) { has_workflow_rules? }
+
+ entry.compose!(deps)
+
+ expect(entry.only_value).to eq(result)
+ end
+ end
+ end
+
+ context 'when workflow rules is used' do
+ context 'when rules are used' do
+ let(:config) { { script: 'ls', cache: { key: 'test' }, rules: [] } }
+
+ it 'does not define only' do
+ expect(entry).not_to be_only_defined
+ end
+ end
+
+ context 'when rules are not used' do
+ let(:config) { { script: 'ls', cache: { key: 'test' }, only: [] } }
+
+ it 'does not define only' do
+ expect(entry).not_to be_only_defined
+ end
+ end
+ end
+ end
+
+ context 'when composed' do
+ before do
+ entry.compose!
+ end
+
+ describe '#value' do
+ context 'when entry is correct' do
+ let(:config) do
+ { stage: 'test' }
+ end
+
+ it 'returns correct value' do
+ expect(entry.value)
+ .to eq(name: :rspec,
+ stage: 'test',
+ only: { refs: %w[branches tags] })
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index cfc3d852de0..e303557bd00 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -2419,7 +2419,9 @@ module Gitlab
it 'returns errors and empty configuration' do
expect(subject.valid?).to eq(false)
- expect(subject.errors).to eq(['jobs:rspec config contains unknown keys: bad_tags', 'jobs:rspec rules should be an array of hashes'])
+ expect(subject.errors).to contain_exactly(
+ 'jobs:rspec config contains unknown keys: bad_tags',
+ 'jobs:rspec rules should be an array of hashes')
expect(subject.content).to be_blank
end
end
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
index 2561e763429..4b378936965 100644
--- a/spec/lib/gitlab/danger/helper_spec.rb
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -218,6 +218,8 @@ describe Gitlab::Danger::Helper do
'scripts/foo' | :engineering_productivity
'lib/gitlab/danger/foo' | :engineering_productivity
'ee/lib/gitlab/danger/foo' | :engineering_productivity
+ '.overcommit.yml.example' | :engineering_productivity
+ 'tooling/overcommit/foo' | :engineering_productivity
'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml' | :backend
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index 01a7901062a..7e2b5ed534f 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -4,167 +4,191 @@ require 'spec_helper'
describe Gitlab::ImportExport::MembersMapper do
describe 'map members' do
- let(:user) { create(:admin) }
- let(:project) { create(:project, :public, name: 'searchable_project') }
- let(:user2) { create(:user) }
- let(:exported_user_id) { 99 }
- let(:exported_members) do
- [{
- "id" => 2,
- "access_level" => 40,
- "source_id" => 14,
- "source_type" => "Project",
- "notification_level" => 3,
- "created_at" => "2016-03-11T10:21:44.822Z",
- "updated_at" => "2016-03-11T10:21:44.822Z",
- "created_by_id" => nil,
- "invite_email" => nil,
- "invite_token" => nil,
- "invite_accepted_at" => nil,
- "user" =>
- {
- "id" => exported_user_id,
- "email" => user2.email,
- "username" => 'test'
- },
- "user_id" => 19
- },
- {
- "id" => 3,
- "access_level" => 40,
- "source_id" => 14,
- "source_type" => "Project",
- "user_id" => nil,
- "notification_level" => 3,
- "created_at" => "2016-03-11T10:21:44.822Z",
- "updated_at" => "2016-03-11T10:21:44.822Z",
- "created_by_id" => 1,
- "invite_email" => 'invite@test.com',
- "invite_token" => 'token',
- "invite_accepted_at" => nil
- }]
- end
-
- let(:members_mapper) do
- described_class.new(
- exported_members: exported_members, user: user, importable: project)
- end
-
- it 'includes the exported user ID in the map' do
- expect(members_mapper.map.keys).to include(exported_user_id)
- end
-
- it 'maps a project member' do
- expect(members_mapper.map[exported_user_id]).to eq(user2.id)
- end
-
- it 'defaults to importer project member if it does not exist' do
- expect(members_mapper.map[-1]).to eq(user.id)
- end
-
- it 'has invited members with no user' do
- members_mapper.map
-
- expect(ProjectMember.find_by_invite_email('invite@test.com')).not_to be_nil
- end
-
- it 'authorizes the users to the project' do
- members_mapper.map
-
- expect(user.authorized_project?(project)).to be true
- expect(user2.authorized_project?(project)).to be true
- end
-
- it 'maps an owner as a maintainer' do
- exported_members.first['access_level'] = ProjectMember::OWNER
-
- expect(members_mapper.map[exported_user_id]).to eq(user2.id)
- expect(ProjectMember.find_by_user_id(user2.id).access_level).to eq(ProjectMember::MAINTAINER)
- end
-
- it 'removes old user_id from member_hash to avoid conflict with user key' do
- expect(ProjectMember)
- .to receive(:create)
- .twice
- .with(hash_excluding('user_id'))
- .and_call_original
-
- members_mapper.map
- end
-
- context 'user is not an admin' do
- let(:user) { create(:user) }
-
- it 'does not map a project member' do
- expect(members_mapper.map[exported_user_id]).to eq(user.id)
+ shared_examples 'imports exported members' do
+ let(:user) { create(:admin) }
+ let(:user2) { create(:user) }
+ let(:exported_user_id) { 99 }
+ let(:exported_members) do
+ [{
+ "id" => 2,
+ "access_level" => 40,
+ "source_id" => 14,
+ "source_type" => source_type,
+ "notification_level" => 3,
+ "created_at" => "2016-03-11T10:21:44.822Z",
+ "updated_at" => "2016-03-11T10:21:44.822Z",
+ "created_by_id" => nil,
+ "invite_email" => nil,
+ "invite_token" => nil,
+ "invite_accepted_at" => nil,
+ "user" =>
+ {
+ "id" => exported_user_id,
+ "email" => user2.email,
+ "username" => 'test'
+ },
+ "user_id" => 19
+ },
+ {
+ "id" => 3,
+ "access_level" => 40,
+ "source_id" => 14,
+ "source_type" => source_type,
+ "user_id" => nil,
+ "notification_level" => 3,
+ "created_at" => "2016-03-11T10:21:44.822Z",
+ "updated_at" => "2016-03-11T10:21:44.822Z",
+ "created_by_id" => 1,
+ "invite_email" => 'invite@test.com',
+ "invite_token" => 'token',
+ "invite_accepted_at" => nil
+ }]
end
- it 'defaults to importer project member if it does not exist' do
- expect(members_mapper.map[-1]).to eq(user.id)
+ let(:members_mapper) do
+ described_class.new(
+ exported_members: exported_members, user: user, importable: importable)
end
- end
- context 'chooses the one with an email first' do
- let(:user3) { create(:user, username: 'test') }
+ it 'includes the exported user ID in the map' do
+ expect(members_mapper.map.keys).to include(exported_user_id)
+ end
- it 'maps the project member that has a matching email first' do
+ it 'maps a member' do
expect(members_mapper.map[exported_user_id]).to eq(user2.id)
end
- end
- context 'importer same as group member' do
- let(:user2) { create(:admin) }
- let(:group) { create(:group) }
- let(:project) { create(:project, :public, name: 'searchable_project', namespace: group) }
- let(:members_mapper) do
- described_class.new(
- exported_members: exported_members, user: user2, importable: project)
+ it 'defaults to importer member if it does not exist' do
+ expect(members_mapper.map[-1]).to eq(user.id)
end
- before do
- group.add_users([user, user2], GroupMember::DEVELOPER)
- end
+ it 'has invited members with no user' do
+ members_mapper.map
- it 'maps the project member' do
- expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ expect(member_class.find_by_invite_email('invite@test.com')).not_to be_nil
end
- it 'maps the project member if it already exists' do
- project.add_maintainer(user2)
+ it 'removes old user_id from member_hash to avoid conflict with user key' do
+ expect(member_class)
+ .to receive(:create)
+ .twice
+ .with(hash_excluding('user_id'))
+ .and_call_original
- expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ members_mapper.map
end
- end
- context 'importing group members' do
- let(:group) { create(:group) }
- let(:project) { create(:project, namespace: group) }
- let(:members_mapper) do
- described_class.new(
- exported_members: exported_members, user: user, importable: project)
- end
+ context 'user is not an admin' do
+ let(:user) { create(:user) }
- before do
- group.add_users([user, user2], GroupMember::DEVELOPER)
- user.update(email: 'invite@test.com')
+ it 'does not map a member' do
+ expect(members_mapper.map[exported_user_id]).to eq(user.id)
+ end
+
+ it 'defaults to importer member if it does not exist' do
+ expect(members_mapper.map[-1]).to eq(user.id)
+ end
end
- it 'maps the importer' do
- expect(members_mapper.map[-1]).to eq(user.id)
+ context 'chooses the one with an email' do
+ let(:user3) { create(:user, username: 'test') }
+
+ it 'maps the member that has a matching email' do
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ end
end
+ end
- it 'maps the group member' do
- expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ context 'when importable is Project' do
+ include_examples 'imports exported members' do
+ let(:source_type) { 'Project' }
+ let(:member_class) { ProjectMember }
+ let(:importable) { create(:project, :public, name: 'searchable_project') }
+
+ it 'authorizes the users to the project' do
+ members_mapper.map
+
+ expect(user.authorized_project?(importable)).to be true
+ expect(user2.authorized_project?(importable)).to be true
+ end
+
+ it 'maps an owner as a maintainer' do
+ exported_members.first['access_level'] = ProjectMember::OWNER
+
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ expect(member_class.find_by_user_id(user2.id).access_level).to eq(ProjectMember::MAINTAINER)
+ end
+
+ context 'importer same as group member' do
+ let(:user2) { create(:admin) }
+ let(:group) { create(:group) }
+ let(:importable) { create(:project, :public, name: 'searchable_project', namespace: group) }
+ let(:members_mapper) do
+ described_class.new(
+ exported_members: exported_members, user: user2, importable: importable)
+ end
+
+ before do
+ group.add_users([user, user2], GroupMember::DEVELOPER)
+ end
+
+ it 'maps the project member' do
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ end
+
+ it 'maps the project member if it already exists' do
+ importable.add_maintainer(user2)
+
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ end
+ end
+
+ context 'importing group members' do
+ let(:group) { create(:group) }
+ let(:importable) { create(:project, namespace: group) }
+ let(:members_mapper) do
+ described_class.new(
+ exported_members: exported_members, user: user, importable: importable)
+ end
+
+ before do
+ group.add_users([user, user2], GroupMember::DEVELOPER)
+ user.update(email: 'invite@test.com')
+ end
+
+ it 'maps the importer' do
+ expect(members_mapper.map[-1]).to eq(user.id)
+ end
+
+ it 'maps the group member' do
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ end
+ end
+
+ context 'when importer mapping fails' do
+ let(:exception_message) { 'Something went wrong' }
+
+ it 'includes importer specific error message' do
+ expect(member_class).to receive(:create!).and_raise(StandardError.new(exception_message))
+
+ expect { members_mapper.map }.to raise_error(StandardError, "Error adding importer user to Project members. #{exception_message}")
+ end
+ end
end
end
- context 'when importer mapping fails' do
- let(:exception_message) { 'Something went wrong' }
+ context 'when importable is Group' do
+ include_examples 'imports exported members' do
+ let(:source_type) { 'Namespace' }
+ let(:member_class) { GroupMember }
+ let(:importable) { create(:group) }
- it 'includes importer specific error message' do
- expect(ProjectMember).to receive(:create!).and_raise(StandardError.new(exception_message))
+ it 'does not lower owner access level' do
+ exported_members.first['access_level'] = member_class::OWNER
- expect { members_mapper.map }.to raise_error(StandardError, "Error adding importer user to Project members. #{exception_message}")
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ expect(member_class.find_by_user_id(user2.id).access_level).to eq(member_class::OWNER)
+ end
end
end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index 24a734a2915..3c62219a9a5 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -32,7 +32,7 @@ describe Gitlab::Kubernetes::Helm::Pod do
it 'generates the appropriate specifications for the container' do
container = subject.generate.spec.containers.first
expect(container.name).to eq('helm')
- expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.16.1-kube-1.13.12')
+ expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.16.3-kube-1.13.12')
expect(container.env.count).to eq(3)
expect(container.env.map(&:name)).to match_array([:HELM_VERSION, :TILLER_NAMESPACE, :COMMAND_SCRIPT])
expect(container.command).to match_array(["/bin/sh"])
diff --git a/spec/lib/gitlab/reference_counter_spec.rb b/spec/lib/gitlab/reference_counter_spec.rb
index f9361d08faf..ae7b18ca007 100644
--- a/spec/lib/gitlab/reference_counter_spec.rb
+++ b/spec/lib/gitlab/reference_counter_spec.rb
@@ -2,38 +2,54 @@
require 'spec_helper'
-describe Gitlab::ReferenceCounter do
- let(:redis) { double('redis') }
- let(:reference_counter_key) { "git-receive-pack-reference-counter:project-1" }
+describe Gitlab::ReferenceCounter, :clean_gitlab_redis_shared_state do
let(:reference_counter) { described_class.new('project-1') }
- before do
- allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis)
+ describe '#increase' do
+ it 'increases and sets the expire time of a reference count for a path' do
+ expect { reference_counter.increase }.to change { reference_counter.value }.by(1)
+ expect(reference_counter.expires_in).to be_positive
+ expect(reference_counter.increase).to be(true)
+ end
end
- it 'increases and set the expire time of a reference count for a path' do
- expect(redis).to receive(:incr).with(reference_counter_key)
- expect(redis).to receive(:expire).with(reference_counter_key,
- described_class::REFERENCE_EXPIRE_TIME)
- expect(reference_counter.increase).to be(true)
+ describe '#decrease' do
+ it 'decreases the reference count for a path' do
+ reference_counter.increase
+
+ expect { reference_counter.decrease }.to change { reference_counter.value }.by(-1)
+ end
+
+ it 'warns if attempting to decrease a counter with a value of zero or less, and resets the counter' do
+ expect(Rails.logger).to receive(:warn).with("Reference counter for project-1" \
+ " decreased when its value was less than 1. Resetting the counter.")
+ expect { reference_counter.decrease }.not_to change { reference_counter.value }
+ end
end
- it 'decreases the reference count for a path' do
- allow(redis).to receive(:decr).and_return(0)
- expect(redis).to receive(:decr).with(reference_counter_key)
- expect(reference_counter.decrease).to be(true)
+ describe '#value' do
+ it 'get the reference count for a path' do
+ expect(reference_counter.value).to eq(0)
+
+ reference_counter.increase
+
+ expect(reference_counter.value).to eq(1)
+ end
end
- it 'warns if attempting to decrease a counter with a value of one or less, and resets the counter' do
- expect(redis).to receive(:decr).and_return(-1)
- expect(redis).to receive(:del)
- expect(Rails.logger).to receive(:warn).with("Reference counter for project-1" \
- " decreased when its value was less than 1. Reseting the counter.")
- expect(reference_counter.decrease).to be(true)
+ describe '#reset!' do
+ it 'resets reference count down to zero' do
+ 3.times { reference_counter.increase }
+
+ expect { reference_counter.reset! }.to change { reference_counter.value}.from(3).to(0)
+ end
end
- it 'get the reference count for a path' do
- allow(redis).to receive(:get).and_return(1)
- expect(reference_counter.value).to be(1)
+ describe '#expires_in' do
+ it 'displays the expiration time in seconds' do
+ reference_counter.increase
+
+ expect(reference_counter.expires_in).to be_between(500, 600)
+ end
end
end
diff --git a/spec/migrations/update_application_setting_npm_package_requests_forwarding_default_spec.rb b/spec/migrations/update_application_setting_npm_package_requests_forwarding_default_spec.rb
new file mode 100644
index 00000000000..dfe14b40c6e
--- /dev/null
+++ b/spec/migrations/update_application_setting_npm_package_requests_forwarding_default_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20200221105436_update_application_setting_npm_package_requests_forwarding_default.rb')
+
+describe UpdateApplicationSettingNpmPackageRequestsForwardingDefault, :migration do
+ # Create test data - pipeline and CI/CD jobs.
+ let(:application_settings) { table(:application_settings) }
+
+ before do
+ application_settings.create!(npm_package_requests_forwarding: false)
+ end
+
+ # Test just the up migration.
+ it 'correctly migrates the application setting' do
+ expect { migrate! }.to change { current_application_setting }.from(false).to(true)
+ end
+
+ # Test a reversible migration.
+ it 'correctly migrates up and down the application setting' do
+ reversible_migration do |migration|
+ # Expectations will run before the up migration,
+ # and then again after the down migration
+ migration.before -> {
+ expect(current_application_setting).to eq false
+ }
+
+ # Expectations will run after the up migration.
+ migration.after -> {
+ expect(current_application_setting).to eq true
+ }
+ end
+ end
+
+ def current_application_setting
+ ApplicationSetting.current_without_cache.npm_package_requests_forwarding
+ end
+end
diff --git a/spec/requests/api/internal/pages_spec.rb b/spec/requests/api/internal/pages_spec.rb
index 9a8c1a0e03b..99cb2bfe221 100644
--- a/spec/requests/api/internal/pages_spec.rb
+++ b/spec/requests/api/internal/pages_spec.rb
@@ -56,6 +56,88 @@ describe API::Internal::Pages do
end
end
+ context 'serverless domain' do
+ let(:namespace) { create(:namespace, name: 'gitlab-org') }
+ let(:project) { create(:project, namespace: namespace, name: 'gitlab-ce') }
+ let(:environment) { create(:environment, project: project) }
+ let(:pages_domain) { create(:pages_domain, domain: 'serverless.gitlab.io') }
+ let(:knative_without_ingress) { create(:clusters_applications_knative) }
+ let(:knative_with_ingress) { create(:clusters_applications_knative, external_ip: '10.0.0.1') }
+
+ context 'without a knative ingress gateway IP' do
+ let!(:serverless_domain_cluster) do
+ create(
+ :serverless_domain_cluster,
+ uuid: 'abcdef12345678',
+ pages_domain: pages_domain,
+ knative: knative_without_ingress
+ )
+ end
+
+ let(:serverless_domain) do
+ create(
+ :serverless_domain,
+ serverless_domain_cluster: serverless_domain_cluster,
+ environment: environment
+ )
+ end
+
+ it 'responds with 204 no content' do
+ query_host(serverless_domain.uri.host)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(response.body).to be_empty
+ end
+ end
+
+ context 'with a knative ingress gateway IP' do
+ let!(:serverless_domain_cluster) do
+ create(
+ :serverless_domain_cluster,
+ uuid: 'abcdef12345678',
+ pages_domain: pages_domain,
+ knative: knative_with_ingress
+ )
+ end
+
+ let(:serverless_domain) do
+ create(
+ :serverless_domain,
+ serverless_domain_cluster: serverless_domain_cluster,
+ environment: environment
+ )
+ end
+
+ it 'responds with proxy configuration' do
+ query_host(serverless_domain.uri.host)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('internal/serverless/virtual_domain')
+
+ expect(json_response['certificate']).to eq(pages_domain.certificate)
+ expect(json_response['key']).to eq(pages_domain.key)
+
+ expect(json_response['lookup_paths']).to eq(
+ [
+ {
+ 'source' => {
+ 'type' => 'serverless',
+ 'service' => "test-function.#{project.name}-#{project.id}-#{environment.slug}.#{serverless_domain_cluster.knative.hostname}",
+ 'cluster' => {
+ 'hostname' => serverless_domain_cluster.knative.hostname,
+ 'address' => serverless_domain_cluster.knative.external_ip,
+ 'port' => 443,
+ 'cert' => serverless_domain_cluster.certificate,
+ 'key' => serverless_domain_cluster.key
+ }
+ }
+ }
+ ]
+ )
+ end
+ end
+ end
+
context 'custom domain' do
let(:namespace) { create(:namespace, name: 'gitlab-org') }
let(:project) { create(:project, namespace: namespace, name: 'gitlab-ce') }
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index 0f2d994efd4..2da1350e2af 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -612,7 +612,8 @@ module Ci
allow(attempt_counter).to receive(:increment)
expect(job_queue_duration_seconds).to receive(:observe)
.with({ shared_runner: expected_shared_runner,
- jobs_running_for_project: expected_jobs_running_for_project_first_job }, 1800)
+ jobs_running_for_project: expected_jobs_running_for_project_first_job,
+ shard: expected_shard }, 1800)
execute(runner)
end
@@ -625,7 +626,8 @@ module Ci
allow(attempt_counter).to receive(:increment)
expect(job_queue_duration_seconds).to receive(:observe)
.with({ shared_runner: expected_shared_runner,
- jobs_running_for_project: expected_jobs_running_for_project_third_job }, 1800)
+ jobs_running_for_project: expected_jobs_running_for_project_third_job,
+ shard: expected_shard }, 1800)
execute(runner)
end
@@ -638,13 +640,28 @@ module Ci
end
context 'when shared runner is used' do
- let(:runner) { shared_runner }
+ let(:runner) { create(:ci_runner, :instance, tag_list: %w(tag1 tag2)) }
let(:expected_shared_runner) { true }
+ let(:expected_shard) { Ci::RegisterJobService::DEFAULT_METRICS_SHARD }
let(:expected_jobs_running_for_project_first_job) { 0 }
let(:expected_jobs_running_for_project_third_job) { 2 }
it_behaves_like 'metrics collector'
+ context 'when metrics_shard tag is defined' do
+ let(:runner) { create(:ci_runner, :instance, tag_list: %w(tag1 metrics_shard::shard_tag tag2)) }
+ let(:expected_shard) { 'shard_tag' }
+
+ it_behaves_like 'metrics collector'
+ end
+
+ context 'when multiple metrics_shard tag is defined' do
+ let(:runner) { create(:ci_runner, :instance, tag_list: %w(tag1 metrics_shard::shard_tag metrics_shard::shard_tag_2 tag2)) }
+ let(:expected_shard) { 'shard_tag' }
+
+ it_behaves_like 'metrics collector'
+ end
+
context 'when pending job with queued_at=nil is used' do
before do
pending_job.update(queued_at: nil)
@@ -662,8 +679,9 @@ module Ci
end
context 'when specific runner is used' do
- let(:runner) { specific_runner }
+ let(:runner) { create(:ci_runner, :project, projects: [project], tag_list: %w(tag1 metrics_shard::shard_tag tag2)) }
let(:expected_shared_runner) { false }
+ let(:expected_shard) { Ci::RegisterJobService::DEFAULT_METRICS_SHARD }
let(:expected_jobs_running_for_project_first_job) { '+Inf' }
let(:expected_jobs_running_for_project_third_job) { '+Inf' }